sqlite3
Advanced tools
| #define timespecclear(tsp) (tsp)->tv_sec = (tsp)->tv_nsec = 0 | ||
| #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) | ||
| #define timespecisvalid(tsp) \ | ||
| ((tsp)->tv_nsec >= 0 && (tsp)->tv_nsec < 1000000000L) | ||
| #define timespeccmp(tsp, usp, cmp) \ | ||
| (((tsp)->tv_sec == (usp)->tv_sec) ? \ | ||
| ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ | ||
| ((tsp)->tv_sec cmp (usp)->tv_sec)) | ||
| #define timespecsub(tsp, usp, vsp) \ | ||
| do { \ | ||
| (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ | ||
| (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ | ||
| if ((vsp)->tv_nsec < 0) { \ | ||
| (vsp)->tv_sec--; \ | ||
| (vsp)->tv_nsec += 1000000000L; \ | ||
| } \ | ||
| } while (0) | ||
| #define timespecafter(tsp, usp) \ | ||
| (((tsp)->tv_sec > (usp)->tv_sec) || \ | ||
| ((tsp)->tv_sec == (usp)->tv_sec && (tsp)->tv_nsec > (usp)->tv_nsec)) |
Sorry, the diff of this file is not supported yet
+152
-0
| # sqlite3-ruby Changelog | ||
| ## 2.0.0 / 2024-04-17 | ||
| This is a major release which contains some breaking changes, primarily the removal of | ||
| long-deprecated functionality. Before upgrading, please make sure to address deprecation warnings | ||
| emitted from your application using sqlite3-ruby v1.7.x. | ||
| ### Ruby | ||
| - This release drops support for Ruby 2.7. [#453] @flavorjones | ||
| ### Packaging | ||
| Native (precompiled) gems are now available for Linux Musl. [#442] @flavorjones | ||
| Here are the platforms for which native gems are shipped: | ||
| - `aarch64-linux-gnu` (requires: glibc >= 2.29) | ||
| - `aarch64-linux-musl` | ||
| - `arm-linux-gnu` (requires: glibc >= 2.29) | ||
| - `arm-linux-musl` | ||
| - `arm64-darwin` | ||
| - `x64-mingw32` / `x64-mingw-ucrt` | ||
| - `x86-linux-gnu` (requires: glibc >= 2.17) | ||
| - `x86-linux-musl` | ||
| - `x86_64-darwin` | ||
| - `x86_64-linux-gnu` (requires: glibc >= 2.17) | ||
| - `x86_64-linux-musl` | ||
| ⚠ Ruby 3.0 linux users must use Rubygems >= 3.3.22 in order to use these gems. | ||
| ⚠ Musl linux users should update to Bundler >= 2.5.6 to avoid https://github.com/rubygems/rubygems/issues/7432 | ||
| See [the INSTALLATION doc](https://github.com/sparklemotion/sqlite3-ruby/blob/main/INSTALLATION.md) for more information. | ||
| ### Dependencies | ||
| - Vendored sqlite is updated to [v3.45.3](https://sqlite.org/releaselog/3_45_3.html). @flavorjones | ||
| ### Added | ||
| - `Database#busy_handler_timeout=` introduced as an alternative to `#busy_timeout=` that can be used when it's desired to release the GVL between retries. [#443, #456] @fractaledmind | ||
| - Support the `SUPER_JOURNAL` flag which is an alias for `MASTER_JOURNAL` as of sqlite 3.33.0. [#467] @flavorjones | ||
| - `Statement#stat` and `Statement#memused` introduced to report statistics. [#461] @fractaledmind | ||
| - `Statement#sql` and `Statement#expanded_sql` introduced to retrieve the SQL statement associated with the `Statement` object. [#293, #498] @tenderlove | ||
| - `SQLite3.status` introduced to return run-time status and reset high-water marks. See `SQLite3::Constants::Status` for details. [#520] @wjlroe | ||
| ### Improved | ||
| - Avoid leaking memory for statements that are not closed properly. [#392] @haileys | ||
| - Moved some C code into Ruby. [#451, #455] @tenderlove | ||
| - Improve performance of `ResultSet` hashes. [#154, #484, #468] @tenderlove | ||
| - Fix a GC compaction issue with `busy_handler`. [#466] @byroot | ||
| - Remove unused `ResultSet` instance variable. [#469] @tenderlove | ||
| - Fix encoding for values passed to custom functions. [#218, #488] @tenderlove | ||
| ### Changed | ||
| - Consistently use `SQLite3::Exception` or subclasses. Previously some `Pragmas` methods raised `Exception`, and `Database#execute_batch2` and `Database#load_extension` raised `RuntimeError`. [#467, #490] @flavorjones | ||
| - `Database#columns` returns a list of internal frozen strings. [#155, #474, #486] @tenderlove | ||
| - Freeze results that come from the database. [#480] @tenderlove | ||
| - The encoding of a Database is no longer cached. [#485] @tenderlove | ||
| - `Database#transaction` returns the result of the block when used with a block. [#508] @alexcwatt | ||
| - `Database#execute_batch` returns the result of the last statement executed. [#512] @alexcwatt | ||
| ### Removed | ||
| - Removed class `SQLite3::Translator` and all related type translation methods which have been deprecated since v1.3.2. [#470] @tenderlove | ||
| If you need to do type translation on values returned from the statement object, please wrap it | ||
| with a delegate object. Here is an example of using a delegate class to implement type | ||
| translation: | ||
| ```ruby | ||
| require "sqlite3" | ||
| require "delegate" | ||
| db = SQLite3::Database.new(":memory:") | ||
| return_value = db.execute_batch2 <<-EOSQL | ||
| CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, name string); | ||
| INSERT INTO items (name) VALUES ("foo"); | ||
| INSERT INTO items (name) VALUES ("bar"); | ||
| EOSQL | ||
| class MyTranslator < DelegateClass(SQLite3::Statement) | ||
| def step | ||
| row = super | ||
| return if done? | ||
| row.map.with_index do |item, i| | ||
| case types[i] | ||
| when "integer" # turn all integers to floats | ||
| item.to_f | ||
| when "string" # add "hello" to all strings | ||
| item + "hello" | ||
| end | ||
| end | ||
| end | ||
| end | ||
| db.prepare("SELECT * FROM items") do |stmt| | ||
| stmt = MyTranslator.new(stmt) | ||
| while row = stmt.step | ||
| p row | ||
| end | ||
| end | ||
| ``` | ||
| - Removed `types` and `fields` readers on row objects, which have been deprecated since | ||
| v1.3.6. [#471] @tenderlove | ||
| Deprecated code looks like this: | ||
| ```ruby | ||
| row = @db.execute("select * from foo") | ||
| assert_equal ["blob"], row.first.types | ||
| ``` | ||
| If you would like to access the "types" associated with a returned query, | ||
| use a prepared statement like this: | ||
| ```ruby | ||
| @db.prepare("select * from foo") do |v| | ||
| assert_equal ["blob"], v.types | ||
| end | ||
| ``` | ||
| - Removed support for non-Array bind parameters to methods `Database#execute`, `#execute_batch`, and `#query`, which has been deprecated since v1.3.0. [#511] @flavorjones | ||
| Deprecated code looks like this: | ||
| ``` ruby | ||
| @db.query("select * from foo where a = ? and b = ? and c = ?", 1, 2, 3) | ||
| ``` | ||
| For these cases, pass the bind parameters as an array: | ||
| ``` ruby | ||
| @db.query("select * from foo where a = ? and b = ? and c = ?", [1, 2, 3]) | ||
| ``` | ||
| - Removed class `SQLite3::VersionProxy` which has been deprecated since v1.3.2. [#453] @flavorjones | ||
| - Removed methods `SQLite3::Database::FunctionProxy#count` and `#set_error` which have been broken since at least v1.3.13. [#164, #509, #510] @alexcwatt @flavorjones | ||
| ## 1.7.3 / 2024-03-15 | ||
@@ -4,0 +156,0 @@ |
+23
-1
@@ -8,2 +8,24 @@ # Contributing to sqlite3-ruby | ||
| ## Architecture notes | ||
| ### Garbage collection | ||
| All statements keep pointers back to their respective database connections. | ||
| The `@connection` instance variable on the `Statement` handle keeps the database | ||
| connection alive. Memory allocated for a statement handler will be freed in | ||
| two cases: | ||
| 1. `#close` is called on the statement | ||
| 2. The `SQLite3::Database` object gets garbage collected | ||
| We can't free the memory for the statement in the garbage collection function | ||
| for the statement handler. The reason is because there exists a race | ||
| condition. We cannot guarantee the order in which objects will be garbage | ||
| collected. So, it is possible that a connection and a statement are up for | ||
| garbage collection. If the database connection were to be free'd before the | ||
| statement, then boom. Instead we'll be conservative and free unclosed | ||
| statements when the connection is terminated. | ||
| ## Building gems | ||
@@ -30,3 +52,3 @@ | ||
| - [ ] make sure CI is green! | ||
| - [ ] update `CHANGELOG.md` and `lib/sqlite3/version.rb` including `VersionProxy::{MINOR,TINY}` | ||
| - [ ] update `CHANGELOG.md` and `lib/sqlite3/version.rb` | ||
| - [ ] run `bin/build-gems` and make sure it completes and all the tests pass | ||
@@ -33,0 +55,0 @@ - [ ] create a git tag using a format that matches the pattern `v\d+\.\d+\.\d+`, e.g. `v1.3.13` |
+10
-11
@@ -1,14 +0,13 @@ | ||
| # TODO: stop using symbols here once we no longer support Ruby 2.7 and can rely on symbolize_names | ||
| :sqlite3: | ||
| sqlite3: | ||
| # checksum verified by first checking the published sha3(256) checksum against https://sqlite.org/download.html: | ||
| # 1b02c58a711d15b50da8a1089e0f8807ebbdf3e674c714100eda9a03a69ac758 | ||
| # cc1050780e0266de4d91b31c8deaf4638336908c12c21898e9f1fcae1e2ac303 | ||
| # | ||
| # $ sha3sum -a 256 ports/archives/sqlite-autoconf-3450200.tar.gz | ||
| # 1b02c58a711d15b50da8a1089e0f8807ebbdf3e674c714100eda9a03a69ac758 ports/archives/sqlite-autoconf-3450200.tar.gz | ||
| # $ sha3sum -a 256 ports/archives/sqlite-autoconf-3450300.tar.gz | ||
| # cc1050780e0266de4d91b31c8deaf4638336908c12c21898e9f1fcae1e2ac303 ports/archives/sqlite-autoconf-3450300.tar.gz | ||
| # | ||
| # $ sha256sum ports/archives/sqlite-autoconf-3450200.tar.gz | ||
| # bc9067442eedf3dd39989b5c5cfbfff37ae66cc9c99274e0c3052dc4d4a8f6ae ports/archives/sqlite-autoconf-3450200.tar.gz | ||
| :version: "3.45.2" | ||
| :files: | ||
| - :url: "https://sqlite.org/2024/sqlite-autoconf-3450200.tar.gz" | ||
| :sha256: "bc9067442eedf3dd39989b5c5cfbfff37ae66cc9c99274e0c3052dc4d4a8f6ae" | ||
| # $ sha256sum ports/archives/sqlite-autoconf-3450300.tar.gz | ||
| # b2809ca53124c19c60f42bf627736eae011afdcc205bb48270a5ee9a38191531 ports/archives/sqlite-autoconf-3450300.tar.gz | ||
| version: "3.45.3" | ||
| files: | ||
| - url: "https://sqlite.org/2024/sqlite-autoconf-3450300.tar.gz" | ||
| sha256: "b2809ca53124c19c60f42bf627736eae011afdcc205bb48270a5ee9a38191531" |
+130
-133
@@ -34,6 +34,6 @@ #include <aggregator.h> | ||
| { | ||
| protected_funcall_args_t *args = | ||
| (protected_funcall_args_t*)protected_funcall_args_ptr; | ||
| protected_funcall_args_t *args = | ||
| (protected_funcall_args_t *)protected_funcall_args_ptr; | ||
| return rb_funcall2(args->self, args->method, args->argc, args->params); | ||
| return rb_funcall2(args->self, args->method, args->argc, args->params); | ||
| } | ||
@@ -43,8 +43,8 @@ | ||
| rb_sqlite3_protected_funcall(VALUE self, ID method, int argc, VALUE *params, | ||
| int* exc_status) | ||
| int *exc_status) | ||
| { | ||
| protected_funcall_args_t args = { | ||
| .self = self, .method = method, .argc = argc, .params = params | ||
| }; | ||
| return rb_protect(rb_sqlite3_protected_funcall_body, (VALUE)(&args), exc_status); | ||
| protected_funcall_args_t args = { | ||
| .self = self, .method = method, .argc = argc, .params = params | ||
| }; | ||
| return rb_protect(rb_sqlite3_protected_funcall_body, (VALUE)(&args), exc_status); | ||
| } | ||
@@ -59,32 +59,32 @@ | ||
| { | ||
| VALUE aw = (VALUE) sqlite3_user_data(ctx); | ||
| VALUE handler_klass = rb_iv_get(aw, "-handler_klass"); | ||
| VALUE inst; | ||
| VALUE *inst_ptr = sqlite3_aggregate_context(ctx, (int)sizeof(VALUE)); | ||
| VALUE aw = (VALUE) sqlite3_user_data(ctx); | ||
| VALUE handler_klass = rb_iv_get(aw, "-handler_klass"); | ||
| VALUE inst; | ||
| VALUE *inst_ptr = sqlite3_aggregate_context(ctx, (int)sizeof(VALUE)); | ||
| if (!inst_ptr) { | ||
| rb_fatal("SQLite is out-of-merory"); | ||
| } | ||
| if (!inst_ptr) { | ||
| rb_fatal("SQLite is out-of-merory"); | ||
| } | ||
| inst = *inst_ptr; | ||
| inst = *inst_ptr; | ||
| if (inst == Qfalse) { /* Qfalse == 0 */ | ||
| VALUE instances = rb_iv_get(aw, "-instances"); | ||
| int exc_status; | ||
| if (inst == Qfalse) { /* Qfalse == 0 */ | ||
| VALUE instances = rb_iv_get(aw, "-instances"); | ||
| int exc_status; | ||
| inst = rb_class_new_instance(0, NULL, cAggregatorInstance); | ||
| rb_iv_set(inst, "-handler_instance", rb_sqlite3_protected_funcall( | ||
| handler_klass, rb_intern("new"), 0, NULL, &exc_status)); | ||
| rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); | ||
| inst = rb_class_new_instance(0, NULL, cAggregatorInstance); | ||
| rb_iv_set(inst, "-handler_instance", rb_sqlite3_protected_funcall( | ||
| handler_klass, rb_intern("new"), 0, NULL, &exc_status)); | ||
| rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); | ||
| rb_ary_push(instances, inst); | ||
| rb_ary_push(instances, inst); | ||
| *inst_ptr = inst; | ||
| } | ||
| *inst_ptr = inst; | ||
| } | ||
| if (inst == Qnil) { | ||
| rb_fatal("SQLite called us back on an already destroyed aggregate instance"); | ||
| } | ||
| if (inst == Qnil) { | ||
| rb_fatal("SQLite called us back on an already destroyed aggregate instance"); | ||
| } | ||
| return inst; | ||
| return inst; | ||
| } | ||
@@ -98,54 +98,54 @@ | ||
| { | ||
| VALUE aw = (VALUE) sqlite3_user_data(ctx); | ||
| VALUE instances = rb_iv_get(aw, "-instances"); | ||
| VALUE *inst_ptr = sqlite3_aggregate_context(ctx, 0); | ||
| VALUE inst; | ||
| VALUE aw = (VALUE) sqlite3_user_data(ctx); | ||
| VALUE instances = rb_iv_get(aw, "-instances"); | ||
| VALUE *inst_ptr = sqlite3_aggregate_context(ctx, 0); | ||
| VALUE inst; | ||
| if (!inst_ptr || (inst = *inst_ptr)) { | ||
| return; | ||
| } | ||
| if (!inst_ptr || (inst = *inst_ptr)) { | ||
| return; | ||
| } | ||
| if (inst == Qnil) { | ||
| rb_fatal("attempt to destroy aggregate instance twice"); | ||
| } | ||
| if (inst == Qnil) { | ||
| rb_fatal("attempt to destroy aggregate instance twice"); | ||
| } | ||
| rb_iv_set(inst, "-handler_instance", Qnil); // may catch use-after-free | ||
| if (rb_ary_delete(instances, inst) == Qnil) { | ||
| rb_fatal("must be in instances at that point"); | ||
| } | ||
| rb_iv_set(inst, "-handler_instance", Qnil); // may catch use-after-free | ||
| if (rb_ary_delete(instances, inst) == Qnil) { | ||
| rb_fatal("must be in instances at that point"); | ||
| } | ||
| *inst_ptr = Qnil; | ||
| *inst_ptr = Qnil; | ||
| } | ||
| static void | ||
| rb_sqlite3_aggregator_step(sqlite3_context * ctx, int argc, sqlite3_value **argv) | ||
| rb_sqlite3_aggregator_step(sqlite3_context *ctx, int argc, sqlite3_value **argv) | ||
| { | ||
| VALUE inst = rb_sqlite3_aggregate_instance(ctx); | ||
| VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); | ||
| VALUE * params = NULL; | ||
| VALUE one_param; | ||
| int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); | ||
| int i; | ||
| VALUE inst = rb_sqlite3_aggregate_instance(ctx); | ||
| VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); | ||
| VALUE *params = NULL; | ||
| VALUE one_param; | ||
| int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); | ||
| int i; | ||
| if (exc_status) { | ||
| return; | ||
| } | ||
| if (exc_status) { | ||
| return; | ||
| } | ||
| if (argc == 1) { | ||
| one_param = sqlite3val2rb(argv[0]); | ||
| params = &one_param; | ||
| } | ||
| if (argc > 1) { | ||
| params = xcalloc((size_t)argc, sizeof(VALUE)); | ||
| for(i = 0; i < argc; i++) { | ||
| params[i] = sqlite3val2rb(argv[i]); | ||
| if (argc == 1) { | ||
| one_param = sqlite3val2rb(argv[0]); | ||
| params = &one_param; | ||
| } | ||
| } | ||
| rb_sqlite3_protected_funcall( | ||
| handler_instance, rb_intern("step"), argc, params, &exc_status); | ||
| if (argc > 1) { | ||
| xfree(params); | ||
| } | ||
| if (argc > 1) { | ||
| params = xcalloc((size_t)argc, sizeof(VALUE)); | ||
| for (i = 0; i < argc; i++) { | ||
| params[i] = sqlite3val2rb(argv[i]); | ||
| } | ||
| } | ||
| rb_sqlite3_protected_funcall( | ||
| handler_instance, rb_intern("step"), argc, params, &exc_status); | ||
| if (argc > 1) { | ||
| xfree(params); | ||
| } | ||
| rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); | ||
| rb_iv_set(inst, "-exc_status", INT2NUM(exc_status)); | ||
| } | ||
@@ -155,24 +155,24 @@ | ||
| static void | ||
| rb_sqlite3_aggregator_final(sqlite3_context * ctx) | ||
| rb_sqlite3_aggregator_final(sqlite3_context *ctx) | ||
| { | ||
| VALUE inst = rb_sqlite3_aggregate_instance(ctx); | ||
| VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); | ||
| int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); | ||
| VALUE inst = rb_sqlite3_aggregate_instance(ctx); | ||
| VALUE handler_instance = rb_iv_get(inst, "-handler_instance"); | ||
| int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status")); | ||
| if (!exc_status) { | ||
| VALUE result = rb_sqlite3_protected_funcall( | ||
| handler_instance, rb_intern("finalize"), 0, NULL, &exc_status); | ||
| if (!exc_status) { | ||
| set_sqlite3_func_result(ctx, result); | ||
| VALUE result = rb_sqlite3_protected_funcall( | ||
| handler_instance, rb_intern("finalize"), 0, NULL, &exc_status); | ||
| if (!exc_status) { | ||
| set_sqlite3_func_result(ctx, result); | ||
| } | ||
| } | ||
| } | ||
| if (exc_status) { | ||
| /* the user should never see this, as Statement.step() will pick up the | ||
| * outstanding exception and raise it instead of generating a new one | ||
| * for SQLITE_ERROR with message "Ruby Exception occurred" */ | ||
| sqlite3_result_error(ctx, "Ruby Exception occurred", -1); | ||
| } | ||
| if (exc_status) { | ||
| /* the user should never see this, as Statement.step() will pick up the | ||
| * outstanding exception and raise it instead of generating a new one | ||
| * for SQLITE_ERROR with message "Ruby Exception occurred" */ | ||
| sqlite3_result_error(ctx, "Ruby Exception occurred", -1); | ||
| } | ||
| rb_sqlite3_aggregate_instance_destroy(ctx); | ||
| rb_sqlite3_aggregate_instance_destroy(ctx); | ||
| } | ||
@@ -213,56 +213,53 @@ | ||
| { | ||
| /* define_aggregator is added as a method to SQLite3::Database in database.c */ | ||
| sqlite3RubyPtr ctx = sqlite3_database_unwrap(self); | ||
| int arity, status; | ||
| VALUE aw; | ||
| VALUE aggregators; | ||
| /* define_aggregator is added as a method to SQLite3::Database in database.c */ | ||
| sqlite3RubyPtr ctx = sqlite3_database_unwrap(self); | ||
| int arity, status; | ||
| VALUE aw; | ||
| VALUE aggregators; | ||
| if (!ctx->db) { | ||
| rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); | ||
| } | ||
| if (!ctx->db) { | ||
| rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database"); | ||
| } | ||
| if (rb_respond_to(aggregator, rb_intern("arity"))) { | ||
| VALUE ruby_arity = rb_funcall(aggregator, rb_intern("arity"), 0); | ||
| arity = NUM2INT(ruby_arity); | ||
| } else { | ||
| arity = -1; | ||
| } | ||
| if (rb_respond_to(aggregator, rb_intern("arity"))) { | ||
| VALUE ruby_arity = rb_funcall(aggregator, rb_intern("arity"), 0); | ||
| arity = NUM2INT(ruby_arity); | ||
| } else { | ||
| arity = -1; | ||
| } | ||
| if (arity < -1 || arity > 127) { | ||
| if (arity < -1 || arity > 127) { | ||
| #ifdef PRIsVALUE | ||
| rb_raise(rb_eArgError, "%"PRIsVALUE" arity=%d out of range -1..127", | ||
| self, arity); | ||
| rb_raise(rb_eArgError, "%"PRIsVALUE" arity=%d out of range -1..127", | ||
| self, arity); | ||
| #else | ||
| rb_raise(rb_eArgError, "Aggregator arity=%d out of range -1..127", arity); | ||
| rb_raise(rb_eArgError, "Aggregator arity=%d out of range -1..127", arity); | ||
| #endif | ||
| } | ||
| } | ||
| if (!rb_ivar_defined(self, rb_intern("-aggregators"))) { | ||
| rb_iv_set(self, "-aggregators", rb_ary_new()); | ||
| } | ||
| aggregators = rb_iv_get(self, "-aggregators"); | ||
| if (!rb_ivar_defined(self, rb_intern("-aggregators"))) { | ||
| rb_iv_set(self, "-aggregators", rb_ary_new()); | ||
| } | ||
| aggregators = rb_iv_get(self, "-aggregators"); | ||
| aw = rb_class_new_instance(0, NULL, cAggregatorWrapper); | ||
| rb_iv_set(aw, "-handler_klass", aggregator); | ||
| rb_iv_set(aw, "-instances", rb_ary_new()); | ||
| aw = rb_class_new_instance(0, NULL, cAggregatorWrapper); | ||
| rb_iv_set(aw, "-handler_klass", aggregator); | ||
| rb_iv_set(aw, "-instances", rb_ary_new()); | ||
| status = sqlite3_create_function( | ||
| ctx->db, | ||
| StringValueCStr(ruby_name), | ||
| arity, | ||
| SQLITE_UTF8, | ||
| (void*)aw, | ||
| NULL, | ||
| rb_sqlite3_aggregator_step, | ||
| rb_sqlite3_aggregator_final | ||
| ); | ||
| status = sqlite3_create_function( | ||
| ctx->db, | ||
| StringValueCStr(ruby_name), | ||
| arity, | ||
| SQLITE_UTF8, | ||
| (void *)aw, | ||
| NULL, | ||
| rb_sqlite3_aggregator_step, | ||
| rb_sqlite3_aggregator_final | ||
| ); | ||
| if (status != SQLITE_OK) { | ||
| rb_sqlite3_raise(ctx->db, status); | ||
| return self; // just in case rb_sqlite3_raise returns. | ||
| } | ||
| CHECK(ctx->db, status); | ||
| rb_ary_push(aggregators, aw); | ||
| rb_ary_push(aggregators, aw); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -273,8 +270,8 @@ | ||
| { | ||
| /* rb_class_new generatos class with undefined allocator in ruby 1.9 */ | ||
| cAggregatorWrapper = rb_funcall(rb_cClass, rb_intern("new"), 0); | ||
| rb_gc_register_mark_object(cAggregatorWrapper); | ||
| /* rb_class_new generatos class with undefined allocator in ruby 1.9 */ | ||
| cAggregatorWrapper = rb_funcall(rb_cClass, rb_intern("new"), 0); | ||
| rb_gc_register_mark_object(cAggregatorWrapper); | ||
| cAggregatorInstance = rb_funcall(rb_cClass, rb_intern("new"), 0); | ||
| rb_gc_register_mark_object(cAggregatorInstance); | ||
| cAggregatorInstance = rb_funcall(rb_cClass, rb_intern("new"), 0); | ||
| rb_gc_register_mark_object(cAggregatorInstance); | ||
| } |
@@ -6,8 +6,6 @@ #ifndef SQLITE3_AGGREGATOR_RUBY | ||
| VALUE | ||
| rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name); | ||
| VALUE rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name); | ||
| void | ||
| rb_sqlite3_aggregator_init(void); | ||
| void rb_sqlite3_aggregator_init(void); | ||
| #endif |
+69
-60
@@ -11,7 +11,8 @@ #ifdef HAVE_SQLITE3_BACKUP_INIT | ||
| static size_t backup_memsize(const void *data) | ||
| static size_t | ||
| backup_memsize(const void *data) | ||
| { | ||
| sqlite3BackupRubyPtr ctx = (sqlite3BackupRubyPtr)data; | ||
| // NB: can't account for ctx->p because the type is incomplete. | ||
| return sizeof(*ctx); | ||
| sqlite3BackupRubyPtr ctx = (sqlite3BackupRubyPtr)data; | ||
| // NB: can't account for ctx->p because the type is incomplete. | ||
| return sizeof(*ctx); | ||
| } | ||
@@ -31,6 +32,7 @@ | ||
| static VALUE allocate(VALUE klass) | ||
| static VALUE | ||
| allocate(VALUE klass) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3BackupRuby, &backup_type, ctx); | ||
| sqlite3BackupRubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3BackupRuby, &backup_type, ctx); | ||
| } | ||
@@ -74,27 +76,29 @@ | ||
| */ | ||
| static VALUE initialize(VALUE self, VALUE dstdb, VALUE dstname, VALUE srcdb, VALUE srcname) | ||
| static VALUE | ||
| initialize(VALUE self, VALUE dstdb, VALUE dstname, VALUE srcdb, VALUE srcname) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| sqlite3RubyPtr ddb_ctx, sdb_ctx; | ||
| sqlite3_backup *pBackup; | ||
| sqlite3BackupRubyPtr ctx; | ||
| sqlite3RubyPtr ddb_ctx, sdb_ctx; | ||
| sqlite3_backup *pBackup; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| ddb_ctx = sqlite3_database_unwrap(dstdb); | ||
| sdb_ctx = sqlite3_database_unwrap(srcdb); | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| ddb_ctx = sqlite3_database_unwrap(dstdb); | ||
| sdb_ctx = sqlite3_database_unwrap(srcdb); | ||
| if(!sdb_ctx->db) | ||
| rb_raise(rb_eArgError, "cannot backup from a closed database"); | ||
| if(!ddb_ctx->db) | ||
| rb_raise(rb_eArgError, "cannot backup to a closed database"); | ||
| if (!sdb_ctx->db) { | ||
| rb_raise(rb_eArgError, "cannot backup from a closed database"); | ||
| } | ||
| if (!ddb_ctx->db) { | ||
| rb_raise(rb_eArgError, "cannot backup to a closed database"); | ||
| } | ||
| pBackup = sqlite3_backup_init(ddb_ctx->db, StringValuePtr(dstname), | ||
| sdb_ctx->db, StringValuePtr(srcname)); | ||
| if( pBackup ){ | ||
| ctx->p = pBackup; | ||
| } | ||
| else { | ||
| CHECK(ddb_ctx->db, sqlite3_errcode(ddb_ctx->db)); | ||
| } | ||
| pBackup = sqlite3_backup_init(ddb_ctx->db, StringValuePtr(dstname), | ||
| sdb_ctx->db, StringValuePtr(srcname)); | ||
| if (pBackup) { | ||
| ctx->p = pBackup; | ||
| } else { | ||
| CHECK(ddb_ctx->db, sqlite3_errcode(ddb_ctx->db)); | ||
| } | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -111,11 +115,12 @@ | ||
| */ | ||
| static VALUE step(VALUE self, VALUE nPage) | ||
| static VALUE | ||
| step(VALUE self, VALUE nPage) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| int status; | ||
| sqlite3BackupRubyPtr ctx; | ||
| int status; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| status = sqlite3_backup_step(ctx->p, NUM2INT(nPage)); | ||
| return INT2NUM(status); | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| status = sqlite3_backup_step(ctx->p, NUM2INT(nPage)); | ||
| return INT2NUM(status); | ||
| } | ||
@@ -127,11 +132,12 @@ | ||
| */ | ||
| static VALUE finish(VALUE self) | ||
| static VALUE | ||
| finish(VALUE self) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| sqlite3BackupRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| (void)sqlite3_backup_finish(ctx->p); | ||
| ctx->p = NULL; | ||
| return Qnil; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| (void)sqlite3_backup_finish(ctx->p); | ||
| ctx->p = NULL; | ||
| return Qnil; | ||
| } | ||
@@ -146,9 +152,10 @@ | ||
| */ | ||
| static VALUE remaining(VALUE self) | ||
| static VALUE | ||
| remaining(VALUE self) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| sqlite3BackupRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| return INT2NUM(sqlite3_backup_remaining(ctx->p)); | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| return INT2NUM(sqlite3_backup_remaining(ctx->p)); | ||
| } | ||
@@ -163,26 +170,28 @@ | ||
| */ | ||
| static VALUE pagecount(VALUE self) | ||
| static VALUE | ||
| pagecount(VALUE self) | ||
| { | ||
| sqlite3BackupRubyPtr ctx; | ||
| sqlite3BackupRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| return INT2NUM(sqlite3_backup_pagecount(ctx->p)); | ||
| TypedData_Get_Struct(self, sqlite3BackupRuby, &backup_type, ctx); | ||
| REQUIRE_OPEN_BACKUP(ctx); | ||
| return INT2NUM(sqlite3_backup_pagecount(ctx->p)); | ||
| } | ||
| void init_sqlite3_backup(void) | ||
| void | ||
| init_sqlite3_backup(void) | ||
| { | ||
| #if 0 | ||
| VALUE mSqlite3 = rb_define_module("SQLite3"); | ||
| VALUE mSqlite3 = rb_define_module("SQLite3"); | ||
| #endif | ||
| cSqlite3Backup = rb_define_class_under(mSqlite3, "Backup", rb_cObject); | ||
| cSqlite3Backup = rb_define_class_under(mSqlite3, "Backup", rb_cObject); | ||
| rb_define_alloc_func(cSqlite3Backup, allocate); | ||
| rb_define_method(cSqlite3Backup, "initialize", initialize, 4); | ||
| rb_define_method(cSqlite3Backup, "step", step, 1); | ||
| rb_define_method(cSqlite3Backup, "finish", finish, 0); | ||
| rb_define_method(cSqlite3Backup, "remaining", remaining, 0); | ||
| rb_define_method(cSqlite3Backup, "pagecount", pagecount, 0); | ||
| rb_define_alloc_func(cSqlite3Backup, allocate); | ||
| rb_define_method(cSqlite3Backup, "initialize", initialize, 4); | ||
| rb_define_method(cSqlite3Backup, "step", step, 1); | ||
| rb_define_method(cSqlite3Backup, "finish", finish, 0); | ||
| rb_define_method(cSqlite3Backup, "remaining", remaining, 0); | ||
| rb_define_method(cSqlite3Backup, "pagecount", pagecount, 0); | ||
| } | ||
| #endif |
@@ -7,7 +7,7 @@ #if !defined(SQLITE3_BACKUP_RUBY) && defined(HAVE_SQLITE3_BACKUP_INIT) | ||
| struct _sqlite3BackupRuby { | ||
| sqlite3_backup *p; | ||
| sqlite3_backup *p; | ||
| }; | ||
| typedef struct _sqlite3BackupRuby sqlite3BackupRuby; | ||
| typedef sqlite3BackupRuby * sqlite3BackupRubyPtr; | ||
| typedef sqlite3BackupRuby *sqlite3BackupRubyPtr; | ||
@@ -14,0 +14,0 @@ void init_sqlite3_backup(); |
+520
-467
@@ -15,34 +15,42 @@ #include <sqlite3_ruby.h> | ||
| static void deallocate(void * ctx) | ||
| static void | ||
| database_mark(void *ctx) | ||
| { | ||
| sqlite3RubyPtr c = (sqlite3RubyPtr)ctx; | ||
| sqlite3 * db = c->db; | ||
| sqlite3RubyPtr c = (sqlite3RubyPtr)ctx; | ||
| rb_gc_mark(c->busy_handler); | ||
| } | ||
| if(db) sqlite3_close(db); | ||
| xfree(c); | ||
| static void | ||
| deallocate(void *ctx) | ||
| { | ||
| sqlite3RubyPtr c = (sqlite3RubyPtr)ctx; | ||
| sqlite3 *db = c->db; | ||
| if (db) { sqlite3_close(db); } | ||
| xfree(c); | ||
| } | ||
| static size_t database_memsize(const void *ctx) | ||
| static size_t | ||
| database_memsize(const void *ctx) | ||
| { | ||
| const sqlite3RubyPtr c = (const sqlite3RubyPtr)ctx; | ||
| // NB: can't account for ctx->db because the type is incomplete. | ||
| return sizeof(*c); | ||
| const sqlite3RubyPtr c = (const sqlite3RubyPtr)ctx; | ||
| // NB: can't account for ctx->db because the type is incomplete. | ||
| return sizeof(*c); | ||
| } | ||
| static const rb_data_type_t database_type = { | ||
| "SQLite3::Backup", | ||
| { | ||
| NULL, | ||
| deallocate, | ||
| database_memsize, | ||
| }, | ||
| 0, | ||
| 0, | ||
| RUBY_TYPED_WB_PROTECTED, // Not freed immediately because the dfree function do IOs. | ||
| .wrap_struct_name = "SQLite3::Backup", | ||
| .function = { | ||
| .dmark = database_mark, | ||
| .dfree = deallocate, | ||
| .dsize = database_memsize, | ||
| }, | ||
| .flags = RUBY_TYPED_WB_PROTECTED, // Not freed immediately because the dfree function do IOs. | ||
| }; | ||
| static VALUE allocate(VALUE klass) | ||
| static VALUE | ||
| allocate(VALUE klass) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx); | ||
| sqlite3RubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx); | ||
| } | ||
@@ -53,5 +61,5 @@ | ||
| { | ||
| StringValue(str); | ||
| rb_str_buf_cat(str, "\x00\x00", 2L); | ||
| return RSTRING_PTR(str); | ||
| StringValue(str); | ||
| rb_str_buf_cat(str, "\x00\x00", 2L); | ||
| return RSTRING_PTR(str); | ||
| } | ||
@@ -61,50 +69,54 @@ | ||
| sqlite3RubyPtr sqlite3_database_unwrap(VALUE database){ | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(database, sqlite3Ruby, &database_type, ctx); | ||
| return ctx; | ||
| sqlite3RubyPtr | ||
| sqlite3_database_unwrap(VALUE database) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(database, sqlite3Ruby, &database_type, ctx); | ||
| return ctx; | ||
| } | ||
| static VALUE rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs) | ||
| static VALUE | ||
| rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| #if defined TAINTING_SUPPORT | ||
| # if defined StringValueCStr | ||
| StringValuePtr(file); | ||
| rb_check_safe_obj(file); | ||
| StringValuePtr(file); | ||
| rb_check_safe_obj(file); | ||
| # else | ||
| Check_SafeStr(file); | ||
| Check_SafeStr(file); | ||
| # endif | ||
| #endif | ||
| status = sqlite3_open_v2( | ||
| StringValuePtr(file), | ||
| &ctx->db, | ||
| NUM2INT(mode), | ||
| NIL_P(zvfs) ? NULL : StringValuePtr(zvfs) | ||
| ); | ||
| status = sqlite3_open_v2( | ||
| StringValuePtr(file), | ||
| &ctx->db, | ||
| NUM2INT(mode), | ||
| NIL_P(zvfs) ? NULL : StringValuePtr(zvfs) | ||
| ); | ||
| CHECK(ctx->db, status) | ||
| CHECK(ctx->db, status) | ||
| return self; | ||
| return self; | ||
| } | ||
| static VALUE rb_sqlite3_disable_quirk_mode(VALUE self) | ||
| static VALUE | ||
| rb_sqlite3_disable_quirk_mode(VALUE self) | ||
| { | ||
| #if defined SQLITE_DBCONFIG_DQS_DDL | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| if(!ctx->db) return Qfalse; | ||
| if (!ctx->db) { return Qfalse; } | ||
| sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DDL, 0, (void*)0); | ||
| sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DML, 0, (void*)0); | ||
| sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DDL, 0, (void *)0); | ||
| sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DML, 0, (void *)0); | ||
| return Qtrue; | ||
| return Qtrue; | ||
| #else | ||
| return Qfalse; | ||
| return Qfalse; | ||
| #endif | ||
@@ -117,16 +129,17 @@ } | ||
| */ | ||
| static VALUE sqlite3_rb_close(VALUE self) | ||
| static VALUE | ||
| sqlite3_rb_close(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| sqlite3 * db; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| sqlite3RubyPtr ctx; | ||
| sqlite3 *db; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| db = ctx->db; | ||
| CHECK(db, sqlite3_close(ctx->db)); | ||
| db = ctx->db; | ||
| CHECK(db, sqlite3_close(ctx->db)); | ||
| ctx->db = NULL; | ||
| ctx->db = NULL; | ||
| rb_iv_set(self, "-aggregators", Qnil); | ||
| rb_iv_set(self, "-aggregators", Qnil); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -138,10 +151,11 @@ | ||
| */ | ||
| static VALUE closed_p(VALUE self) | ||
| static VALUE | ||
| closed_p(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| if(!ctx->db) return Qtrue; | ||
| if (!ctx->db) { return Qtrue; } | ||
| return Qfalse; | ||
| return Qfalse; | ||
| } | ||
@@ -154,16 +168,18 @@ | ||
| */ | ||
| static VALUE total_changes(VALUE self) | ||
| static VALUE | ||
| total_changes(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return INT2NUM(sqlite3_total_changes(ctx->db)); | ||
| return INT2NUM(sqlite3_total_changes(ctx->db)); | ||
| } | ||
| static void tracefunc(void * data, const char *sql) | ||
| static void | ||
| tracefunc(void *data, const char *sql) | ||
| { | ||
| VALUE self = (VALUE)data; | ||
| VALUE thing = rb_iv_get(self, "@tracefunc"); | ||
| rb_funcall(thing, rb_intern("call"), 1, rb_str_new2(sql)); | ||
| VALUE self = (VALUE)data; | ||
| VALUE thing = rb_iv_get(self, "@tracefunc"); | ||
| rb_funcall(thing, rb_intern("call"), 1, rb_str_new2(sql)); | ||
| } | ||
@@ -179,30 +195,33 @@ | ||
| */ | ||
| static VALUE trace(int argc, VALUE *argv, VALUE self) | ||
| static VALUE | ||
| trace(int argc, VALUE *argv, VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| rb_scan_args(argc, argv, "01", &block); | ||
| rb_scan_args(argc, argv, "01", &block); | ||
| if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); | ||
| if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); } | ||
| rb_iv_set(self, "@tracefunc", block); | ||
| rb_iv_set(self, "@tracefunc", block); | ||
| sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc, (void *)self); | ||
| sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc, (void *)self); | ||
| return self; | ||
| return self; | ||
| } | ||
| static int rb_sqlite3_busy_handler(void * ctx, int count) | ||
| static int | ||
| rb_sqlite3_busy_handler(void *context, int count) | ||
| { | ||
| VALUE self = (VALUE)(ctx); | ||
| VALUE handle = rb_iv_get(self, "@busy_handler"); | ||
| VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM(count)); | ||
| sqlite3RubyPtr ctx = (sqlite3RubyPtr)context; | ||
| if(Qfalse == result) return 0; | ||
| VALUE handle = ctx->busy_handler; | ||
| VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM(count)); | ||
| return 1; | ||
| if (Qfalse == result) { return 0; } | ||
| return 1; | ||
| } | ||
@@ -224,25 +243,66 @@ | ||
| */ | ||
| static VALUE busy_handler(int argc, VALUE *argv, VALUE self) | ||
| static VALUE | ||
| busy_handler(int argc, VALUE *argv, VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| int status; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| rb_scan_args(argc, argv, "01", &block); | ||
| rb_scan_args(argc, argv, "01", &block); | ||
| if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc(); | ||
| if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); } | ||
| ctx->busy_handler = block; | ||
| rb_iv_set(self, "@busy_handler", block); | ||
| status = sqlite3_busy_handler( | ||
| ctx->db, | ||
| NIL_P(block) ? NULL : rb_sqlite3_busy_handler, | ||
| (void *)ctx | ||
| ); | ||
| status = sqlite3_busy_handler( | ||
| ctx->db, NIL_P(block) ? NULL : rb_sqlite3_busy_handler, (void *)self); | ||
| CHECK(ctx->db, status); | ||
| CHECK(ctx->db, status); | ||
| return self; | ||
| } | ||
| return self; | ||
| static int | ||
| rb_sqlite3_statement_timeout(void *context) | ||
| { | ||
| sqlite3RubyPtr ctx = (sqlite3RubyPtr)context; | ||
| struct timespec currentTime; | ||
| clock_gettime(CLOCK_MONOTONIC, ¤tTime); | ||
| if (!timespecisset(&ctx->stmt_deadline)) { | ||
| // Set stmt_deadline if not already set | ||
| ctx->stmt_deadline = currentTime; | ||
| } else if (timespecafter(¤tTime, &ctx->stmt_deadline)) { | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } | ||
| /* call-seq: db.statement_timeout = ms | ||
| * | ||
| * Indicates that if a query lasts longer than the indicated number of | ||
| * milliseconds, SQLite should interrupt that query and return an error. | ||
| * By default, SQLite does not interrupt queries. To restore the default | ||
| * behavior, send 0 as the +ms+ parameter. | ||
| */ | ||
| static VALUE | ||
| set_statement_timeout(VALUE self, VALUE milliseconds) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| ctx->stmt_timeout = NUM2INT(milliseconds); | ||
| int n = NUM2INT(milliseconds) == 0 ? -1 : 1000; | ||
| sqlite3_progress_handler(ctx->db, n, rb_sqlite3_statement_timeout, (void *)ctx); | ||
| return self; | ||
| } | ||
| /* call-seq: last_insert_row_id | ||
@@ -253,111 +313,118 @@ * | ||
| */ | ||
| static VALUE last_insert_row_id(VALUE self) | ||
| static VALUE | ||
| last_insert_row_id(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return LL2NUM(sqlite3_last_insert_rowid(ctx->db)); | ||
| return LL2NUM(sqlite3_last_insert_rowid(ctx->db)); | ||
| } | ||
| VALUE sqlite3val2rb(sqlite3_value * val) | ||
| VALUE | ||
| sqlite3val2rb(sqlite3_value *val) | ||
| { | ||
| switch(sqlite3_value_type(val)) { | ||
| case SQLITE_INTEGER: | ||
| return LL2NUM(sqlite3_value_int64(val)); | ||
| break; | ||
| case SQLITE_FLOAT: | ||
| return rb_float_new(sqlite3_value_double(val)); | ||
| break; | ||
| case SQLITE_TEXT: | ||
| return rb_str_new2((const char *)sqlite3_value_text(val)); | ||
| break; | ||
| case SQLITE_BLOB: { | ||
| /* Sqlite warns calling sqlite3_value_bytes may invalidate pointer from sqlite3_value_blob, | ||
| so we explicitly get the length before getting blob pointer. | ||
| Note that rb_str_new apparently create string with ASCII-8BIT (BINARY) encoding, | ||
| which is what we want, as blobs are binary | ||
| */ | ||
| int len = sqlite3_value_bytes(val); | ||
| return rb_str_new((const char *)sqlite3_value_blob(val), len); | ||
| break; | ||
| VALUE rb_val; | ||
| switch (sqlite3_value_type(val)) { | ||
| case SQLITE_INTEGER: | ||
| rb_val = LL2NUM(sqlite3_value_int64(val)); | ||
| break; | ||
| case SQLITE_FLOAT: | ||
| rb_val = rb_float_new(sqlite3_value_double(val)); | ||
| break; | ||
| case SQLITE_TEXT: { | ||
| rb_val = rb_utf8_str_new_cstr((const char *)sqlite3_value_text(val)); | ||
| rb_obj_freeze(rb_val); | ||
| break; | ||
| } | ||
| case SQLITE_BLOB: { | ||
| int len = sqlite3_value_bytes(val); | ||
| rb_val = rb_str_new((const char *)sqlite3_value_blob(val), len); | ||
| rb_obj_freeze(rb_val); | ||
| break; | ||
| } | ||
| case SQLITE_NULL: | ||
| rb_val = Qnil; | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "bad type"); | ||
| } | ||
| case SQLITE_NULL: | ||
| return Qnil; | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "bad type"); /* FIXME */ | ||
| } | ||
| return rb_val; | ||
| } | ||
| void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result) | ||
| void | ||
| set_sqlite3_func_result(sqlite3_context *ctx, VALUE result) | ||
| { | ||
| switch(TYPE(result)) { | ||
| case T_NIL: | ||
| sqlite3_result_null(ctx); | ||
| break; | ||
| case T_FIXNUM: | ||
| sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result)); | ||
| break; | ||
| case T_BIGNUM: { | ||
| switch (TYPE(result)) { | ||
| case T_NIL: | ||
| sqlite3_result_null(ctx); | ||
| break; | ||
| case T_FIXNUM: | ||
| sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result)); | ||
| break; | ||
| case T_BIGNUM: { | ||
| #if SIZEOF_LONG < 8 | ||
| sqlite3_int64 num64; | ||
| sqlite3_int64 num64; | ||
| if (bignum_to_int64(result, &num64)) { | ||
| sqlite3_result_int64(ctx, num64); | ||
| break; | ||
| } | ||
| if (bignum_to_int64(result, &num64)) { | ||
| sqlite3_result_int64(ctx, num64); | ||
| break; | ||
| } | ||
| #endif | ||
| } | ||
| case T_FLOAT: | ||
| sqlite3_result_double(ctx, NUM2DBL(result)); | ||
| break; | ||
| case T_STRING: | ||
| if (CLASS_OF(result) == cSqlite3Blob | ||
| || rb_enc_get_index(result) == rb_ascii8bit_encindex() | ||
| ) { | ||
| sqlite3_result_blob( | ||
| ctx, | ||
| (const void *)StringValuePtr(result), | ||
| (int)RSTRING_LEN(result), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| sqlite3_result_text( | ||
| ctx, | ||
| (const char *)StringValuePtr(result), | ||
| (int)RSTRING_LEN(result), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "can't return %s", | ||
| rb_class2name(CLASS_OF(result))); | ||
| } | ||
| case T_FLOAT: | ||
| sqlite3_result_double(ctx, NUM2DBL(result)); | ||
| break; | ||
| case T_STRING: | ||
| if(CLASS_OF(result) == cSqlite3Blob | ||
| || rb_enc_get_index(result) == rb_ascii8bit_encindex() | ||
| ) { | ||
| sqlite3_result_blob( | ||
| ctx, | ||
| (const void *)StringValuePtr(result), | ||
| (int)RSTRING_LEN(result), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| sqlite3_result_text( | ||
| ctx, | ||
| (const char *)StringValuePtr(result), | ||
| (int)RSTRING_LEN(result), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "can't return %s", | ||
| rb_class2name(CLASS_OF(result))); | ||
| } | ||
| } | ||
| static void rb_sqlite3_func(sqlite3_context * ctx, int argc, sqlite3_value **argv) | ||
| static void | ||
| rb_sqlite3_func(sqlite3_context *ctx, int argc, sqlite3_value **argv) | ||
| { | ||
| VALUE callable = (VALUE)sqlite3_user_data(ctx); | ||
| VALUE params = rb_ary_new2(argc); | ||
| VALUE result; | ||
| int i; | ||
| VALUE callable = (VALUE)sqlite3_user_data(ctx); | ||
| VALUE params = rb_ary_new2(argc); | ||
| VALUE result; | ||
| int i; | ||
| if (argc > 0) { | ||
| for(i = 0; i < argc; i++) { | ||
| VALUE param = sqlite3val2rb(argv[i]); | ||
| rb_ary_push(params, param); | ||
| if (argc > 0) { | ||
| for (i = 0; i < argc; i++) { | ||
| VALUE param = sqlite3val2rb(argv[i]); | ||
| rb_ary_push(params, param); | ||
| } | ||
| } | ||
| } | ||
| result = rb_apply(callable, rb_intern("call"), params); | ||
| result = rb_apply(callable, rb_intern("call"), params); | ||
| set_sqlite3_func_result(ctx, result); | ||
| set_sqlite3_func_result(ctx, result); | ||
| } | ||
| #ifndef HAVE_RB_PROC_ARITY | ||
| int rb_proc_arity(VALUE self) | ||
| int | ||
| rb_proc_arity(VALUE self) | ||
| { | ||
| return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0)); | ||
| return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0)); | ||
| } | ||
@@ -371,29 +438,30 @@ #endif | ||
| */ | ||
| static VALUE define_function_with_flags(VALUE self, VALUE name, VALUE flags) | ||
| static VALUE | ||
| define_function_with_flags(VALUE self, VALUE name, VALUE flags) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| VALUE block; | ||
| int status; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| block = rb_block_proc(); | ||
| block = rb_block_proc(); | ||
| status = sqlite3_create_function( | ||
| ctx->db, | ||
| StringValuePtr(name), | ||
| rb_proc_arity(block), | ||
| NUM2INT(flags), | ||
| (void *)block, | ||
| rb_sqlite3_func, | ||
| NULL, | ||
| NULL | ||
| ); | ||
| status = sqlite3_create_function( | ||
| ctx->db, | ||
| StringValuePtr(name), | ||
| rb_proc_arity(block), | ||
| NUM2INT(flags), | ||
| (void *)block, | ||
| rb_sqlite3_func, | ||
| NULL, | ||
| NULL | ||
| ); | ||
| CHECK(ctx->db, status); | ||
| CHECK(ctx->db, status); | ||
| rb_hash_aset(rb_iv_get(self, "@functions"), name, block); | ||
| rb_hash_aset(rb_iv_get(self, "@functions"), name, block); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -406,5 +474,6 @@ | ||
| */ | ||
| static VALUE define_function(VALUE self, VALUE name) | ||
| static VALUE | ||
| define_function(VALUE self, VALUE name) | ||
| { | ||
| return define_function_with_flags(self, name, INT2FIX(SQLITE_UTF8)); | ||
| return define_function_with_flags(self, name, INT2FIX(SQLITE_UTF8)); | ||
| } | ||
@@ -416,11 +485,12 @@ | ||
| */ | ||
| static VALUE interrupt(VALUE self) | ||
| static VALUE | ||
| interrupt(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3_interrupt(ctx->db); | ||
| sqlite3_interrupt(ctx->db); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -433,9 +503,10 @@ | ||
| */ | ||
| static VALUE errmsg(VALUE self) | ||
| static VALUE | ||
| errmsg(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return rb_str_new2(sqlite3_errmsg(ctx->db)); | ||
| return rb_str_new2(sqlite3_errmsg(ctx->db)); | ||
| } | ||
@@ -448,9 +519,10 @@ | ||
| */ | ||
| static VALUE errcode_(VALUE self) | ||
| static VALUE | ||
| errcode_(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return INT2NUM(sqlite3_errcode(ctx->db)); | ||
| return INT2NUM(sqlite3_errcode(ctx->db)); | ||
| } | ||
@@ -463,8 +535,10 @@ | ||
| */ | ||
| static VALUE complete_p(VALUE UNUSED(self), VALUE sql) | ||
| static VALUE | ||
| complete_p(VALUE UNUSED(self), VALUE sql) | ||
| { | ||
| if(sqlite3_complete(StringValuePtr(sql))) | ||
| return Qtrue; | ||
| if (sqlite3_complete(StringValuePtr(sql))) { | ||
| return Qtrue; | ||
| } | ||
| return Qfalse; | ||
| return Qfalse; | ||
| } | ||
@@ -478,33 +552,35 @@ | ||
| */ | ||
| static VALUE changes(VALUE self) | ||
| static VALUE | ||
| changes(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return INT2NUM(sqlite3_changes(ctx->db)); | ||
| return INT2NUM(sqlite3_changes(ctx->db)); | ||
| } | ||
| static int rb_sqlite3_auth( | ||
| static int | ||
| rb_sqlite3_auth( | ||
| void *ctx, | ||
| int _action, | ||
| const char * _a, | ||
| const char * _b, | ||
| const char * _c, | ||
| const char * _d) | ||
| const char *_a, | ||
| const char *_b, | ||
| const char *_c, | ||
| const char *_d) | ||
| { | ||
| VALUE self = (VALUE)ctx; | ||
| VALUE action = INT2NUM(_action); | ||
| VALUE a = _a ? rb_str_new2(_a) : Qnil; | ||
| VALUE b = _b ? rb_str_new2(_b) : Qnil; | ||
| VALUE c = _c ? rb_str_new2(_c) : Qnil; | ||
| VALUE d = _d ? rb_str_new2(_d) : Qnil; | ||
| VALUE callback = rb_iv_get(self, "@authorizer"); | ||
| VALUE result = rb_funcall(callback, rb_intern("call"), 5, action, a, b, c, d); | ||
| VALUE self = (VALUE)ctx; | ||
| VALUE action = INT2NUM(_action); | ||
| VALUE a = _a ? rb_str_new2(_a) : Qnil; | ||
| VALUE b = _b ? rb_str_new2(_b) : Qnil; | ||
| VALUE c = _c ? rb_str_new2(_c) : Qnil; | ||
| VALUE d = _d ? rb_str_new2(_d) : Qnil; | ||
| VALUE callback = rb_iv_get(self, "@authorizer"); | ||
| VALUE result = rb_funcall(callback, rb_intern("call"), 5, action, a, b, c, d); | ||
| if(T_FIXNUM == TYPE(result)) return (int)NUM2INT(result); | ||
| if(Qtrue == result) return SQLITE_OK; | ||
| if(Qfalse == result) return SQLITE_DENY; | ||
| if (T_FIXNUM == TYPE(result)) { return (int)NUM2INT(result); } | ||
| if (Qtrue == result) { return SQLITE_OK; } | ||
| if (Qfalse == result) { return SQLITE_DENY; } | ||
| return SQLITE_IGNORE; | ||
| return SQLITE_IGNORE; | ||
| } | ||
@@ -522,19 +598,20 @@ | ||
| */ | ||
| static VALUE set_authorizer(VALUE self, VALUE authorizer) | ||
| static VALUE | ||
| set_authorizer(VALUE self, VALUE authorizer) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| status = sqlite3_set_authorizer( | ||
| ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth, (void *)self | ||
| ); | ||
| status = sqlite3_set_authorizer( | ||
| ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth, (void *)self | ||
| ); | ||
| CHECK(ctx->db, status); | ||
| CHECK(ctx->db, status); | ||
| rb_iv_set(self, "@authorizer", authorizer); | ||
| rb_iv_set(self, "@authorizer", authorizer); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -552,11 +629,12 @@ | ||
| */ | ||
| static VALUE set_busy_timeout(VALUE self, VALUE timeout) | ||
| static VALUE | ||
| set_busy_timeout(VALUE self, VALUE timeout) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout))); | ||
| CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout))); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -569,38 +647,40 @@ | ||
| */ | ||
| static VALUE set_extended_result_codes(VALUE self, VALUE enable) | ||
| static VALUE | ||
| set_extended_result_codes(VALUE self, VALUE enable) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| CHECK(ctx->db, sqlite3_extended_result_codes(ctx->db, RTEST(enable) ? 1 : 0)); | ||
| CHECK(ctx->db, sqlite3_extended_result_codes(ctx->db, RTEST(enable) ? 1 : 0)); | ||
| return self; | ||
| return self; | ||
| } | ||
| int rb_comparator_func(void * ctx, int a_len, const void * a, int b_len, const void * b) | ||
| int | ||
| rb_comparator_func(void *ctx, int a_len, const void *a, int b_len, const void *b) | ||
| { | ||
| VALUE comparator; | ||
| VALUE a_str; | ||
| VALUE b_str; | ||
| VALUE comparison; | ||
| rb_encoding * internal_encoding; | ||
| VALUE comparator; | ||
| VALUE a_str; | ||
| VALUE b_str; | ||
| VALUE comparison; | ||
| rb_encoding *internal_encoding; | ||
| internal_encoding = rb_default_internal_encoding(); | ||
| internal_encoding = rb_default_internal_encoding(); | ||
| comparator = (VALUE)ctx; | ||
| a_str = rb_str_new((const char *)a, a_len); | ||
| b_str = rb_str_new((const char *)b, b_len); | ||
| comparator = (VALUE)ctx; | ||
| a_str = rb_str_new((const char *)a, a_len); | ||
| b_str = rb_str_new((const char *)b, b_len); | ||
| rb_enc_associate_index(a_str, rb_utf8_encindex()); | ||
| rb_enc_associate_index(b_str, rb_utf8_encindex()); | ||
| rb_enc_associate_index(a_str, rb_utf8_encindex()); | ||
| rb_enc_associate_index(b_str, rb_utf8_encindex()); | ||
| if(internal_encoding) { | ||
| a_str = rb_str_export_to_enc(a_str, internal_encoding); | ||
| b_str = rb_str_export_to_enc(b_str, internal_encoding); | ||
| } | ||
| if (internal_encoding) { | ||
| a_str = rb_str_export_to_enc(a_str, internal_encoding); | ||
| b_str = rb_str_export_to_enc(b_str, internal_encoding); | ||
| } | ||
| comparison = rb_funcall(comparator, rb_intern("compare"), 2, a_str, b_str); | ||
| comparison = rb_funcall(comparator, rb_intern("compare"), 2, a_str, b_str); | ||
| return NUM2INT(comparison); | ||
| return NUM2INT(comparison); | ||
| } | ||
@@ -615,19 +695,20 @@ | ||
| */ | ||
| static VALUE collation(VALUE self, VALUE name, VALUE comparator) | ||
| static VALUE | ||
| collation(VALUE self, VALUE name, VALUE comparator) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| CHECK(ctx->db, sqlite3_create_collation( | ||
| ctx->db, | ||
| StringValuePtr(name), | ||
| SQLITE_UTF8, | ||
| (void *)comparator, | ||
| NIL_P(comparator) ? NULL : rb_comparator_func)); | ||
| CHECK(ctx->db, sqlite3_create_collation( | ||
| ctx->db, | ||
| StringValuePtr(name), | ||
| SQLITE_UTF8, | ||
| (void *)comparator, | ||
| NIL_P(comparator) ? NULL : rb_comparator_func)); | ||
| /* Make sure our comparator doesn't get garbage collected. */ | ||
| rb_hash_aset(rb_iv_get(self, "@collations"), name, comparator); | ||
| /* Make sure our comparator doesn't get garbage collected. */ | ||
| rb_hash_aset(rb_iv_get(self, "@collations"), name, comparator); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -642,20 +723,17 @@ | ||
| */ | ||
| static VALUE load_extension(VALUE self, VALUE file) | ||
| static VALUE | ||
| load_extension(VALUE self, VALUE file) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| char *errMsg; | ||
| VALUE errexp; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| char *errMsg; | ||
| status = sqlite3_load_extension(ctx->db, StringValuePtr(file), 0, &errMsg); | ||
| if (status != SQLITE_OK) | ||
| { | ||
| errexp = rb_exc_new2(rb_eRuntimeError, errMsg); | ||
| sqlite3_free(errMsg); | ||
| rb_exc_raise(errexp); | ||
| } | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return self; | ||
| status = sqlite3_load_extension(ctx->db, StringValuePtr(file), 0, &errMsg); | ||
| CHECK_MSG(ctx->db, status, errMsg); | ||
| return self; | ||
| } | ||
@@ -669,55 +747,24 @@ #endif | ||
| */ | ||
| static VALUE enable_load_extension(VALUE self, VALUE onoff) | ||
| static VALUE | ||
| enable_load_extension(VALUE self, VALUE onoff) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| int onoffparam; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| int onoffparam; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| if (Qtrue == onoff) { | ||
| onoffparam = 1; | ||
| } else if (Qfalse == onoff) { | ||
| onoffparam = 0; | ||
| } else { | ||
| onoffparam = (int)NUM2INT(onoff); | ||
| } | ||
| if (Qtrue == onoff) { | ||
| onoffparam = 1; | ||
| } else if (Qfalse == onoff) { | ||
| onoffparam = 0; | ||
| } else { | ||
| onoffparam = (int)NUM2INT(onoff); | ||
| } | ||
| CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, onoffparam)); | ||
| CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, onoffparam)); | ||
| return self; | ||
| return self; | ||
| } | ||
| #endif | ||
| static int enc_cb(void * _self, int UNUSED(columns), char **data, char **UNUSED(names)) | ||
| { | ||
| VALUE self = (VALUE)_self; | ||
| int index = rb_enc_find_index(data[0]); | ||
| rb_encoding * e = rb_enc_from_index(index); | ||
| rb_iv_set(self, "@encoding", rb_enc_from_encoding(e)); | ||
| return 0; | ||
| } | ||
| /* call-seq: db.encoding | ||
| * | ||
| * Fetch the encoding set on this database | ||
| */ | ||
| static VALUE db_encoding(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| VALUE enc; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| enc = rb_iv_get(self, "@encoding"); | ||
| if(NIL_P(enc)) { | ||
| sqlite3_exec(ctx->db, "PRAGMA encoding", enc_cb, (void *)self, NULL); | ||
| } | ||
| return rb_iv_get(self, "@encoding"); | ||
| } | ||
| /* call-seq: db.transaction_active? | ||
@@ -728,45 +775,48 @@ * | ||
| */ | ||
| static VALUE transaction_active_p(VALUE self) | ||
| static VALUE | ||
| transaction_active_p(VALUE self) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| return sqlite3_get_autocommit(ctx->db) ? Qfalse : Qtrue; | ||
| return sqlite3_get_autocommit(ctx->db) ? Qfalse : Qtrue; | ||
| } | ||
| static int hash_callback_function(VALUE callback_ary, int count, char **data, char **columns) | ||
| static int | ||
| hash_callback_function(VALUE callback_ary, int count, char **data, char **columns) | ||
| { | ||
| VALUE new_hash = rb_hash_new(); | ||
| int i; | ||
| VALUE new_hash = rb_hash_new(); | ||
| int i; | ||
| for (i = 0; i < count; i++) { | ||
| if (data[i] == NULL) { | ||
| rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), Qnil); | ||
| } else { | ||
| rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), rb_str_new_cstr(data[i])); | ||
| for (i = 0; i < count; i++) { | ||
| if (data[i] == NULL) { | ||
| rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), Qnil); | ||
| } else { | ||
| rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), rb_str_new_cstr(data[i])); | ||
| } | ||
| } | ||
| } | ||
| rb_ary_push(callback_ary, new_hash); | ||
| rb_ary_push(callback_ary, new_hash); | ||
| return 0; | ||
| return 0; | ||
| } | ||
| static int regular_callback_function(VALUE callback_ary, int count, char **data, char **columns) | ||
| static int | ||
| regular_callback_function(VALUE callback_ary, int count, char **data, char **columns) | ||
| { | ||
| VALUE new_ary = rb_ary_new(); | ||
| int i; | ||
| VALUE new_ary = rb_ary_new(); | ||
| int i; | ||
| for (i = 0; i < count; i++) { | ||
| if (data[i] == NULL) { | ||
| rb_ary_push(new_ary, Qnil); | ||
| } else { | ||
| rb_ary_push(new_ary, rb_str_new_cstr(data[i])); | ||
| for (i = 0; i < count; i++) { | ||
| if (data[i] == NULL) { | ||
| rb_ary_push(new_ary, Qnil); | ||
| } else { | ||
| rb_ary_push(new_ary, rb_str_new_cstr(data[i])); | ||
| } | ||
| } | ||
| } | ||
| rb_ary_push(callback_ary, new_ary); | ||
| rb_ary_push(callback_ary, new_ary); | ||
| return 0; | ||
| return 0; | ||
| } | ||
@@ -783,27 +833,26 @@ | ||
| */ | ||
| static VALUE exec_batch(VALUE self, VALUE sql, VALUE results_as_hash) | ||
| static VALUE | ||
| exec_batch(VALUE self, VALUE sql, VALUE results_as_hash) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| VALUE callback_ary = rb_ary_new(); | ||
| char *errMsg; | ||
| VALUE errexp; | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| VALUE callback_ary = rb_ary_new(); | ||
| char *errMsg; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| if(results_as_hash == Qtrue) { | ||
| status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)hash_callback_function, (void*)callback_ary, &errMsg); | ||
| } else { | ||
| status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)regular_callback_function, (void*)callback_ary, &errMsg); | ||
| } | ||
| if (results_as_hash == Qtrue) { | ||
| status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)hash_callback_function, | ||
| (void *)callback_ary, | ||
| &errMsg); | ||
| } else { | ||
| status = sqlite3_exec(ctx->db, StringValuePtr(sql), (sqlite3_callback)regular_callback_function, | ||
| (void *)callback_ary, | ||
| &errMsg); | ||
| } | ||
| if (status != SQLITE_OK) | ||
| { | ||
| errexp = rb_exc_new2(rb_eRuntimeError, errMsg); | ||
| sqlite3_free(errMsg); | ||
| rb_exc_raise(errexp); | ||
| } | ||
| CHECK_MSG(ctx->db, status, errMsg); | ||
| return callback_ary; | ||
| return callback_ary; | ||
| } | ||
@@ -816,84 +865,88 @@ | ||
| */ | ||
| static VALUE db_filename(VALUE self, VALUE db_name) | ||
| static VALUE | ||
| db_filename(VALUE self, VALUE db_name) | ||
| { | ||
| sqlite3RubyPtr ctx; | ||
| const char * fname; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| sqlite3RubyPtr ctx; | ||
| const char *fname; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| REQUIRE_OPEN_DB(ctx); | ||
| fname = sqlite3_db_filename(ctx->db, StringValueCStr(db_name)); | ||
| fname = sqlite3_db_filename(ctx->db, StringValueCStr(db_name)); | ||
| if(fname) return SQLITE3_UTF8_STR_NEW2(fname); | ||
| return Qnil; | ||
| if (fname) { return SQLITE3_UTF8_STR_NEW2(fname); } | ||
| return Qnil; | ||
| } | ||
| static VALUE rb_sqlite3_open16(VALUE self, VALUE file) | ||
| static VALUE | ||
| rb_sqlite3_open16(VALUE self, VALUE file) | ||
| { | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| int status; | ||
| sqlite3RubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx); | ||
| #if defined TAINTING_SUPPORT | ||
| #if defined StringValueCStr | ||
| StringValuePtr(file); | ||
| rb_check_safe_obj(file); | ||
| StringValuePtr(file); | ||
| rb_check_safe_obj(file); | ||
| #else | ||
| Check_SafeStr(file); | ||
| Check_SafeStr(file); | ||
| #endif | ||
| #endif | ||
| status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db); | ||
| status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db); | ||
| CHECK(ctx->db, status) | ||
| CHECK(ctx->db, status) | ||
| return INT2NUM(status); | ||
| return INT2NUM(status); | ||
| } | ||
| void init_sqlite3_database(void) | ||
| void | ||
| init_sqlite3_database(void) | ||
| { | ||
| #if 0 | ||
| VALUE mSqlite3 = rb_define_module("SQLite3"); | ||
| VALUE mSqlite3 = rb_define_module("SQLite3"); | ||
| #endif | ||
| cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject); | ||
| cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject); | ||
| rb_define_alloc_func(cSqlite3Database, allocate); | ||
| rb_define_private_method(cSqlite3Database, "open_v2", rb_sqlite3_open_v2, 3); | ||
| rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1); | ||
| rb_define_method(cSqlite3Database, "collation", collation, 2); | ||
| rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0); | ||
| rb_define_method(cSqlite3Database, "closed?", closed_p, 0); | ||
| rb_define_method(cSqlite3Database, "total_changes", total_changes, 0); | ||
| rb_define_method(cSqlite3Database, "trace", trace, -1); | ||
| rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0); | ||
| rb_define_method(cSqlite3Database, "define_function", define_function, 1); | ||
| rb_define_method(cSqlite3Database, "define_function_with_flags", define_function_with_flags, 2); | ||
| /* public "define_aggregator" is now a shim around define_aggregator2 | ||
| * implemented in Ruby */ | ||
| rb_define_private_method(cSqlite3Database, "define_aggregator2", rb_sqlite3_define_aggregator2, 2); | ||
| rb_define_private_method(cSqlite3Database, "disable_quirk_mode", rb_sqlite3_disable_quirk_mode, 0); | ||
| rb_define_method(cSqlite3Database, "interrupt", interrupt, 0); | ||
| rb_define_method(cSqlite3Database, "errmsg", errmsg, 0); | ||
| rb_define_method(cSqlite3Database, "errcode", errcode_, 0); | ||
| rb_define_method(cSqlite3Database, "complete?", complete_p, 1); | ||
| rb_define_method(cSqlite3Database, "changes", changes, 0); | ||
| rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1); | ||
| rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1); | ||
| 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); | ||
| rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2); | ||
| rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1); | ||
| rb_define_alloc_func(cSqlite3Database, allocate); | ||
| rb_define_private_method(cSqlite3Database, "open_v2", rb_sqlite3_open_v2, 3); | ||
| rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1); | ||
| rb_define_method(cSqlite3Database, "collation", collation, 2); | ||
| rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0); | ||
| rb_define_method(cSqlite3Database, "closed?", closed_p, 0); | ||
| rb_define_method(cSqlite3Database, "total_changes", total_changes, 0); | ||
| rb_define_method(cSqlite3Database, "trace", trace, -1); | ||
| rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0); | ||
| rb_define_method(cSqlite3Database, "define_function", define_function, 1); | ||
| rb_define_method(cSqlite3Database, "define_function_with_flags", define_function_with_flags, 2); | ||
| /* public "define_aggregator" is now a shim around define_aggregator2 | ||
| * implemented in Ruby */ | ||
| rb_define_private_method(cSqlite3Database, "define_aggregator2", rb_sqlite3_define_aggregator2, 2); | ||
| rb_define_private_method(cSqlite3Database, "disable_quirk_mode", rb_sqlite3_disable_quirk_mode, 0); | ||
| rb_define_method(cSqlite3Database, "interrupt", interrupt, 0); | ||
| rb_define_method(cSqlite3Database, "errmsg", errmsg, 0); | ||
| rb_define_method(cSqlite3Database, "errcode", errcode_, 0); | ||
| rb_define_method(cSqlite3Database, "complete?", complete_p, 1); | ||
| rb_define_method(cSqlite3Database, "changes", changes, 0); | ||
| rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1); | ||
| rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1); | ||
| rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1); | ||
| #ifndef SQLITE_OMIT_PROGRESS_CALLBACK | ||
| rb_define_method(cSqlite3Database, "statement_timeout=", set_statement_timeout, 1); | ||
| #endif | ||
| rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1); | ||
| rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0); | ||
| rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2); | ||
| rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1); | ||
| #ifdef HAVE_SQLITE3_LOAD_EXTENSION | ||
| rb_define_method(cSqlite3Database, "load_extension", load_extension, 1); | ||
| rb_define_method(cSqlite3Database, "load_extension", load_extension, 1); | ||
| #endif | ||
| #ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION | ||
| rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1); | ||
| rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1); | ||
| #endif | ||
| rb_define_method(cSqlite3Database, "encoding", db_encoding, 0); | ||
| rb_sqlite3_aggregator_init(); | ||
| rb_sqlite3_aggregator_init(); | ||
| } | ||
@@ -900,0 +953,0 @@ |
@@ -7,14 +7,17 @@ #ifndef SQLITE3_DATABASE_RUBY | ||
| struct _sqlite3Ruby { | ||
| sqlite3 *db; | ||
| sqlite3 *db; | ||
| VALUE busy_handler; | ||
| int stmt_timeout; | ||
| struct timespec stmt_deadline; | ||
| }; | ||
| typedef struct _sqlite3Ruby sqlite3Ruby; | ||
| typedef sqlite3Ruby * sqlite3RubyPtr; | ||
| typedef sqlite3Ruby *sqlite3RubyPtr; | ||
| void init_sqlite3_database(); | ||
| void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result); | ||
| void set_sqlite3_func_result(sqlite3_context *ctx, VALUE result); | ||
| sqlite3RubyPtr sqlite3_database_unwrap(VALUE database); | ||
| VALUE sqlite3val2rb(sqlite3_value * val); | ||
| VALUE sqlite3val2rb(sqlite3_value *val); | ||
| #endif |
+111
-92
| #include <sqlite3_ruby.h> | ||
| void rb_sqlite3_raise(sqlite3 * db, int status) | ||
| void | ||
| rb_sqlite3_raise(sqlite3 *db, int status) | ||
| { | ||
| VALUE klass = Qnil; | ||
| VALUE klass = Qnil; | ||
| /* Consider only lower 8 bits, to work correctly when | ||
| extended result codes are enabled. */ | ||
| switch(status & 0xff) { | ||
| case SQLITE_OK: | ||
| return; | ||
| break; | ||
| case SQLITE_ERROR: | ||
| klass = rb_path2class("SQLite3::SQLException"); | ||
| break; | ||
| case SQLITE_INTERNAL: | ||
| klass = rb_path2class("SQLite3::InternalException"); | ||
| break; | ||
| case SQLITE_PERM: | ||
| klass = rb_path2class("SQLite3::PermissionException"); | ||
| break; | ||
| case SQLITE_ABORT: | ||
| klass = rb_path2class("SQLite3::AbortException"); | ||
| break; | ||
| case SQLITE_BUSY: | ||
| klass = rb_path2class("SQLite3::BusyException"); | ||
| break; | ||
| case SQLITE_LOCKED: | ||
| klass = rb_path2class("SQLite3::LockedException"); | ||
| break; | ||
| case SQLITE_NOMEM: | ||
| klass = rb_path2class("SQLite3::MemoryException"); | ||
| break; | ||
| case SQLITE_READONLY: | ||
| klass = rb_path2class("SQLite3::ReadOnlyException"); | ||
| break; | ||
| case SQLITE_INTERRUPT: | ||
| klass = rb_path2class("SQLite3::InterruptException"); | ||
| break; | ||
| case SQLITE_IOERR: | ||
| klass = rb_path2class("SQLite3::IOException"); | ||
| break; | ||
| case SQLITE_CORRUPT: | ||
| klass = rb_path2class("SQLite3::CorruptException"); | ||
| break; | ||
| case SQLITE_NOTFOUND: | ||
| klass = rb_path2class("SQLite3::NotFoundException"); | ||
| break; | ||
| case SQLITE_FULL: | ||
| klass = rb_path2class("SQLite3::FullException"); | ||
| break; | ||
| case SQLITE_CANTOPEN: | ||
| klass = rb_path2class("SQLite3::CantOpenException"); | ||
| break; | ||
| case SQLITE_PROTOCOL: | ||
| klass = rb_path2class("SQLite3::ProtocolException"); | ||
| break; | ||
| case SQLITE_EMPTY: | ||
| klass = rb_path2class("SQLite3::EmptyException"); | ||
| break; | ||
| case SQLITE_SCHEMA: | ||
| klass = rb_path2class("SQLite3::SchemaChangedException"); | ||
| break; | ||
| case SQLITE_TOOBIG: | ||
| klass = rb_path2class("SQLite3::TooBigException"); | ||
| break; | ||
| case SQLITE_CONSTRAINT: | ||
| klass = rb_path2class("SQLite3::ConstraintException"); | ||
| break; | ||
| case SQLITE_MISMATCH: | ||
| klass = rb_path2class("SQLite3::MismatchException"); | ||
| break; | ||
| case SQLITE_MISUSE: | ||
| klass = rb_path2class("SQLite3::MisuseException"); | ||
| break; | ||
| case SQLITE_NOLFS: | ||
| klass = rb_path2class("SQLite3::UnsupportedException"); | ||
| break; | ||
| case SQLITE_AUTH: | ||
| klass = rb_path2class("SQLite3::AuthorizationException"); | ||
| break; | ||
| case SQLITE_FORMAT: | ||
| klass = rb_path2class("SQLite3::FormatException"); | ||
| break; | ||
| case SQLITE_RANGE: | ||
| klass = rb_path2class("SQLite3::RangeException"); | ||
| break; | ||
| case SQLITE_NOTADB: | ||
| klass = rb_path2class("SQLite3::NotADatabaseException"); | ||
| break; | ||
| default: | ||
| klass = rb_eRuntimeError; | ||
| } | ||
| /* Consider only lower 8 bits, to work correctly when | ||
| extended result codes are enabled. */ | ||
| switch (status & 0xff) { | ||
| case SQLITE_OK: | ||
| return; | ||
| break; | ||
| case SQLITE_ERROR: | ||
| klass = rb_path2class("SQLite3::SQLException"); | ||
| break; | ||
| case SQLITE_INTERNAL: | ||
| klass = rb_path2class("SQLite3::InternalException"); | ||
| break; | ||
| case SQLITE_PERM: | ||
| klass = rb_path2class("SQLite3::PermissionException"); | ||
| break; | ||
| case SQLITE_ABORT: | ||
| klass = rb_path2class("SQLite3::AbortException"); | ||
| break; | ||
| case SQLITE_BUSY: | ||
| klass = rb_path2class("SQLite3::BusyException"); | ||
| break; | ||
| case SQLITE_LOCKED: | ||
| klass = rb_path2class("SQLite3::LockedException"); | ||
| break; | ||
| case SQLITE_NOMEM: | ||
| klass = rb_path2class("SQLite3::MemoryException"); | ||
| break; | ||
| case SQLITE_READONLY: | ||
| klass = rb_path2class("SQLite3::ReadOnlyException"); | ||
| break; | ||
| case SQLITE_INTERRUPT: | ||
| klass = rb_path2class("SQLite3::InterruptException"); | ||
| break; | ||
| case SQLITE_IOERR: | ||
| klass = rb_path2class("SQLite3::IOException"); | ||
| break; | ||
| case SQLITE_CORRUPT: | ||
| klass = rb_path2class("SQLite3::CorruptException"); | ||
| break; | ||
| case SQLITE_NOTFOUND: | ||
| klass = rb_path2class("SQLite3::NotFoundException"); | ||
| break; | ||
| case SQLITE_FULL: | ||
| klass = rb_path2class("SQLite3::FullException"); | ||
| break; | ||
| case SQLITE_CANTOPEN: | ||
| klass = rb_path2class("SQLite3::CantOpenException"); | ||
| break; | ||
| case SQLITE_PROTOCOL: | ||
| klass = rb_path2class("SQLite3::ProtocolException"); | ||
| break; | ||
| case SQLITE_EMPTY: | ||
| klass = rb_path2class("SQLite3::EmptyException"); | ||
| break; | ||
| case SQLITE_SCHEMA: | ||
| klass = rb_path2class("SQLite3::SchemaChangedException"); | ||
| break; | ||
| case SQLITE_TOOBIG: | ||
| klass = rb_path2class("SQLite3::TooBigException"); | ||
| break; | ||
| case SQLITE_CONSTRAINT: | ||
| klass = rb_path2class("SQLite3::ConstraintException"); | ||
| break; | ||
| case SQLITE_MISMATCH: | ||
| klass = rb_path2class("SQLite3::MismatchException"); | ||
| break; | ||
| case SQLITE_MISUSE: | ||
| klass = rb_path2class("SQLite3::MisuseException"); | ||
| break; | ||
| case SQLITE_NOLFS: | ||
| klass = rb_path2class("SQLite3::UnsupportedException"); | ||
| break; | ||
| case SQLITE_AUTH: | ||
| klass = rb_path2class("SQLite3::AuthorizationException"); | ||
| break; | ||
| case SQLITE_FORMAT: | ||
| klass = rb_path2class("SQLite3::FormatException"); | ||
| break; | ||
| case SQLITE_RANGE: | ||
| klass = rb_path2class("SQLite3::RangeException"); | ||
| break; | ||
| case SQLITE_NOTADB: | ||
| klass = rb_path2class("SQLite3::NotADatabaseException"); | ||
| break; | ||
| default: | ||
| klass = rb_path2class("SQLite3::Exception"); | ||
| } | ||
| klass = rb_exc_new2(klass, sqlite3_errmsg(db)); | ||
| rb_iv_set(klass, "@code", INT2FIX(status)); | ||
| rb_exc_raise(klass); | ||
| klass = rb_exc_new2(klass, sqlite3_errmsg(db)); | ||
| rb_iv_set(klass, "@code", INT2FIX(status)); | ||
| rb_exc_raise(klass); | ||
| } | ||
| /* | ||
| * accepts a sqlite3 error message as the final argument, which will be `sqlite3_free`d | ||
| */ | ||
| void | ||
| rb_sqlite3_raise_msg(sqlite3 *db, int status, const char *msg) | ||
| { | ||
| VALUE exception; | ||
| if (status == SQLITE_OK) { | ||
| return; | ||
| } | ||
| exception = rb_exc_new2(rb_path2class("SQLite3::Exception"), msg); | ||
| sqlite3_free((void *)msg); | ||
| rb_iv_set(exception, "@code", INT2FIX(status)); | ||
| rb_exc_raise(exception); | ||
| } |
@@ -5,5 +5,7 @@ #ifndef SQLITE3_EXCEPTION_RUBY | ||
| #define CHECK(_db, _status) rb_sqlite3_raise(_db, _status); | ||
| #define CHECK_MSG(_db, _status, _msg) rb_sqlite3_raise_msg(_db, _status, _msg); | ||
| void rb_sqlite3_raise(sqlite3 * db, int status); | ||
| void rb_sqlite3_raise(sqlite3 *db, int status); | ||
| void rb_sqlite3_raise_msg(sqlite3 *db, int status, const char *msg); | ||
| #endif |
+21
-22
@@ -22,3 +22,3 @@ require "mkmf" | ||
| create_makefile('sqlite3/sqlite3_native') | ||
| create_makefile("sqlite3/sqlite3_native") | ||
| end | ||
@@ -56,3 +56,3 @@ | ||
| "--enable-static=yes", | ||
| "--enable-fts5", | ||
| "--enable-fts5" | ||
| ] | ||
@@ -65,7 +65,7 @@ ENV.to_h.tap do |env| | ||
| "-fvisibility=hidden", # see https://github.com/rake-compiler/rake-compiler-dock/issues/87 | ||
| "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1", | ||
| "-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1" | ||
| ] | ||
| env["CFLAGS"] = [user_cflags, env["CFLAGS"], more_cflags].flatten.join(" ") | ||
| recipe.configure_options += env.select { |k,v| ENV_ALLOWLIST.include?(k) } | ||
| .map { |key, value| "#{key}=#{value.strip}" } | ||
| recipe.configure_options += env.select { |k, v| ENV_ALLOWLIST.include?(k) } | ||
| .map { |key, value| "#{key}=#{value.strip}" } | ||
| end | ||
@@ -103,6 +103,2 @@ | ||
| def configure_extension | ||
| if Gem::Requirement.new("< 2.7").satisfied_by?(Gem::Version.new(RUBY_VERSION)) | ||
| append_cppflags("-DTAINTING_SUPPORT") | ||
| end | ||
| append_cflags("-fvisibility=hidden") # see https://github.com/rake-compiler/rake-compiler-dock/issues/87 | ||
@@ -120,22 +116,26 @@ | ||
| # Truffle Ruby doesn't support this yet: | ||
| # https://github.com/oracle/truffleruby/issues/3408 | ||
| have_func("rb_enc_interned_str_cstr") | ||
| # Functions defined in 1.9 but not 1.8 | ||
| have_func('rb_proc_arity') | ||
| have_func("rb_proc_arity") | ||
| # Functions defined in 2.1 but not 2.0 | ||
| have_func('rb_integer_pack') | ||
| have_func("rb_integer_pack") | ||
| # These functions may not be defined | ||
| have_func('sqlite3_initialize') | ||
| have_func('sqlite3_backup_init') | ||
| have_func('sqlite3_column_database_name') | ||
| have_func('sqlite3_enable_load_extension') | ||
| have_func('sqlite3_load_extension') | ||
| have_func("sqlite3_initialize") | ||
| have_func("sqlite3_backup_init") | ||
| have_func("sqlite3_column_database_name") | ||
| have_func("sqlite3_enable_load_extension") | ||
| have_func("sqlite3_load_extension") | ||
| unless have_func('sqlite3_open_v2') # https://www.sqlite.org/releaselog/3_5_0.html | ||
| unless have_func("sqlite3_open_v2") # https://www.sqlite.org/releaselog/3_5_0.html | ||
| abort("\nPlease use a version of SQLite3 >= 3.5.0\n\n") | ||
| end | ||
| have_func('sqlite3_prepare_v2') | ||
| have_type('sqlite3_int64', 'sqlite3.h') | ||
| have_type('sqlite3_uint64', 'sqlite3.h') | ||
| have_func("sqlite3_prepare_v2") | ||
| have_type("sqlite3_int64", "sqlite3.h") | ||
| have_type("sqlite3_uint64", "sqlite3.h") | ||
| end | ||
@@ -166,4 +166,3 @@ | ||
| def mini_portile_config | ||
| # TODO: once Ruby 2.7 is no longer supported, use symbolize_names: true | ||
| YAML.load_file(File.join(package_root_dir, "dependencies.yml")) | ||
| YAML.load_file(File.join(package_root_dir, "dependencies.yml"), symbolize_names: true) | ||
| end | ||
@@ -170,0 +169,0 @@ |
@@ -21,4 +21,3 @@ #ifndef SQLITE3_RUBY | ||
| #define UTF16_BE_P(_obj) (rb_enc_get_index(_obj) == rb_enc_find_index("UTF-16BE")) | ||
| #define SQLITE3_UTF8_STR_NEW2(_obj) \ | ||
| (rb_enc_associate_index(rb_str_new2(_obj), rb_utf8_encindex())) | ||
| #define SQLITE3_UTF8_STR_NEW2(_obj) (rb_utf8_str_new_cstr(_obj)) | ||
@@ -46,2 +45,3 @@ #ifdef USING_SQLCIPHER_INC_SUBDIR | ||
| #include <backup.h> | ||
| #include <timespec.h> | ||
@@ -48,0 +48,0 @@ int bignum_to_int64(VALUE big, sqlite3_int64 *result); |
+154
-110
@@ -6,27 +6,28 @@ #include <sqlite3_ruby.h> | ||
| int bignum_to_int64(VALUE value, sqlite3_int64 *result) | ||
| int | ||
| bignum_to_int64(VALUE value, sqlite3_int64 *result) | ||
| { | ||
| #ifdef HAVE_RB_INTEGER_PACK | ||
| const int nails = 0; | ||
| int t = rb_integer_pack(value, result, 1, sizeof(*result), nails, | ||
| INTEGER_PACK_NATIVE_BYTE_ORDER| | ||
| INTEGER_PACK_2COMP); | ||
| switch (t) { | ||
| case -2: case +2: | ||
| return 0; | ||
| case +1: | ||
| if (!nails) { | ||
| if (*result < 0) return 0; | ||
| const int nails = 0; | ||
| int t = rb_integer_pack(value, result, 1, sizeof(*result), nails, | ||
| INTEGER_PACK_NATIVE_BYTE_ORDER | | ||
| INTEGER_PACK_2COMP); | ||
| switch (t) { | ||
| case -2: | ||
| case +2: | ||
| return 0; | ||
| case +1: | ||
| if (!nails) { | ||
| if (*result < 0) { return 0; } | ||
| } | ||
| break; | ||
| case -1: | ||
| if (!nails) { | ||
| if (*result >= 0) { return 0; } | ||
| } else { | ||
| *result += INT64_MIN; | ||
| } | ||
| break; | ||
| } | ||
| break; | ||
| case -1: | ||
| if (!nails) { | ||
| if (*result >= 0) return 0; | ||
| } | ||
| else { | ||
| *result += INT64_MIN; | ||
| } | ||
| break; | ||
| } | ||
| return 1; | ||
| return 1; | ||
| #else | ||
@@ -36,41 +37,42 @@ # ifndef RBIGNUM_LEN | ||
| # endif | ||
| const long len = RBIGNUM_LEN(value); | ||
| if (len == 0) { | ||
| *result = 0; | ||
| const long len = RBIGNUM_LEN(value); | ||
| if (len == 0) { | ||
| *result = 0; | ||
| return 1; | ||
| } | ||
| if (len > 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) { return 0; } | ||
| if (len == 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) { | ||
| const BDIGIT *digits = RBIGNUM_DIGITS(value); | ||
| BDIGIT blast = digits[len - 1]; | ||
| BDIGIT bmax = (BDIGIT)1UL << (63 % (CHAR_BIT * SIZEOF_BDIGITS)); | ||
| if (blast > bmax) { return 0; } | ||
| if (blast == bmax) { | ||
| if (RBIGNUM_POSITIVE_P(value)) { | ||
| return 0; | ||
| } else { | ||
| long i = len - 1; | ||
| while (i) { | ||
| if (digits[--i]) { return 0; } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| *result = (sqlite3_int64)NUM2LL(value); | ||
| return 1; | ||
| } | ||
| if (len > 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) return 0; | ||
| if (len == 63 / (SIZEOF_BDIGITS * CHAR_BIT) + 1) { | ||
| const BDIGIT *digits = RBIGNUM_DIGITS(value); | ||
| BDIGIT blast = digits[len-1]; | ||
| BDIGIT bmax = (BDIGIT)1UL << (63 % (CHAR_BIT * SIZEOF_BDIGITS)); | ||
| if (blast > bmax) return 0; | ||
| if (blast == bmax) { | ||
| if (RBIGNUM_POSITIVE_P(value)) { | ||
| return 0; | ||
| } | ||
| else { | ||
| long i = len-1; | ||
| while (i) { | ||
| if (digits[--i]) return 0; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| *result = (sqlite3_int64)NUM2LL(value); | ||
| return 1; | ||
| #endif | ||
| } | ||
| static VALUE libversion(VALUE UNUSED(klass)) | ||
| static VALUE | ||
| libversion(VALUE UNUSED(klass)) | ||
| { | ||
| return INT2NUM(sqlite3_libversion_number()); | ||
| return INT2NUM(sqlite3_libversion_number()); | ||
| } | ||
| static VALUE using_sqlcipher(VALUE UNUSED(klass)) | ||
| static VALUE | ||
| using_sqlcipher(VALUE UNUSED(klass)) | ||
| { | ||
| #ifdef USING_SQLCIPHER | ||
| return Qtrue; | ||
| return Qtrue; | ||
| #else | ||
| return Qfalse; | ||
| return Qfalse; | ||
| #endif | ||
@@ -82,49 +84,89 @@ } | ||
| */ | ||
| static VALUE threadsafe_p(VALUE UNUSED(klass)) | ||
| static VALUE | ||
| threadsafe_p(VALUE UNUSED(klass)) | ||
| { | ||
| return INT2NUM(sqlite3_threadsafe()); | ||
| return INT2NUM(sqlite3_threadsafe()); | ||
| } | ||
| void init_sqlite3_constants(void) | ||
| /* | ||
| * call-seq: | ||
| * status(parameter) → Hash | ||
| * status(parameter, reset_flag = false) → Hash | ||
| * | ||
| * Queries the SQLite3 library for run-time status information. Passing a truthy +reset_flag+ will | ||
| * reset the highwater mark to the current value. | ||
| * | ||
| * [Parameters] | ||
| * - +parameter+ (Integer, SQLite3::Constants::Status): The status parameter to query. | ||
| * - +reset_flag+ (Boolean): Whether to reset the highwater mark. (default is +false+) | ||
| * | ||
| * [Returns] | ||
| * A Hash containing +:current+ and +:highwater+ keys for integer values. | ||
| */ | ||
| static VALUE | ||
| rb_sqlite3_status(int argc, VALUE *argv, VALUE klass) | ||
| { | ||
| VALUE mSqlite3Constants; | ||
| VALUE mSqlite3Open; | ||
| VALUE opArg, resetFlagArg; | ||
| mSqlite3Constants = rb_define_module_under(mSqlite3, "Constants"); | ||
| rb_scan_args(argc, argv, "11", &opArg, &resetFlagArg); | ||
| /* sqlite3_open_v2 flags for Database::new */ | ||
| mSqlite3Open = rb_define_module_under(mSqlite3Constants, "Open"); | ||
| int op = NUM2INT(opArg); | ||
| bool resetFlag = RTEST(resetFlagArg); | ||
| /* symbols = IO.readlines('sqlite3.h').map { |n| /\A#define\s+(SQLITE_OPEN_\w+)\s/ =~ n && $1 }.compact | ||
| * pad = symbols.map(&:length).max - 9 | ||
| * symbols.each { |s| printf %Q{ rb_define_const(mSqlite3Open, %-#{pad}s INT2FIX(#{s}));\n}, '"' + s[12..-1] + '",' } | ||
| */ | ||
| rb_define_const(mSqlite3Open, "READONLY", INT2FIX(SQLITE_OPEN_READONLY)); | ||
| rb_define_const(mSqlite3Open, "READWRITE", INT2FIX(SQLITE_OPEN_READWRITE)); | ||
| rb_define_const(mSqlite3Open, "CREATE", INT2FIX(SQLITE_OPEN_CREATE)); | ||
| rb_define_const(mSqlite3Open, "DELETEONCLOSE", INT2FIX(SQLITE_OPEN_DELETEONCLOSE)); | ||
| rb_define_const(mSqlite3Open, "EXCLUSIVE", INT2FIX(SQLITE_OPEN_EXCLUSIVE)); | ||
| rb_define_const(mSqlite3Open, "MAIN_DB", INT2FIX(SQLITE_OPEN_MAIN_DB)); | ||
| rb_define_const(mSqlite3Open, "TEMP_DB", INT2FIX(SQLITE_OPEN_TEMP_DB)); | ||
| rb_define_const(mSqlite3Open, "TRANSIENT_DB", INT2FIX(SQLITE_OPEN_TRANSIENT_DB)); | ||
| rb_define_const(mSqlite3Open, "MAIN_JOURNAL", INT2FIX(SQLITE_OPEN_MAIN_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "TEMP_JOURNAL", INT2FIX(SQLITE_OPEN_TEMP_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "SUBJOURNAL", INT2FIX(SQLITE_OPEN_SUBJOURNAL)); | ||
| rb_define_const(mSqlite3Open, "MASTER_JOURNAL", INT2FIX(SQLITE_OPEN_MASTER_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "NOMUTEX", INT2FIX(SQLITE_OPEN_NOMUTEX)); | ||
| rb_define_const(mSqlite3Open, "FULLMUTEX", INT2FIX(SQLITE_OPEN_FULLMUTEX)); | ||
| int pCurrent = 0; | ||
| int pHighwater = 0; | ||
| sqlite3_status(op, &pCurrent, &pHighwater, resetFlag); | ||
| VALUE hash = rb_hash_new(); | ||
| rb_hash_aset(hash, ID2SYM(rb_intern("current")), INT2FIX(pCurrent)); | ||
| rb_hash_aset(hash, ID2SYM(rb_intern("highwater")), INT2FIX(pHighwater)); | ||
| return hash; | ||
| } | ||
| void | ||
| init_sqlite3_constants(void) | ||
| { | ||
| VALUE mSqlite3Constants; | ||
| VALUE mSqlite3Open; | ||
| mSqlite3Constants = rb_define_module_under(mSqlite3, "Constants"); | ||
| /* sqlite3_open_v2 flags for Database::new */ | ||
| mSqlite3Open = rb_define_module_under(mSqlite3Constants, "Open"); | ||
| /* symbols = IO.readlines('sqlite3.h').map { |n| /\A#define\s+(SQLITE_OPEN_\w+)\s/ =~ n && $1 }.compact | ||
| * pad = symbols.map(&:length).max - 9 | ||
| * symbols.each { |s| printf %Q{ rb_define_const(mSqlite3Open, %-#{pad}s INT2FIX(#{s}));\n}, '"' + s[12..-1] + '",' } | ||
| */ | ||
| rb_define_const(mSqlite3Open, "READONLY", INT2FIX(SQLITE_OPEN_READONLY)); | ||
| rb_define_const(mSqlite3Open, "READWRITE", INT2FIX(SQLITE_OPEN_READWRITE)); | ||
| rb_define_const(mSqlite3Open, "CREATE", INT2FIX(SQLITE_OPEN_CREATE)); | ||
| rb_define_const(mSqlite3Open, "DELETEONCLOSE", INT2FIX(SQLITE_OPEN_DELETEONCLOSE)); | ||
| rb_define_const(mSqlite3Open, "EXCLUSIVE", INT2FIX(SQLITE_OPEN_EXCLUSIVE)); | ||
| rb_define_const(mSqlite3Open, "MAIN_DB", INT2FIX(SQLITE_OPEN_MAIN_DB)); | ||
| rb_define_const(mSqlite3Open, "TEMP_DB", INT2FIX(SQLITE_OPEN_TEMP_DB)); | ||
| rb_define_const(mSqlite3Open, "TRANSIENT_DB", INT2FIX(SQLITE_OPEN_TRANSIENT_DB)); | ||
| rb_define_const(mSqlite3Open, "MAIN_JOURNAL", INT2FIX(SQLITE_OPEN_MAIN_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "TEMP_JOURNAL", INT2FIX(SQLITE_OPEN_TEMP_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "SUBJOURNAL", INT2FIX(SQLITE_OPEN_SUBJOURNAL)); | ||
| rb_define_const(mSqlite3Open, "MASTER_JOURNAL", | ||
| INT2FIX(SQLITE_OPEN_MASTER_JOURNAL)); /* pre-3.33.0 */ | ||
| rb_define_const(mSqlite3Open, "SUPER_JOURNAL", INT2FIX(SQLITE_OPEN_MASTER_JOURNAL)); | ||
| rb_define_const(mSqlite3Open, "NOMUTEX", INT2FIX(SQLITE_OPEN_NOMUTEX)); | ||
| rb_define_const(mSqlite3Open, "FULLMUTEX", INT2FIX(SQLITE_OPEN_FULLMUTEX)); | ||
| #ifdef SQLITE_OPEN_AUTOPROXY | ||
| /* SQLITE_VERSION_NUMBER>=3007002 */ | ||
| rb_define_const(mSqlite3Open, "AUTOPROXY", INT2FIX(SQLITE_OPEN_AUTOPROXY)); | ||
| rb_define_const(mSqlite3Open, "SHAREDCACHE", INT2FIX(SQLITE_OPEN_SHAREDCACHE)); | ||
| rb_define_const(mSqlite3Open, "PRIVATECACHE", INT2FIX(SQLITE_OPEN_PRIVATECACHE)); | ||
| rb_define_const(mSqlite3Open, "WAL", INT2FIX(SQLITE_OPEN_WAL)); | ||
| /* SQLITE_VERSION_NUMBER>=3007002 */ | ||
| rb_define_const(mSqlite3Open, "AUTOPROXY", INT2FIX(SQLITE_OPEN_AUTOPROXY)); | ||
| rb_define_const(mSqlite3Open, "SHAREDCACHE", INT2FIX(SQLITE_OPEN_SHAREDCACHE)); | ||
| rb_define_const(mSqlite3Open, "PRIVATECACHE", INT2FIX(SQLITE_OPEN_PRIVATECACHE)); | ||
| rb_define_const(mSqlite3Open, "WAL", INT2FIX(SQLITE_OPEN_WAL)); | ||
| #endif | ||
| #ifdef SQLITE_OPEN_URI | ||
| /* SQLITE_VERSION_NUMBER>=3007007 */ | ||
| rb_define_const(mSqlite3Open, "URI", INT2FIX(SQLITE_OPEN_URI)); | ||
| /* SQLITE_VERSION_NUMBER>=3007007 */ | ||
| rb_define_const(mSqlite3Open, "URI", INT2FIX(SQLITE_OPEN_URI)); | ||
| #endif | ||
| #ifdef SQLITE_OPEN_MEMORY | ||
| /* SQLITE_VERSION_NUMBER>=3007013 */ | ||
| rb_define_const(mSqlite3Open, "MEMORY", INT2FIX(SQLITE_OPEN_MEMORY)); | ||
| /* SQLITE_VERSION_NUMBER>=3007013 */ | ||
| rb_define_const(mSqlite3Open, "MEMORY", INT2FIX(SQLITE_OPEN_MEMORY)); | ||
| #endif | ||
@@ -134,35 +176,37 @@ } | ||
| RUBY_FUNC_EXPORTED | ||
| void Init_sqlite3_native(void) | ||
| void | ||
| Init_sqlite3_native(void) | ||
| { | ||
| /* | ||
| * SQLite3 is a wrapper around the popular database | ||
| * sqlite[http://sqlite.org]. | ||
| * | ||
| * For an example of usage, see SQLite3::Database. | ||
| */ | ||
| mSqlite3 = rb_define_module("SQLite3"); | ||
| /* | ||
| * SQLite3 is a wrapper around the popular database | ||
| * sqlite[http://sqlite.org]. | ||
| * | ||
| * For an example of usage, see SQLite3::Database. | ||
| */ | ||
| mSqlite3 = rb_define_module("SQLite3"); | ||
| /* A class for differentiating between strings and blobs, when binding them | ||
| * into statements. | ||
| */ | ||
| cSqlite3Blob = rb_define_class_under(mSqlite3, "Blob", rb_cString); | ||
| /* A class for differentiating between strings and blobs, when binding them | ||
| * into statements. | ||
| */ | ||
| cSqlite3Blob = rb_define_class_under(mSqlite3, "Blob", rb_cString); | ||
| /* Initialize the sqlite3 library */ | ||
| /* Initialize the sqlite3 library */ | ||
| #ifdef HAVE_SQLITE3_INITIALIZE | ||
| sqlite3_initialize(); | ||
| sqlite3_initialize(); | ||
| #endif | ||
| init_sqlite3_constants(); | ||
| init_sqlite3_database(); | ||
| init_sqlite3_statement(); | ||
| init_sqlite3_constants(); | ||
| init_sqlite3_database(); | ||
| init_sqlite3_statement(); | ||
| #ifdef HAVE_SQLITE3_BACKUP_INIT | ||
| init_sqlite3_backup(); | ||
| init_sqlite3_backup(); | ||
| #endif | ||
| rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); | ||
| rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); | ||
| rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); | ||
| rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); | ||
| rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); | ||
| rb_define_const(mSqlite3, "SQLITE_LOADED_VERSION", rb_str_new2(sqlite3_libversion())); | ||
| rb_define_singleton_method(mSqlite3, "sqlcipher?", using_sqlcipher, 0); | ||
| rb_define_singleton_method(mSqlite3, "libversion", libversion, 0); | ||
| rb_define_singleton_method(mSqlite3, "threadsafe", threadsafe_p, 0); | ||
| rb_define_singleton_method(mSqlite3, "status", rb_sqlite3_status, -1); | ||
| rb_define_const(mSqlite3, "SQLITE_VERSION", rb_str_new2(SQLITE_VERSION)); | ||
| rb_define_const(mSqlite3, "SQLITE_VERSION_NUMBER", INT2FIX(SQLITE_VERSION_NUMBER)); | ||
| rb_define_const(mSqlite3, "SQLITE_LOADED_VERSION", rb_str_new2(sqlite3_libversion())); | ||
| } |
+498
-282
@@ -9,72 +9,69 @@ #include <sqlite3_ruby.h> | ||
| static size_t statement_memsize(const void *data) | ||
| static void | ||
| statement_deallocate(void *data) | ||
| { | ||
| const sqlite3StmtRubyPtr s = (const sqlite3StmtRubyPtr)data; | ||
| // NB: can't account for s->st because the type is incomplete. | ||
| return sizeof(*s); | ||
| sqlite3StmtRubyPtr s = (sqlite3StmtRubyPtr)data; | ||
| if (s->st) { | ||
| sqlite3_finalize(s->st); | ||
| } | ||
| xfree(data); | ||
| } | ||
| static size_t | ||
| statement_memsize(const void *data) | ||
| { | ||
| const sqlite3StmtRubyPtr s = (const sqlite3StmtRubyPtr)data; | ||
| // NB: can't account for s->st because the type is incomplete. | ||
| return sizeof(*s); | ||
| } | ||
| static const rb_data_type_t statement_type = { | ||
| "SQLite3::Backup", | ||
| { | ||
| NULL, | ||
| RUBY_TYPED_DEFAULT_FREE, | ||
| statement_memsize, | ||
| }, | ||
| 0, | ||
| 0, | ||
| RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, | ||
| "SQLite3::Backup", | ||
| { | ||
| NULL, | ||
| statement_deallocate, | ||
| statement_memsize, | ||
| }, | ||
| 0, | ||
| 0, | ||
| RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, | ||
| }; | ||
| static VALUE allocate(VALUE klass) | ||
| static VALUE | ||
| allocate(VALUE klass) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3StmtRuby, &statement_type, ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| return TypedData_Make_Struct(klass, sqlite3StmtRuby, &statement_type, ctx); | ||
| } | ||
| /* call-seq: SQLite3::Statement.new(db, sql) | ||
| * | ||
| * Create a new statement attached to the given Database instance, and which | ||
| * encapsulates the given SQL text. If the text contains more than one | ||
| * statement (i.e., separated by semicolons), then the #remainder property | ||
| * will be set to the trailing text. | ||
| */ | ||
| static VALUE initialize(VALUE self, VALUE db, VALUE sql) | ||
| static VALUE | ||
| prepare(VALUE self, VALUE db, VALUE sql) | ||
| { | ||
| sqlite3RubyPtr db_ctx = sqlite3_database_unwrap(db); | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char *tail = NULL; | ||
| int status; | ||
| sqlite3RubyPtr db_ctx = sqlite3_database_unwrap(db); | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char *tail = NULL; | ||
| int status; | ||
| StringValue(sql); | ||
| StringValue(sql); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| if(!db_ctx->db) | ||
| rb_raise(rb_eArgError, "prepare called on a closed database"); | ||
| if(!UTF8_P(sql)) { | ||
| sql = rb_str_export_to_enc(sql, rb_utf8_encoding()); | ||
| } | ||
| #ifdef HAVE_SQLITE3_PREPARE_V2 | ||
| status = sqlite3_prepare_v2( | ||
| status = sqlite3_prepare_v2( | ||
| #else | ||
| status = sqlite3_prepare( | ||
| status = sqlite3_prepare( | ||
| #endif | ||
| db_ctx->db, | ||
| (const char *)StringValuePtr(sql), | ||
| (int)RSTRING_LEN(sql), | ||
| &ctx->st, | ||
| &tail | ||
| ); | ||
| db_ctx->db, | ||
| (const char *)StringValuePtr(sql), | ||
| (int)RSTRING_LEN(sql), | ||
| &ctx->st, | ||
| &tail | ||
| ); | ||
| CHECK(db_ctx->db, status); | ||
| CHECK(db_ctx->db, status); | ||
| timespecclear(&db_ctx->stmt_deadline); | ||
| rb_iv_set(self, "@connection", db); | ||
| rb_iv_set(self, "@remainder", rb_str_new2(tail)); | ||
| rb_iv_set(self, "@columns", Qnil); | ||
| rb_iv_set(self, "@types", Qnil); | ||
| return self; | ||
| return rb_str_new2(tail); | ||
| } | ||
@@ -87,14 +84,15 @@ | ||
| */ | ||
| static VALUE sqlite3_rb_close(VALUE self) | ||
| static VALUE | ||
| sqlite3_rb_close(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3_finalize(ctx->st); | ||
| ctx->st = NULL; | ||
| sqlite3_finalize(ctx->st); | ||
| ctx->st = NULL; | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -106,100 +104,101 @@ | ||
| */ | ||
| static VALUE closed_p(VALUE self) | ||
| static VALUE | ||
| closed_p(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| if(!ctx->st) return Qtrue; | ||
| if (!ctx->st) { return Qtrue; } | ||
| return Qfalse; | ||
| return Qfalse; | ||
| } | ||
| static VALUE step(VALUE self) | ||
| static VALUE | ||
| step(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| sqlite3_stmt *stmt; | ||
| int value, length; | ||
| VALUE list; | ||
| rb_encoding * internal_encoding; | ||
| sqlite3StmtRubyPtr ctx; | ||
| sqlite3_stmt *stmt; | ||
| int value, length; | ||
| VALUE list; | ||
| rb_encoding *internal_encoding; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| if(ctx->done_p) return Qnil; | ||
| if (ctx->done_p) { return Qnil; } | ||
| { | ||
| VALUE db = rb_iv_get(self, "@connection"); | ||
| rb_funcall(db, rb_intern("encoding"), 0); | ||
| internal_encoding = rb_default_internal_encoding(); | ||
| } | ||
| internal_encoding = rb_default_internal_encoding(); | ||
| stmt = ctx->st; | ||
| stmt = ctx->st; | ||
| value = sqlite3_step(stmt); | ||
| if (rb_errinfo() != Qnil) { | ||
| /* some user defined function was invoked as a callback during step and | ||
| * it raised an exception that has been suppressed until step returns. | ||
| * Now re-raise it. */ | ||
| VALUE exception = rb_errinfo(); | ||
| rb_set_errinfo(Qnil); | ||
| rb_exc_raise(exception); | ||
| } | ||
| value = sqlite3_step(stmt); | ||
| if (rb_errinfo() != Qnil) { | ||
| /* some user defined function was invoked as a callback during step and | ||
| * it raised an exception that has been suppressed until step returns. | ||
| * Now re-raise it. */ | ||
| VALUE exception = rb_errinfo(); | ||
| rb_set_errinfo(Qnil); | ||
| rb_exc_raise(exception); | ||
| } | ||
| length = sqlite3_column_count(stmt); | ||
| list = rb_ary_new2((long)length); | ||
| length = sqlite3_column_count(stmt); | ||
| list = rb_ary_new2((long)length); | ||
| switch(value) { | ||
| case SQLITE_ROW: | ||
| { | ||
| int i; | ||
| for(i = 0; i < length; i++) { | ||
| switch(sqlite3_column_type(stmt, i)) { | ||
| case SQLITE_INTEGER: | ||
| rb_ary_push(list, LL2NUM(sqlite3_column_int64(stmt, i))); | ||
| break; | ||
| case SQLITE_FLOAT: | ||
| rb_ary_push(list, rb_float_new(sqlite3_column_double(stmt, i))); | ||
| break; | ||
| case SQLITE_TEXT: | ||
| { | ||
| VALUE str = rb_str_new( | ||
| (const char *)sqlite3_column_text(stmt, i), | ||
| (long)sqlite3_column_bytes(stmt, i) | ||
| ); | ||
| rb_enc_associate_index(str, rb_utf8_encindex()); | ||
| if(internal_encoding) | ||
| str = rb_str_export_to_enc(str, internal_encoding); | ||
| rb_ary_push(list, str); | ||
| } | ||
| break; | ||
| case SQLITE_BLOB: | ||
| { | ||
| VALUE str = rb_str_new( | ||
| (const char *)sqlite3_column_blob(stmt, i), | ||
| (long)sqlite3_column_bytes(stmt, i) | ||
| ); | ||
| rb_ary_push(list, str); | ||
| } | ||
| break; | ||
| case SQLITE_NULL: | ||
| rb_ary_push(list, Qnil); | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "bad type"); | ||
| } | ||
| switch (value) { | ||
| case SQLITE_ROW: { | ||
| int i; | ||
| for (i = 0; i < length; i++) { | ||
| VALUE val; | ||
| switch (sqlite3_column_type(stmt, i)) { | ||
| case SQLITE_INTEGER: | ||
| val = LL2NUM(sqlite3_column_int64(stmt, i)); | ||
| break; | ||
| case SQLITE_FLOAT: | ||
| val = rb_float_new(sqlite3_column_double(stmt, i)); | ||
| break; | ||
| case SQLITE_TEXT: { | ||
| val = rb_utf8_str_new( | ||
| (const char *)sqlite3_column_text(stmt, i), | ||
| (long)sqlite3_column_bytes(stmt, i) | ||
| ); | ||
| if (internal_encoding) { | ||
| val = rb_str_export_to_enc(val, internal_encoding); | ||
| } | ||
| rb_obj_freeze(val); | ||
| } | ||
| break; | ||
| case SQLITE_BLOB: { | ||
| val = rb_str_new( | ||
| (const char *)sqlite3_column_blob(stmt, i), | ||
| (long)sqlite3_column_bytes(stmt, i) | ||
| ); | ||
| rb_obj_freeze(val); | ||
| } | ||
| break; | ||
| case SQLITE_NULL: | ||
| val = Qnil; | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "bad type"); | ||
| } | ||
| rb_ary_store(list, (long)i, val); | ||
| } | ||
| } | ||
| } | ||
| break; | ||
| case SQLITE_DONE: | ||
| ctx->done_p = 1; | ||
| return Qnil; | ||
| break; | ||
| default: | ||
| sqlite3_reset(stmt); | ||
| ctx->done_p = 0; | ||
| CHECK(sqlite3_db_handle(ctx->st), value); | ||
| } | ||
| break; | ||
| case SQLITE_DONE: | ||
| ctx->done_p = 1; | ||
| return Qnil; | ||
| break; | ||
| default: | ||
| sqlite3_reset(stmt); | ||
| ctx->done_p = 0; | ||
| CHECK(sqlite3_db_handle(ctx->st), value); | ||
| } | ||
| return list; | ||
| rb_obj_freeze(list); | ||
| return list; | ||
| } | ||
@@ -215,87 +214,89 @@ | ||
| */ | ||
| static VALUE bind_param(VALUE self, VALUE key, VALUE value) | ||
| static VALUE | ||
| bind_param(VALUE self, VALUE key, VALUE value) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| int status; | ||
| int index; | ||
| sqlite3StmtRubyPtr ctx; | ||
| int status; | ||
| int index; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| switch(TYPE(key)) { | ||
| case T_SYMBOL: | ||
| key = rb_funcall(key, rb_intern("to_s"), 0); | ||
| case T_STRING: | ||
| if(RSTRING_PTR(key)[0] != ':') key = rb_str_plus(rb_str_new2(":"), key); | ||
| index = sqlite3_bind_parameter_index(ctx->st, StringValuePtr(key)); | ||
| break; | ||
| default: | ||
| index = (int)NUM2INT(key); | ||
| } | ||
| switch (TYPE(key)) { | ||
| case T_SYMBOL: | ||
| key = rb_funcall(key, rb_intern("to_s"), 0); | ||
| case T_STRING: | ||
| if (RSTRING_PTR(key)[0] != ':') { key = rb_str_plus(rb_str_new2(":"), key); } | ||
| index = sqlite3_bind_parameter_index(ctx->st, StringValuePtr(key)); | ||
| break; | ||
| default: | ||
| index = (int)NUM2INT(key); | ||
| } | ||
| if(index == 0) | ||
| rb_raise(rb_path2class("SQLite3::Exception"), "no such bind parameter"); | ||
| if (index == 0) { | ||
| rb_raise(rb_path2class("SQLite3::Exception"), "no such bind parameter"); | ||
| } | ||
| switch(TYPE(value)) { | ||
| case T_STRING: | ||
| if(CLASS_OF(value) == cSqlite3Blob | ||
| || rb_enc_get_index(value) == rb_ascii8bit_encindex() | ||
| ) { | ||
| status = sqlite3_bind_blob( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| switch (TYPE(value)) { | ||
| case T_STRING: | ||
| if (CLASS_OF(value) == cSqlite3Blob | ||
| || rb_enc_get_index(value) == rb_ascii8bit_encindex() | ||
| ) { | ||
| status = sqlite3_bind_blob( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| if (UTF16_LE_P(value) || UTF16_BE_P(value)) { | ||
| status = sqlite3_bind_text16( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| if (!UTF8_P(value) || !USASCII_P(value)) { | ||
| value = rb_str_encode(value, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil); | ||
| } | ||
| status = sqlite3_bind_text( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| if (UTF16_LE_P(value) || UTF16_BE_P(value)) { | ||
| status = sqlite3_bind_text16( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } else { | ||
| if (!UTF8_P(value) || !USASCII_P(value)) { | ||
| value = rb_str_encode(value, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil); | ||
| } | ||
| status = sqlite3_bind_text( | ||
| ctx->st, | ||
| index, | ||
| (const char *)StringValuePtr(value), | ||
| (int)RSTRING_LEN(value), | ||
| SQLITE_TRANSIENT | ||
| ); | ||
| } | ||
| } | ||
| break; | ||
| case T_BIGNUM: { | ||
| sqlite3_int64 num64; | ||
| if (bignum_to_int64(value, &num64)) { | ||
| status = sqlite3_bind_int64(ctx->st, index, num64); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| break; | ||
| case T_BIGNUM: { | ||
| sqlite3_int64 num64; | ||
| if (bignum_to_int64(value, &num64)) { | ||
| status = sqlite3_bind_int64(ctx->st, index, num64); | ||
| break; | ||
| } | ||
| case T_FLOAT: | ||
| status = sqlite3_bind_double(ctx->st, index, NUM2DBL(value)); | ||
| break; | ||
| case T_FIXNUM: | ||
| status = sqlite3_bind_int64(ctx->st, index, (sqlite3_int64)FIX2LONG(value)); | ||
| break; | ||
| case T_NIL: | ||
| status = sqlite3_bind_null(ctx->st, index); | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "can't prepare %s", | ||
| rb_class2name(CLASS_OF(value))); | ||
| break; | ||
| } | ||
| case T_FLOAT: | ||
| status = sqlite3_bind_double(ctx->st, index, NUM2DBL(value)); | ||
| break; | ||
| case T_FIXNUM: | ||
| status = sqlite3_bind_int64(ctx->st, index, (sqlite3_int64)FIX2LONG(value)); | ||
| break; | ||
| case T_NIL: | ||
| status = sqlite3_bind_null(ctx->st, index); | ||
| break; | ||
| default: | ||
| rb_raise(rb_eRuntimeError, "can't prepare %s", | ||
| rb_class2name(CLASS_OF(value))); | ||
| break; | ||
| } | ||
| CHECK(sqlite3_db_handle(ctx->st), status); | ||
| CHECK(sqlite3_db_handle(ctx->st), status); | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -308,14 +309,15 @@ | ||
| */ | ||
| static VALUE reset_bang(VALUE self) | ||
| static VALUE | ||
| reset_bang(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3_reset(ctx->st); | ||
| sqlite3_reset(ctx->st); | ||
| ctx->done_p = 0; | ||
| ctx->done_p = 0; | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -328,14 +330,15 @@ | ||
| */ | ||
| static VALUE clear_bindings_bang(VALUE self) | ||
| static VALUE | ||
| clear_bindings_bang(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3_clear_bindings(ctx->st); | ||
| sqlite3_clear_bindings(ctx->st); | ||
| ctx->done_p = 0; | ||
| ctx->done_p = 0; | ||
| return self; | ||
| return self; | ||
| } | ||
@@ -347,9 +350,10 @@ | ||
| */ | ||
| static VALUE done_p(VALUE self) | ||
| static VALUE | ||
| done_p(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| if(ctx->done_p) return Qtrue; | ||
| return Qfalse; | ||
| if (ctx->done_p) { return Qtrue; } | ||
| return Qfalse; | ||
| } | ||
@@ -361,11 +365,27 @@ | ||
| */ | ||
| static VALUE column_count(VALUE self) | ||
| static VALUE | ||
| column_count(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| return INT2NUM(sqlite3_column_count(ctx->st)); | ||
| return INT2NUM(sqlite3_column_count(ctx->st)); | ||
| } | ||
| #if HAVE_RB_ENC_INTERNED_STR_CSTR | ||
| static VALUE | ||
| interned_utf8_cstr(const char *str) | ||
| { | ||
| return rb_enc_interned_str_cstr(str, rb_utf8_encoding()); | ||
| } | ||
| #else | ||
| static VALUE | ||
| interned_utf8_cstr(const char *str) | ||
| { | ||
| VALUE rb_str = rb_utf8_str_new_cstr(str); | ||
| return rb_funcall(rb_str, rb_intern("-@"), 0); | ||
| } | ||
| #endif | ||
| /* call-seq: stmt.column_name(index) | ||
@@ -375,14 +395,19 @@ * | ||
| */ | ||
| static VALUE column_name(VALUE self, VALUE index) | ||
| static VALUE | ||
| column_name(VALUE self, VALUE index) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char * name; | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char *name; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| name = sqlite3_column_name(ctx->st, (int)NUM2INT(index)); | ||
| name = sqlite3_column_name(ctx->st, (int)NUM2INT(index)); | ||
| if(name) return SQLITE3_UTF8_STR_NEW2(name); | ||
| return Qnil; | ||
| VALUE ret = Qnil; | ||
| if (name) { | ||
| ret = interned_utf8_cstr(name); | ||
| } | ||
| return ret; | ||
| } | ||
@@ -394,14 +419,15 @@ | ||
| */ | ||
| static VALUE column_decltype(VALUE self, VALUE index) | ||
| static VALUE | ||
| column_decltype(VALUE self, VALUE index) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char * name; | ||
| sqlite3StmtRubyPtr ctx; | ||
| const char *name; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index)); | ||
| name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index)); | ||
| if(name) return rb_str_new2(name); | ||
| return Qnil; | ||
| if (name) { return rb_str_new2(name); } | ||
| return Qnil; | ||
| } | ||
@@ -413,11 +439,156 @@ | ||
| */ | ||
| static VALUE bind_parameter_count(VALUE self) | ||
| static VALUE | ||
| bind_parameter_count(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| return INT2NUM(sqlite3_bind_parameter_count(ctx->st)); | ||
| return INT2NUM(sqlite3_bind_parameter_count(ctx->st)); | ||
| } | ||
| enum stmt_stat_sym { | ||
| stmt_stat_sym_fullscan_steps, | ||
| stmt_stat_sym_sorts, | ||
| stmt_stat_sym_autoindexes, | ||
| stmt_stat_sym_vm_steps, | ||
| #ifdef SQLITE_STMTSTATUS_REPREPARE | ||
| stmt_stat_sym_reprepares, | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_RUN | ||
| stmt_stat_sym_runs, | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_MISS | ||
| stmt_stat_sym_filter_misses, | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_HIT | ||
| stmt_stat_sym_filter_hits, | ||
| #endif | ||
| stmt_stat_sym_last | ||
| }; | ||
| static VALUE stmt_stat_symbols[stmt_stat_sym_last]; | ||
| static void | ||
| setup_stmt_stat_symbols(void) | ||
| { | ||
| if (stmt_stat_symbols[0] == 0) { | ||
| #define S(s) stmt_stat_symbols[stmt_stat_sym_##s] = ID2SYM(rb_intern_const(#s)) | ||
| S(fullscan_steps); | ||
| S(sorts); | ||
| S(autoindexes); | ||
| S(vm_steps); | ||
| #ifdef SQLITE_STMTSTATUS_REPREPARE | ||
| S(reprepares); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_RUN | ||
| S(runs); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_MISS | ||
| S(filter_misses); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_HIT | ||
| S(filter_hits); | ||
| #endif | ||
| #undef S | ||
| } | ||
| } | ||
| static size_t | ||
| stmt_stat_internal(VALUE hash_or_sym, sqlite3_stmt *stmt) | ||
| { | ||
| VALUE hash = Qnil, key = Qnil; | ||
| setup_stmt_stat_symbols(); | ||
| if (RB_TYPE_P(hash_or_sym, T_HASH)) { | ||
| hash = hash_or_sym; | ||
| } else if (SYMBOL_P(hash_or_sym)) { | ||
| key = hash_or_sym; | ||
| } else { | ||
| rb_raise(rb_eTypeError, "non-hash or symbol argument"); | ||
| } | ||
| #define SET(name, stat_type) \ | ||
| if (key == stmt_stat_symbols[stmt_stat_sym_##name]) \ | ||
| return sqlite3_stmt_status(stmt, stat_type, 0); \ | ||
| else if (hash != Qnil) \ | ||
| rb_hash_aset(hash, stmt_stat_symbols[stmt_stat_sym_##name], SIZET2NUM(sqlite3_stmt_status(stmt, stat_type, 0))); | ||
| SET(fullscan_steps, SQLITE_STMTSTATUS_FULLSCAN_STEP); | ||
| SET(sorts, SQLITE_STMTSTATUS_SORT); | ||
| SET(autoindexes, SQLITE_STMTSTATUS_AUTOINDEX); | ||
| SET(vm_steps, SQLITE_STMTSTATUS_VM_STEP); | ||
| #ifdef SQLITE_STMTSTATUS_REPREPARE | ||
| SET(reprepares, SQLITE_STMTSTATUS_REPREPARE); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_RUN | ||
| SET(runs, SQLITE_STMTSTATUS_RUN); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_MISS | ||
| SET(filter_misses, SQLITE_STMTSTATUS_FILTER_MISS); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_FILTER_HIT | ||
| SET(filter_hits, SQLITE_STMTSTATUS_FILTER_HIT); | ||
| #endif | ||
| #undef SET | ||
| if (!NIL_P(key)) { /* matched key should return above */ | ||
| rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key)); | ||
| } | ||
| return 0; | ||
| } | ||
| /* call-seq: stmt.stats_as_hash(hash) | ||
| * | ||
| * Returns a Hash containing information about the statement. | ||
| */ | ||
| static VALUE | ||
| stats_as_hash(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| VALUE arg = rb_hash_new(); | ||
| stmt_stat_internal(arg, ctx->st); | ||
| return arg; | ||
| } | ||
| /* call-seq: stmt.stmt_stat(hash_or_key) | ||
| * | ||
| * Returns a Hash containing information about the statement. | ||
| */ | ||
| static VALUE | ||
| stat_for(VALUE self, VALUE key) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| if (SYMBOL_P(key)) { | ||
| size_t value = stmt_stat_internal(key, ctx->st); | ||
| return SIZET2NUM(value); | ||
| } else { | ||
| rb_raise(rb_eTypeError, "non-symbol given"); | ||
| } | ||
| } | ||
| #ifdef SQLITE_STMTSTATUS_MEMUSED | ||
| /* call-seq: stmt.memory_used | ||
| * | ||
| * Return the approximate number of bytes of heap memory used to store the prepared statement | ||
| */ | ||
| static VALUE | ||
| memused(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| return INT2NUM(sqlite3_stmt_status(ctx->st, SQLITE_STMTSTATUS_MEMUSED, 0)); | ||
| } | ||
| #endif | ||
| #ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME | ||
@@ -429,10 +600,11 @@ | ||
| */ | ||
| static VALUE database_name(VALUE self, VALUE index) | ||
| static VALUE | ||
| database_name(VALUE self, VALUE index) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| return SQLITE3_UTF8_STR_NEW2( | ||
| sqlite3_column_database_name(ctx->st, NUM2INT(index))); | ||
| return SQLITE3_UTF8_STR_NEW2( | ||
| sqlite3_column_database_name(ctx->st, NUM2INT(index))); | ||
| } | ||
@@ -442,23 +614,67 @@ | ||
| void init_sqlite3_statement(void) | ||
| /* call-seq: stmt.sql | ||
| * | ||
| * Returns the SQL statement used to create this prepared statement | ||
| */ | ||
| static VALUE | ||
| get_sql(VALUE self) | ||
| { | ||
| cSqlite3Statement = rb_define_class_under(mSqlite3, "Statement", rb_cObject); | ||
| sqlite3StmtRubyPtr ctx; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| rb_define_alloc_func(cSqlite3Statement, allocate); | ||
| rb_define_method(cSqlite3Statement, "initialize", initialize, 2); | ||
| rb_define_method(cSqlite3Statement, "close", sqlite3_rb_close, 0); | ||
| rb_define_method(cSqlite3Statement, "closed?", closed_p, 0); | ||
| rb_define_method(cSqlite3Statement, "bind_param", bind_param, 2); | ||
| rb_define_method(cSqlite3Statement, "reset!", reset_bang, 0); | ||
| rb_define_method(cSqlite3Statement, "clear_bindings!", clear_bindings_bang, 0); | ||
| rb_define_method(cSqlite3Statement, "step", step, 0); | ||
| rb_define_method(cSqlite3Statement, "done?", done_p, 0); | ||
| rb_define_method(cSqlite3Statement, "column_count", column_count, 0); | ||
| rb_define_method(cSqlite3Statement, "column_name", column_name, 1); | ||
| rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1); | ||
| rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0); | ||
| return rb_obj_freeze(SQLITE3_UTF8_STR_NEW2(sqlite3_sql(ctx->st))); | ||
| } | ||
| /* call-seq: stmt.expanded_sql | ||
| * | ||
| * Returns the SQL statement used to create this prepared statement, but | ||
| * with bind parameters substituted in to the statement. | ||
| */ | ||
| static VALUE | ||
| get_expanded_sql(VALUE self) | ||
| { | ||
| sqlite3StmtRubyPtr ctx; | ||
| char *expanded_sql; | ||
| VALUE rb_expanded_sql; | ||
| TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx); | ||
| REQUIRE_OPEN_STMT(ctx); | ||
| expanded_sql = sqlite3_expanded_sql(ctx->st); | ||
| rb_expanded_sql = rb_obj_freeze(SQLITE3_UTF8_STR_NEW2(expanded_sql)); | ||
| sqlite3_free(expanded_sql); | ||
| return rb_expanded_sql; | ||
| } | ||
| void | ||
| init_sqlite3_statement(void) | ||
| { | ||
| cSqlite3Statement = rb_define_class_under(mSqlite3, "Statement", rb_cObject); | ||
| rb_define_alloc_func(cSqlite3Statement, allocate); | ||
| rb_define_method(cSqlite3Statement, "close", sqlite3_rb_close, 0); | ||
| rb_define_method(cSqlite3Statement, "closed?", closed_p, 0); | ||
| rb_define_method(cSqlite3Statement, "bind_param", bind_param, 2); | ||
| rb_define_method(cSqlite3Statement, "reset!", reset_bang, 0); | ||
| rb_define_method(cSqlite3Statement, "clear_bindings!", clear_bindings_bang, 0); | ||
| rb_define_method(cSqlite3Statement, "step", step, 0); | ||
| rb_define_method(cSqlite3Statement, "done?", done_p, 0); | ||
| rb_define_method(cSqlite3Statement, "column_count", column_count, 0); | ||
| rb_define_method(cSqlite3Statement, "column_name", column_name, 1); | ||
| rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1); | ||
| rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0); | ||
| rb_define_method(cSqlite3Statement, "sql", get_sql, 0); | ||
| rb_define_method(cSqlite3Statement, "expanded_sql", get_expanded_sql, 0); | ||
| #ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME | ||
| rb_define_method(cSqlite3Statement, "database_name", database_name, 1); | ||
| rb_define_method(cSqlite3Statement, "database_name", database_name, 1); | ||
| #endif | ||
| #ifdef SQLITE_STMTSTATUS_MEMUSED | ||
| rb_define_method(cSqlite3Statement, "memused", memused, 0); | ||
| #endif | ||
| rb_define_private_method(cSqlite3Statement, "prepare", prepare, 2); | ||
| rb_define_private_method(cSqlite3Statement, "stats_as_hash", stats_as_hash, 0); | ||
| rb_define_private_method(cSqlite3Statement, "stat_for", stat_for, 1); | ||
| } |
@@ -7,8 +7,8 @@ #ifndef SQLITE3_STATEMENT_RUBY | ||
| struct _sqlite3StmtRuby { | ||
| sqlite3_stmt *st; | ||
| int done_p; | ||
| sqlite3_stmt *st; | ||
| int done_p; | ||
| }; | ||
| typedef struct _sqlite3StmtRuby sqlite3StmtRuby; | ||
| typedef sqlite3StmtRuby * sqlite3StmtRubyPtr; | ||
| typedef sqlite3StmtRuby *sqlite3StmtRubyPtr; | ||
@@ -15,0 +15,0 @@ void init_sqlite3_statement(); |
+0
-43
@@ -292,45 +292,2 @@ | ||
| ## I'd like the values from a query to be the correct types, instead of String. | ||
| You can turn on "type translation" by setting `Database#type_translation` to | ||
| true: | ||
| ```ruby | ||
| db.type_translation = true | ||
| db.execute( "select * from table" ) do |row| | ||
| p row | ||
| end | ||
| ``` | ||
| By doing this, each return value for each row will be translated to its | ||
| correct type, based on its declared column type. | ||
| You can even declare your own translation routines, if (for example) you are | ||
| using an SQL type that is not handled by default: | ||
| ```ruby | ||
| # assume "objects" table has the following schema: | ||
| # create table objects ( | ||
| # name varchar2(20), | ||
| # thing object | ||
| # ) | ||
| db.type_translation = true | ||
| db.translator.add_translator( "object" ) do |type, value| | ||
| db.decode( value ) | ||
| end | ||
| h = { :one=>:two, "three"=>"four", 5=>6 } | ||
| dump = db.encode( h ) | ||
| db.execute( "insert into objects values ( ?, ? )", "bob", dump ) | ||
| obj = db.get_first_value( "select thing from objects where name='bob'" ) | ||
| p obj == h | ||
| ``` | ||
| ## How do I insert binary data into the database? | ||
@@ -337,0 +294,0 @@ |
+13
-5
@@ -10,12 +10,20 @@ | ||
| In v1.5.0 and later, native (precompiled) gems are available for recent Ruby versions on these platforms: | ||
| In v2.0.0 and later, native (precompiled) gems are available for recent Ruby versions on these platforms: | ||
| - `aarch64-linux` (requires: glibc >= 2.29) | ||
| - `arm-linux` (requires: glibc >= 2.29) | ||
| - `aarch64-linux-gnu` (requires: glibc >= 2.29) | ||
| - `aarch64-linux-musl` | ||
| - `arm-linux-gnu` (requires: glibc >= 2.29) | ||
| - `arm-linux-musl` | ||
| - `arm64-darwin` | ||
| - `x64-mingw32` / `x64-mingw-ucrt` | ||
| - `x86-linux` (requires: glibc >= 2.17) | ||
| - `x86-linux-gnu` (requires: glibc >= 2.17) | ||
| - `x86-linux-musl` | ||
| - `x86_64-darwin` | ||
| - `x86_64-linux` (requires: glibc >= 2.17) | ||
| - `x86_64-linux-gnu` (requires: glibc >= 2.17) | ||
| - `x86_64-linux-musl` | ||
| ⚠ Ruby 3.0 linux users must use Rubygems >= 3.3.22 in order to use these gems. | ||
| ⚠ Musl linux users should update to Bundler >= 2.5.6 to avoid https://github.com/rubygems/rubygems/issues/7432 | ||
| If you are using one of these Ruby versions on one of these platforms, the native gem is the recommended way to install sqlite3-ruby. | ||
@@ -22,0 +30,0 @@ |
+6
-4
@@ -6,11 +6,13 @@ # support multiple ruby version (fat binaries under windows) | ||
| rescue LoadError | ||
| require 'sqlite3/sqlite3_native' | ||
| require "sqlite3/sqlite3_native" | ||
| end | ||
| require 'sqlite3/database' | ||
| require 'sqlite3/version' | ||
| require "sqlite3/database" | ||
| require "sqlite3/version" | ||
| module SQLite3 | ||
| # Was sqlite3 compiled with thread safety on? | ||
| def self.threadsafe?; threadsafe > 0; end | ||
| def self.threadsafe? | ||
| threadsafe > 0 | ||
| end | ||
| end |
+169
-45
@@ -1,50 +0,174 @@ | ||
| module SQLite3 ; module Constants | ||
| module SQLite3 | ||
| module Constants | ||
| # | ||
| # CAPI3REF: Text Encodings | ||
| # | ||
| # These constant define integer codes that represent the various | ||
| # text encodings supported by SQLite. | ||
| # | ||
| module TextRep | ||
| # IMP: R-37514-35566 | ||
| UTF8 = 1 | ||
| # IMP: R-03371-37637 | ||
| UTF16LE = 2 | ||
| # IMP: R-51971-34154 | ||
| UTF16BE = 3 | ||
| # Use native byte order | ||
| UTF16 = 4 | ||
| # Deprecated | ||
| ANY = 5 | ||
| # sqlite3_create_collation only | ||
| DETERMINISTIC = 0x800 | ||
| end | ||
| module TextRep | ||
| UTF8 = 1 | ||
| UTF16LE = 2 | ||
| UTF16BE = 3 | ||
| UTF16 = 4 | ||
| ANY = 5 | ||
| DETERMINISTIC = 0x800 | ||
| end | ||
| # | ||
| # CAPI3REF: Fundamental Datatypes | ||
| # | ||
| # ^(Every value in SQLite has one of five fundamental datatypes: | ||
| # | ||
| # <ul> | ||
| # <li> 64-bit signed integer | ||
| # <li> 64-bit IEEE floating point number | ||
| # <li> string | ||
| # <li> BLOB | ||
| # <li> NULL | ||
| # </ul>)^ | ||
| # | ||
| # These constants are codes for each of those types. | ||
| # | ||
| module ColumnType | ||
| INTEGER = 1 | ||
| FLOAT = 2 | ||
| TEXT = 3 | ||
| BLOB = 4 | ||
| NULL = 5 | ||
| end | ||
| module ColumnType | ||
| INTEGER = 1 | ||
| FLOAT = 2 | ||
| TEXT = 3 | ||
| BLOB = 4 | ||
| NULL = 5 | ||
| end | ||
| # | ||
| # CAPI3REF: Result Codes | ||
| # | ||
| # Many SQLite functions return an integer result code from the set shown | ||
| # here in order to indicate success or failure. | ||
| # | ||
| # New error codes may be added in future versions of SQLite. | ||
| # | ||
| module ErrorCode | ||
| # Successful result | ||
| OK = 0 | ||
| # SQL error or missing database | ||
| ERROR = 1 | ||
| # An internal logic error in SQLite | ||
| INTERNAL = 2 | ||
| # Access permission denied | ||
| PERM = 3 | ||
| # Callback routine requested an abort | ||
| ABORT = 4 | ||
| # The database file is locked | ||
| BUSY = 5 | ||
| # A table in the database is locked | ||
| LOCKED = 6 | ||
| # A malloc() failed | ||
| NOMEM = 7 | ||
| # Attempt to write a readonly database | ||
| READONLY = 8 | ||
| # Operation terminated by sqlite_interrupt() | ||
| INTERRUPT = 9 | ||
| # Some kind of disk I/O error occurred | ||
| IOERR = 10 | ||
| # The database disk image is malformed | ||
| CORRUPT = 11 | ||
| # (Internal Only) Table or record not found | ||
| NOTFOUND = 12 | ||
| # Insertion failed because database is full | ||
| FULL = 13 | ||
| # Unable to open the database file | ||
| CANTOPEN = 14 | ||
| # Database lock protocol error | ||
| PROTOCOL = 15 | ||
| # (Internal Only) Database table is empty | ||
| EMPTY = 16 | ||
| # The database schema changed | ||
| SCHEMA = 17 | ||
| # Too much data for one row of a table | ||
| TOOBIG = 18 | ||
| # Abort due to constraint violation | ||
| CONSTRAINT = 19 | ||
| # Data type mismatch | ||
| MISMATCH = 20 | ||
| # Library used incorrectly | ||
| MISUSE = 21 | ||
| # Uses OS features not supported on host | ||
| NOLFS = 22 | ||
| # Authorization denied | ||
| AUTH = 23 | ||
| # Not used | ||
| FORMAT = 24 | ||
| # 2nd parameter to sqlite3_bind out of range | ||
| RANGE = 25 | ||
| # File opened that is not a database file | ||
| NOTADB = 26 | ||
| # Notifications from sqlite3_log() | ||
| NOTICE = 27 | ||
| # Warnings from sqlite3_log() | ||
| WARNING = 28 | ||
| # sqlite_step() has another row ready | ||
| ROW = 100 | ||
| # sqlite_step() has finished executing | ||
| DONE = 101 | ||
| end | ||
| module ErrorCode | ||
| OK = 0 # Successful result | ||
| ERROR = 1 # SQL error or missing database | ||
| INTERNAL = 2 # An internal logic error in SQLite | ||
| PERM = 3 # Access permission denied | ||
| ABORT = 4 # Callback routine requested an abort | ||
| BUSY = 5 # The database file is locked | ||
| LOCKED = 6 # A table in the database is locked | ||
| NOMEM = 7 # A malloc() failed | ||
| READONLY = 8 # Attempt to write a readonly database | ||
| INTERRUPT = 9 # Operation terminated by sqlite_interrupt() | ||
| IOERR = 10 # Some kind of disk I/O error occurred | ||
| CORRUPT = 11 # The database disk image is malformed | ||
| NOTFOUND = 12 # (Internal Only) Table or record not found | ||
| FULL = 13 # Insertion failed because database is full | ||
| CANTOPEN = 14 # Unable to open the database file | ||
| PROTOCOL = 15 # Database lock protocol error | ||
| EMPTY = 16 # (Internal Only) Database table is empty | ||
| SCHEMA = 17 # The database schema changed | ||
| TOOBIG = 18 # Too much data for one row of a table | ||
| CONSTRAINT = 19 # Abort due to constraint violation | ||
| MISMATCH = 20 # Data type mismatch | ||
| MISUSE = 21 # Library used incorrectly | ||
| NOLFS = 22 # Uses OS features not supported on host | ||
| AUTH = 23 # Authorization denied | ||
| # | ||
| # CAPI3REF: Status Parameters | ||
| # | ||
| # These integer constants designate various run-time status parameters | ||
| # that can be returned by SQLite3.status | ||
| # | ||
| module Status | ||
| # This parameter is the current amount of memory checked out using sqlite3_malloc(), either | ||
| # directly or indirectly. The figure includes calls made to sqlite3_malloc() by the | ||
| # application and internal memory usage by the SQLite library. Auxiliary page-cache memory | ||
| # controlled by SQLITE_CONFIG_PAGECACHE is not included in this parameter. The amount returned | ||
| # is the sum of the allocation sizes as reported by the xSize method in sqlite3_mem_methods. | ||
| MEMORY_USED = 0 | ||
| ROW = 100 # sqlite_step() has another row ready | ||
| DONE = 101 # sqlite_step() has finished executing | ||
| # This parameter returns the number of pages used out of the pagecache memory allocator that | ||
| # was configured using SQLITE_CONFIG_PAGECACHE. The value returned is in pages, not in bytes. | ||
| PAGECACHE_USED = 1 | ||
| # This parameter returns the number of bytes of page cache allocation which could not be | ||
| # satisfied by the SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to | ||
| # sqlite3_malloc(). The returned value includes allocations that overflowed because they where | ||
| # too large (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and | ||
| # allocations that overflowed because no space was left in the page cache. | ||
| PAGECACHE_OVERFLOW = 2 | ||
| # NOT USED | ||
| SCRATCH_USED = 3 | ||
| # NOT USED | ||
| SCRATCH_OVERFLOW = 4 | ||
| # This parameter records the largest memory allocation request handed to sqlite3_malloc() or | ||
| # sqlite3_realloc() (or their internal equivalents). Only the value returned in the | ||
| # *pHighwater parameter to sqlite3_status() is of interest. The value written into the | ||
| # *pCurrent parameter is undefined. | ||
| MALLOC_SIZE = 5 | ||
| # The *pHighwater parameter records the deepest parser stack. The *pCurrent value is | ||
| # undefined. The *pHighwater value is only meaningful if SQLite is compiled with | ||
| # YYTRACKMAXSTACKDEPTH. | ||
| PARSER_STACK = 6 | ||
| # This parameter records the largest memory allocation request handed to the pagecache memory | ||
| # allocator. Only the value returned in the *pHighwater parameter to sqlite3_status() is of | ||
| # interest. The value written into the *pCurrent parameter is undefined. | ||
| PAGECACHE_SIZE = 7 | ||
| # NOT USED | ||
| SCRATCH_SIZE = 8 | ||
| # This parameter records the number of separate memory allocations currently checked out. | ||
| MALLOC_COUNT = 9 | ||
| end | ||
| end | ||
| end ; end | ||
| end |
+105
-165
@@ -1,10 +0,8 @@ | ||
| require 'sqlite3/constants' | ||
| require 'sqlite3/errors' | ||
| require 'sqlite3/pragmas' | ||
| require 'sqlite3/statement' | ||
| require 'sqlite3/translator' | ||
| require 'sqlite3/value' | ||
| require "sqlite3/constants" | ||
| require "sqlite3/errors" | ||
| require "sqlite3/pragmas" | ||
| require "sqlite3/statement" | ||
| require "sqlite3/value" | ||
| module SQLite3 | ||
| # The Database class encapsulates a single connection to a SQLite3 database. | ||
@@ -35,2 +33,10 @@ # Its usage is very straightforward: | ||
| # hashes, then the results will all be indexible by field name. | ||
| # | ||
| # Thread safety: | ||
| # | ||
| # When `SQLite3.threadsafe?` returns true, it is safe to share instances of | ||
| # the database class among threads without adding specific locking. Other | ||
| # object instances may require applications to provide their own locks if | ||
| # they are to be shared among threads. Please see the README.md for more | ||
| # information. | ||
| class Database | ||
@@ -42,7 +48,6 @@ attr_reader :collations | ||
| class << self | ||
| # Without block works exactly as new. | ||
| # With block, like new closes the database at the end, but unlike new | ||
| # returns the result of the block instead of the database instance. | ||
| def open( *args ) | ||
| def open(*args) | ||
| database = new(*args) | ||
@@ -64,6 +69,5 @@ | ||
| # single-quote characters. The modified string is returned. | ||
| def quote( string ) | ||
| string.gsub( /'/, "''" ) | ||
| def quote(string) | ||
| string.gsub("'", "''") | ||
| end | ||
| end | ||
@@ -91,3 +95,2 @@ | ||
| # - +:results_as_hash+: boolean (default false), return rows as hashes instead of arrays | ||
| # - +:type_translation+: boolean (default false), enable type translation | ||
| # - +:default_transaction_mode+: one of +:deferred+ (default), +:immediate+, or +:exclusive+. If a mode is not specified in a call to #transaction, this will be the default transaction mode. | ||
@@ -127,12 +130,10 @@ # | ||
| @tracefunc = nil | ||
| @authorizer = nil | ||
| @encoding = nil | ||
| @busy_handler = nil | ||
| @collations = {} | ||
| @functions = {} | ||
| @results_as_hash = options[:results_as_hash] | ||
| @type_translation = options[:type_translation] | ||
| @type_translator = make_type_translator @type_translation | ||
| @readonly = mode & Constants::Open::READONLY != 0 | ||
| @tracefunc = nil | ||
| @authorizer = nil | ||
| @busy_handler = nil | ||
| @progress_handler = nil | ||
| @collations = {} | ||
| @functions = {} | ||
| @results_as_hash = options[:results_as_hash] | ||
| @readonly = mode & Constants::Open::READONLY != 0 | ||
| @default_transaction_mode = options[:default_transaction_mode] || :deferred | ||
@@ -149,21 +150,9 @@ | ||
| def type_translation= value # :nodoc: | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Database#type_translation=` which is deprecated and will be removed in version 2.0.0. | ||
| eowarn | ||
| @type_translator = make_type_translator value | ||
| @type_translation = value | ||
| # call-seq: db.encoding | ||
| # | ||
| # Fetch the encoding set on this database | ||
| def encoding | ||
| prepare("PRAGMA encoding") { |stmt| Encoding.find(stmt.first.first) } | ||
| end | ||
| attr_reader :type_translation # :nodoc: | ||
| # Return the type translator employed by this database instance. Each | ||
| # database instance has its own type translator; this allows for different | ||
| # type handlers to be installed in each instance without affecting other | ||
| # instances. Furthermore, the translators are instantiated lazily, so that | ||
| # if a database does not use type translation, it will not be burdened by | ||
| # the overhead of a useless type translator. (See the Translator class.) | ||
| def translator | ||
| @translator ||= Translator.new | ||
| end | ||
| # Installs (or removes) a block that will be invoked for every access | ||
@@ -173,3 +162,3 @@ # to the database. If the block returns 0 (or +nil+), the statement | ||
| # occur, and returning 2 causes the access to be silently denied. | ||
| def authorizer( &block ) | ||
| def authorizer(&block) | ||
| self.authorizer = block | ||
@@ -184,3 +173,3 @@ end | ||
| def prepare sql | ||
| stmt = SQLite3::Statement.new( self, sql ) | ||
| stmt = SQLite3::Statement.new(self, sql) | ||
| return stmt unless block_given? | ||
@@ -198,3 +187,3 @@ | ||
| # temporary or in-memory. | ||
| def filename db_name = 'main' | ||
| def filename db_name = "main" | ||
| db_filename db_name | ||
@@ -218,19 +207,7 @@ end | ||
| def execute sql, bind_vars = [], *args, &block | ||
| if bind_vars.nil? || !args.empty? | ||
| if args.empty? | ||
| bind_vars = [] | ||
| else | ||
| bind_vars = [bind_vars] + args | ||
| end | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Database#execute` with nil or multiple bind params without using an array. Please switch to passing bind parameters as an array. Support for bind parameters as *args will be removed in 2.0.0. | ||
| eowarn | ||
| end | ||
| prepare( sql ) do |stmt| | ||
| prepare(sql) do |stmt| | ||
| stmt.bind_params(bind_vars) | ||
| stmt = ResultSet.new self, stmt | ||
| stmt = build_result_set stmt | ||
| if block_given? | ||
| if block | ||
| stmt.each do |row| | ||
@@ -240,3 +217,3 @@ yield row | ||
| else | ||
| stmt.to_a | ||
| stmt.to_a.freeze | ||
| end | ||
@@ -256,5 +233,5 @@ end | ||
| # executing statements. | ||
| def execute2( sql, *bind_vars ) | ||
| prepare( sql ) do |stmt| | ||
| result = stmt.execute( *bind_vars ) | ||
| def execute2(sql, *bind_vars) | ||
| prepare(sql) do |stmt| | ||
| result = stmt.execute(*bind_vars) | ||
| if block_given? | ||
@@ -264,4 +241,5 @@ yield stmt.columns | ||
| else | ||
| return result.inject( [ stmt.columns ] ) { |arr,row| | ||
| arr << row; arr } | ||
| return result.each_with_object([stmt.columns]) { |row, arr| | ||
| arr << row | ||
| } | ||
| end | ||
@@ -277,32 +255,11 @@ end | ||
| # | ||
| # This always returns +nil+, making it unsuitable for queries that return | ||
| # rows. | ||
| # This always returns the result of the last statement. | ||
| # | ||
| # See also #execute_batch2 for additional ways of | ||
| # executing statements. | ||
| def execute_batch( sql, bind_vars = [], *args ) | ||
| # FIXME: remove this stuff later | ||
| unless [Array, Hash].include?(bind_vars.class) | ||
| bind_vars = [bind_vars] | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Database#execute_batch` with bind parameters that are not a list of a hash. Please switch to passing bind parameters as an array or hash. Support for this behavior will be removed in version 2.0.0. | ||
| eowarn | ||
| end | ||
| # FIXME: remove this stuff later | ||
| if bind_vars.nil? || !args.empty? | ||
| if args.empty? | ||
| bind_vars = [] | ||
| else | ||
| bind_vars = [nil] + args | ||
| end | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Database#execute_batch` with nil or multiple bind params without using an array. Please switch to passing bind parameters as an array. Support for this behavior will be removed in version 2.0.0. | ||
| eowarn | ||
| end | ||
| def execute_batch(sql, bind_vars = [], *args) | ||
| sql = sql.strip | ||
| until sql.empty? do | ||
| prepare( sql ) do |stmt| | ||
| result = nil | ||
| until sql.empty? | ||
| prepare(sql) do |stmt| | ||
| unless stmt.closed? | ||
@@ -314,3 +271,3 @@ # FIXME: this should probably use sqlite3's api for batch execution | ||
| end | ||
| stmt.step | ||
| result = stmt.step | ||
| end | ||
@@ -320,4 +277,4 @@ sql = stmt.remainder.strip | ||
| end | ||
| # FIXME: we should not return `nil` as a success return value | ||
| nil | ||
| result | ||
| end | ||
@@ -339,3 +296,3 @@ | ||
| def execute_batch2(sql, &block) | ||
| if block_given? | ||
| if block | ||
| result = exec_batch(sql, @results_as_hash) | ||
@@ -361,17 +318,4 @@ result.map do |val| | ||
| # terminates. | ||
| def query( sql, bind_vars = [], *args ) | ||
| if bind_vars.nil? || !args.empty? | ||
| if args.empty? | ||
| bind_vars = [] | ||
| else | ||
| bind_vars = [bind_vars] + args | ||
| end | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Database#query` with nil or multiple bind params without using an array. Please switch to passing bind parameters as an array. Support for this will be removed in version 2.0.0. | ||
| eowarn | ||
| end | ||
| result = prepare( sql ).execute( bind_vars ) | ||
| def query(sql, bind_vars = [], *args) | ||
| result = prepare(sql).execute(bind_vars) | ||
| if block_given? | ||
@@ -384,3 +328,3 @@ begin | ||
| else | ||
| return result | ||
| result | ||
| end | ||
@@ -393,4 +337,4 @@ end | ||
| # See also #get_first_value. | ||
| def get_first_row( sql, *bind_vars ) | ||
| execute( sql, *bind_vars ).first | ||
| def get_first_row(sql, *bind_vars) | ||
| execute(sql, *bind_vars).first | ||
| end | ||
@@ -403,4 +347,4 @@ | ||
| # See also #get_first_row. | ||
| def get_first_value( sql, *bind_vars ) | ||
| query( sql, bind_vars ) do |rs| | ||
| def get_first_value(sql, *bind_vars) | ||
| query(sql, bind_vars) do |rs| | ||
| if (row = rs.next) | ||
@@ -413,3 +357,3 @@ return @results_as_hash ? row[rs.columns[0]] : row[0] | ||
| alias :busy_timeout :busy_timeout= | ||
| alias_method :busy_timeout, :busy_timeout= | ||
@@ -439,3 +383,3 @@ # Creates a new function for use in SQL statements. It will be added as | ||
| # puts db.get_first_value( "select maim(name) from table" ) | ||
| def create_function name, arity, text_rep=Constants::TextRep::UTF8, &block | ||
| def create_function name, arity, text_rep = Constants::TextRep::UTF8, &block | ||
| define_function_with_flags(name, text_rep) do |*args| | ||
@@ -485,11 +429,11 @@ fp = FunctionProxy.new | ||
| # aggregate functions. | ||
| def create_aggregate( name, arity, step=nil, finalize=nil, | ||
| text_rep=Constants::TextRep::ANY, &block ) | ||
| def create_aggregate(name, arity, step = nil, finalize = nil, | ||
| text_rep = Constants::TextRep::ANY, &block) | ||
| proxy = Class.new do | ||
| def self.step( &block ) | ||
| def self.step(&block) | ||
| define_method(:step_with_ctx, &block) | ||
| end | ||
| def self.finalize( &block ) | ||
| def self.finalize(&block) | ||
| define_method(:finalize_with_ctx, &block) | ||
@@ -499,3 +443,3 @@ end | ||
| if block_given? | ||
| if block | ||
| proxy.instance_eval(&block) | ||
@@ -526,3 +470,3 @@ else | ||
| def step( *args ) | ||
| def step(*args) | ||
| step_with_ctx(@ctx, *args) | ||
@@ -586,3 +530,3 @@ end | ||
| # puts db.get_first_value( "select lengths(name) from A" ) | ||
| def create_aggregate_handler( handler ) | ||
| def create_aggregate_handler(handler) | ||
| # This is a compatibility shim so the (basically pointless) FunctionProxy | ||
@@ -601,3 +545,3 @@ # "ctx" object is passed as first argument to both step() and finalize(). | ||
| def step( *args ) | ||
| def step(*args) | ||
| super(@fp, *args) | ||
@@ -625,3 +569,3 @@ end | ||
| # The functions arity is the arity of the +step+ method. | ||
| def define_aggregator( name, aggregator ) | ||
| def define_aggregator(name, aggregator) | ||
| # Previously, this has been implemented in C. Now this is just yet | ||
@@ -680,5 +624,5 @@ # another compatibility shim | ||
| # #rollback. | ||
| def transaction( mode = nil ) | ||
| def transaction(mode = nil) | ||
| mode = @default_transaction_mode if mode.nil? | ||
| execute "begin #{mode.to_s} transaction" | ||
| execute "begin #{mode} transaction" | ||
@@ -695,5 +639,5 @@ if block_given? | ||
| end | ||
| else | ||
| true | ||
| end | ||
| true | ||
| end | ||
@@ -725,2 +669,21 @@ | ||
| # Sets a #busy_handler that releases the GVL between retries, | ||
| # but only retries up to the indicated number of +milliseconds+. | ||
| # This is an alternative to #busy_timeout, which holds the GVL | ||
| # while SQLite sleeps and retries. | ||
| def busy_handler_timeout=(milliseconds) | ||
| timeout_seconds = milliseconds.fdiv(1000) | ||
| busy_handler do |count| | ||
| now = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
| if count.zero? | ||
| @timeout_deadline = now + timeout_seconds | ||
| elsif now > @timeout_deadline | ||
| next false | ||
| else | ||
| sleep(0.001) | ||
| end | ||
| end | ||
| end | ||
| # A helper class for dealing with custom functions (see #create_function, | ||
@@ -742,23 +705,10 @@ # #create_aggregate, and #create_aggregate_handler). It encapsulates the | ||
| def initialize | ||
| @result = nil | ||
| @context = {} | ||
| @result = nil | ||
| @context = {} | ||
| end | ||
| # Set the result of the function to the given error message. | ||
| # The function will then return that error. | ||
| def set_error( error ) | ||
| @driver.result_error( @func, error.to_s, -1 ) | ||
| end | ||
| # (Only available to aggregate functions.) Returns the number of rows | ||
| # that the aggregate has processed so far. This will include the current | ||
| # row, and so will always return at least 1. | ||
| def count | ||
| @driver.aggregate_count( @func ) | ||
| end | ||
| # Returns the value with the given key from the context. This is only | ||
| # available to aggregate functions. | ||
| def []( key ) | ||
| @context[ key ] | ||
| def [](key) | ||
| @context[key] | ||
| end | ||
@@ -768,25 +718,15 @@ | ||
| # available to aggregate functions. | ||
| def []=( key, value ) | ||
| @context[ key ] = value | ||
| def []=(key, value) | ||
| @context[key] = value | ||
| end | ||
| end | ||
| # Translates a +row+ of data from the database with the given +types+ | ||
| def translate_from_db types, row | ||
| @type_translator.call types, row | ||
| end | ||
| private | ||
| NULL_TRANSLATOR = lambda { |_, row| row } | ||
| def make_type_translator should_translate | ||
| if should_translate | ||
| lambda { |types, row| | ||
| types.zip(row).map do |type, value| | ||
| translator.translate( type, value ) | ||
| end | ||
| } | ||
| # Given a statement, return a result set. | ||
| # This is not intended for general consumption | ||
| # :nodoc: | ||
| def build_result_set stmt | ||
| if results_as_hash | ||
| HashResultSet.new(self, stmt) | ||
| else | ||
| NULL_TRANSLATOR | ||
| ResultSet.new(self, stmt) | ||
| end | ||
@@ -793,0 +733,0 @@ end |
@@ -1,2 +0,2 @@ | ||
| require 'sqlite3/constants' | ||
| require "sqlite3/constants" | ||
@@ -10,27 +10,52 @@ module SQLite3 | ||
| class SQLException < Exception; end | ||
| class InternalException < Exception; end | ||
| class PermissionException < Exception; end | ||
| class AbortException < Exception; end | ||
| class BusyException < Exception; end | ||
| class LockedException < Exception; end | ||
| class MemoryException < Exception; end | ||
| class ReadOnlyException < Exception; end | ||
| class InterruptException < Exception; end | ||
| class IOException < Exception; end | ||
| class CorruptException < Exception; end | ||
| class NotFoundException < Exception; end | ||
| class FullException < Exception; end | ||
| class CantOpenException < Exception; end | ||
| class ProtocolException < Exception; end | ||
| class EmptyException < Exception; end | ||
| class SchemaChangedException < Exception; end | ||
| class TooBigException < Exception; end | ||
| class ConstraintException < Exception; end | ||
| class MismatchException < Exception; end | ||
| class MisuseException < Exception; end | ||
| class UnsupportedException < Exception; end | ||
| class AuthorizationException < Exception; end | ||
| class FormatException < Exception; end | ||
| class RangeException < Exception; end | ||
| class NotADatabaseException < Exception; end | ||
| end |
+125
-135
@@ -1,5 +0,4 @@ | ||
| require 'sqlite3/errors' | ||
| require "sqlite3/errors" | ||
| module SQLite3 | ||
| # This module is intended for inclusion solely by the Database class. It | ||
@@ -11,6 +10,5 @@ # defines convenience methods for the various pragmas supported by SQLite3. | ||
| module Pragmas | ||
| # Returns +true+ or +false+ depending on the value of the named pragma. | ||
| def get_boolean_pragma( name ) | ||
| get_first_value( "PRAGMA #{name}" ) != 0 | ||
| def get_boolean_pragma(name) | ||
| get_first_value("PRAGMA #{name}") != 0 | ||
| end | ||
@@ -21,22 +19,20 @@ | ||
| # integer that represents truth. | ||
| def set_boolean_pragma( name, mode ) | ||
| def set_boolean_pragma(name, mode) | ||
| case mode | ||
| when String | ||
| case mode.downcase | ||
| when "on", "yes", "true", "y", "t"; mode = "'ON'" | ||
| when "off", "no", "false", "n", "f"; mode = "'OFF'" | ||
| else | ||
| raise Exception, | ||
| "unrecognized pragma parameter #{mode.inspect}" | ||
| end | ||
| case mode.downcase | ||
| when "on", "yes", "true", "y", "t" then mode = "'ON'" | ||
| when "off", "no", "false", "n", "f" then mode = "'OFF'" | ||
| else | ||
| raise SQLite3::Exception, "unrecognized pragma parameter #{mode.inspect}" | ||
| end | ||
| when true, 1 | ||
| mode = "ON" | ||
| mode = "ON" | ||
| when false, 0, nil | ||
| mode = "OFF" | ||
| mode = "OFF" | ||
| else | ||
| raise Exception, | ||
| "unrecognized pragma parameter #{mode.inspect}" | ||
| raise SQLite3::Exception, "unrecognized pragma parameter #{mode.inspect}" | ||
| end | ||
| execute( "PRAGMA #{name}=#{mode}" ) | ||
| execute("PRAGMA #{name}=#{mode}") | ||
| end | ||
@@ -47,8 +43,8 @@ | ||
| # are returned as an array. | ||
| def get_query_pragma( name, *params, &block ) # :yields: row | ||
| def get_query_pragma(name, *params, &block) # :yields: row | ||
| if params.empty? | ||
| execute( "PRAGMA #{name}", &block ) | ||
| execute("PRAGMA #{name}", &block) | ||
| else | ||
| args = "'" + params.join("','") + "'" | ||
| execute( "PRAGMA #{name}( #{args} )", &block ) | ||
| execute("PRAGMA #{name}( #{args} )", &block) | ||
| end | ||
@@ -58,4 +54,4 @@ end | ||
| # Return the value of the given pragma. | ||
| def get_enum_pragma( name ) | ||
| get_first_value( "PRAGMA #{name}" ) | ||
| def get_enum_pragma(name) | ||
| get_first_value("PRAGMA #{name}") | ||
| end | ||
@@ -68,12 +64,13 @@ | ||
| # #temp_store, and #default_temp_store for usage examples. | ||
| def set_enum_pragma( name, mode, enums ) | ||
| def set_enum_pragma(name, mode, enums) | ||
| match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } } | ||
| raise Exception, | ||
| "unrecognized #{name} #{mode.inspect}" unless match | ||
| execute( "PRAGMA #{name}='#{match.first.upcase}'" ) | ||
| unless match | ||
| raise SQLite3::Exception, "unrecognized #{name} #{mode.inspect}" | ||
| end | ||
| execute("PRAGMA #{name}='#{match.first.upcase}'") | ||
| end | ||
| # Returns the value of the given pragma as an integer. | ||
| def get_int_pragma( name ) | ||
| get_first_value( "PRAGMA #{name}" ).to_i | ||
| def get_int_pragma(name) | ||
| get_first_value("PRAGMA #{name}").to_i | ||
| end | ||
@@ -83,27 +80,27 @@ | ||
| # parameter. | ||
| def set_int_pragma( name, value ) | ||
| execute( "PRAGMA #{name}=#{value.to_i}" ) | ||
| def set_int_pragma(name, value) | ||
| execute("PRAGMA #{name}=#{value.to_i}") | ||
| end | ||
| # The enumeration of valid synchronous modes. | ||
| SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ] | ||
| SYNCHRONOUS_MODES = [["full", 2], ["normal", 1], ["off", 0]] | ||
| # The enumeration of valid temp store modes. | ||
| TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ] | ||
| TEMP_STORE_MODES = [["default", 0], ["file", 1], ["memory", 2]] | ||
| # The enumeration of valid auto vacuum modes. | ||
| AUTO_VACUUM_MODES = [ [ 'none', 0 ], [ 'full', 1 ], [ 'incremental', 2 ] ] | ||
| AUTO_VACUUM_MODES = [["none", 0], ["full", 1], ["incremental", 2]] | ||
| # The list of valid journaling modes. | ||
| JOURNAL_MODES = [ [ 'delete' ], [ 'truncate' ], [ 'persist' ], [ 'memory' ], | ||
| [ 'wal' ], [ 'off' ] ] | ||
| JOURNAL_MODES = [["delete"], ["truncate"], ["persist"], ["memory"], | ||
| ["wal"], ["off"]] | ||
| # The list of valid locking modes. | ||
| LOCKING_MODES = [ [ 'normal' ], [ 'exclusive' ] ] | ||
| LOCKING_MODES = [["normal"], ["exclusive"]] | ||
| # The list of valid encodings. | ||
| ENCODINGS = [ [ 'utf-8' ], [ 'utf-16' ], [ 'utf-16le' ], [ 'utf-16be ' ] ] | ||
| ENCODINGS = [["utf-8"], ["utf-16"], ["utf-16le"], ["utf-16be "]] | ||
| # The list of valid WAL checkpoints. | ||
| WAL_CHECKPOINTS = [ [ 'passive' ], [ 'full' ], [ 'restart' ], [ 'truncate' ] ] | ||
| WAL_CHECKPOINTS = [["passive"], ["full"], ["restart"], ["truncate"]] | ||
@@ -114,3 +111,3 @@ def application_id | ||
| def application_id=( integer ) | ||
| def application_id=(integer) | ||
| set_int_pragma "application_id", integer | ||
@@ -123,3 +120,3 @@ end | ||
| def auto_vacuum=( mode ) | ||
| def auto_vacuum=(mode) | ||
| set_enum_pragma "auto_vacuum", mode, AUTO_VACUUM_MODES | ||
@@ -132,3 +129,3 @@ end | ||
| def automatic_index=( mode ) | ||
| def automatic_index=(mode) | ||
| set_boolean_pragma "automatic_index", mode | ||
@@ -141,3 +138,3 @@ end | ||
| def busy_timeout=( milliseconds ) | ||
| def busy_timeout=(milliseconds) | ||
| set_int_pragma "busy_timeout", milliseconds | ||
@@ -150,3 +147,3 @@ end | ||
| def cache_size=( size ) | ||
| def cache_size=(size) | ||
| set_int_pragma "cache_size", size | ||
@@ -159,7 +156,7 @@ end | ||
| def cache_spill=( mode ) | ||
| def cache_spill=(mode) | ||
| set_boolean_pragma "cache_spill", mode | ||
| end | ||
| def case_sensitive_like=( mode ) | ||
| def case_sensitive_like=(mode) | ||
| set_boolean_pragma "case_sensitive_like", mode | ||
@@ -172,3 +169,3 @@ end | ||
| def cell_size_check=( mode ) | ||
| def cell_size_check=(mode) | ||
| set_boolean_pragma "cell_size_check", mode | ||
@@ -181,11 +178,11 @@ end | ||
| def checkpoint_fullfsync=( mode ) | ||
| def checkpoint_fullfsync=(mode) | ||
| set_boolean_pragma "checkpoint_fullfsync", mode | ||
| end | ||
| def collation_list( &block ) # :yields: row | ||
| def collation_list(&block) # :yields: row | ||
| get_query_pragma "collation_list", &block | ||
| end | ||
| def compile_options( &block ) # :yields: row | ||
| def compile_options(&block) # :yields: row | ||
| get_query_pragma "compile_options", &block | ||
@@ -198,3 +195,3 @@ end | ||
| def count_changes=( mode ) | ||
| def count_changes=(mode) | ||
| set_boolean_pragma "count_changes", mode | ||
@@ -207,3 +204,3 @@ end | ||
| def database_list( &block ) # :yields: row | ||
| def database_list(&block) # :yields: row | ||
| get_query_pragma "database_list", &block | ||
@@ -216,3 +213,3 @@ end | ||
| def default_cache_size=( size ) | ||
| def default_cache_size=(size) | ||
| set_int_pragma "default_cache_size", size | ||
@@ -225,3 +222,3 @@ end | ||
| def default_synchronous=( mode ) | ||
| def default_synchronous=(mode) | ||
| set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES | ||
@@ -234,3 +231,3 @@ end | ||
| def default_temp_store=( mode ) | ||
| def default_temp_store=(mode) | ||
| set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES | ||
@@ -243,3 +240,3 @@ end | ||
| def defer_foreign_keys=( mode ) | ||
| def defer_foreign_keys=(mode) | ||
| set_boolean_pragma "defer_foreign_keys", mode | ||
@@ -252,11 +249,11 @@ end | ||
| def encoding=( mode ) | ||
| def encoding=(mode) | ||
| set_enum_pragma "encoding", mode, ENCODINGS | ||
| end | ||
| def foreign_key_check( *table, &block ) # :yields: row | ||
| def foreign_key_check(*table, &block) # :yields: row | ||
| get_query_pragma "foreign_key_check", *table, &block | ||
| end | ||
| def foreign_key_list( table, &block ) # :yields: row | ||
| def foreign_key_list(table, &block) # :yields: row | ||
| get_query_pragma "foreign_key_list", table, &block | ||
@@ -269,3 +266,3 @@ end | ||
| def foreign_keys=( mode ) | ||
| def foreign_keys=(mode) | ||
| set_boolean_pragma "foreign_keys", mode | ||
@@ -282,3 +279,3 @@ end | ||
| def full_column_names=( mode ) | ||
| def full_column_names=(mode) | ||
| set_boolean_pragma "full_column_names", mode | ||
@@ -291,27 +288,27 @@ end | ||
| def fullfsync=( mode ) | ||
| def fullfsync=(mode) | ||
| set_boolean_pragma "fullfsync", mode | ||
| end | ||
| def ignore_check_constraints=( mode ) | ||
| def ignore_check_constraints=(mode) | ||
| set_boolean_pragma "ignore_check_constraints", mode | ||
| end | ||
| def incremental_vacuum( pages, &block ) # :yields: row | ||
| def incremental_vacuum(pages, &block) # :yields: row | ||
| get_query_pragma "incremental_vacuum", pages, &block | ||
| end | ||
| def index_info( index, &block ) # :yields: row | ||
| def index_info(index, &block) # :yields: row | ||
| get_query_pragma "index_info", index, &block | ||
| end | ||
| def index_list( table, &block ) # :yields: row | ||
| def index_list(table, &block) # :yields: row | ||
| get_query_pragma "index_list", table, &block | ||
| end | ||
| def index_xinfo( index, &block ) # :yields: row | ||
| def index_xinfo(index, &block) # :yields: row | ||
| get_query_pragma "index_xinfo", index, &block | ||
| end | ||
| def integrity_check( *num_errors, &block ) # :yields: row | ||
| def integrity_check(*num_errors, &block) # :yields: row | ||
| get_query_pragma "integrity_check", *num_errors, &block | ||
@@ -324,3 +321,3 @@ end | ||
| def journal_mode=( mode ) | ||
| def journal_mode=(mode) | ||
| set_enum_pragma "journal_mode", mode, JOURNAL_MODES | ||
@@ -333,3 +330,3 @@ end | ||
| def journal_size_limit=( size ) | ||
| def journal_size_limit=(size) | ||
| set_int_pragma "journal_size_limit", size | ||
@@ -342,3 +339,3 @@ end | ||
| def legacy_file_format=( mode ) | ||
| def legacy_file_format=(mode) | ||
| set_boolean_pragma "legacy_file_format", mode | ||
@@ -351,3 +348,3 @@ end | ||
| def locking_mode=( mode ) | ||
| def locking_mode=(mode) | ||
| set_enum_pragma "locking_mode", mode, LOCKING_MODES | ||
@@ -360,3 +357,3 @@ end | ||
| def max_page_count=( size ) | ||
| def max_page_count=(size) | ||
| set_int_pragma "max_page_count", size | ||
@@ -369,3 +366,3 @@ end | ||
| def mmap_size=( size ) | ||
| def mmap_size=(size) | ||
| set_int_pragma "mmap_size", size | ||
@@ -382,7 +379,7 @@ end | ||
| def page_size=( size ) | ||
| def page_size=(size) | ||
| set_int_pragma "page_size", size | ||
| end | ||
| def parser_trace=( mode ) | ||
| def parser_trace=(mode) | ||
| set_boolean_pragma "parser_trace", mode | ||
@@ -395,7 +392,7 @@ end | ||
| def query_only=( mode ) | ||
| def query_only=(mode) | ||
| set_boolean_pragma "query_only", mode | ||
| end | ||
| def quick_check( *num_errors, &block ) # :yields: row | ||
| def quick_check(*num_errors, &block) # :yields: row | ||
| get_query_pragma "quick_check", *num_errors, &block | ||
@@ -408,3 +405,3 @@ end | ||
| def read_uncommitted=( mode ) | ||
| def read_uncommitted=(mode) | ||
| set_boolean_pragma "read_uncommitted", mode | ||
@@ -417,3 +414,3 @@ end | ||
| def recursive_triggers=( mode ) | ||
| def recursive_triggers=(mode) | ||
| set_boolean_pragma "recursive_triggers", mode | ||
@@ -426,3 +423,3 @@ end | ||
| def reverse_unordered_selects=( mode ) | ||
| def reverse_unordered_selects=(mode) | ||
| set_boolean_pragma "reverse_unordered_selects", mode | ||
@@ -435,3 +432,3 @@ end | ||
| def schema_cookie=( cookie ) | ||
| def schema_cookie=(cookie) | ||
| set_int_pragma "schema_cookie", cookie | ||
@@ -444,3 +441,3 @@ end | ||
| def schema_version=( version ) | ||
| def schema_version=(version) | ||
| set_int_pragma "schema_version", version | ||
@@ -453,3 +450,3 @@ end | ||
| def secure_delete=( mode ) | ||
| def secure_delete=(mode) | ||
| set_boolean_pragma "secure_delete", mode | ||
@@ -462,3 +459,3 @@ end | ||
| def short_column_names=( mode ) | ||
| def short_column_names=(mode) | ||
| set_boolean_pragma "short_column_names", mode | ||
@@ -468,3 +465,3 @@ end | ||
| def shrink_memory | ||
| execute( "PRAGMA shrink_memory" ) | ||
| execute("PRAGMA shrink_memory") | ||
| end | ||
@@ -476,7 +473,7 @@ | ||
| def soft_heap_limit=( mode ) | ||
| def soft_heap_limit=(mode) | ||
| set_int_pragma "soft_heap_limit", mode | ||
| end | ||
| def stats( &block ) # :yields: row | ||
| def stats(&block) # :yields: row | ||
| get_query_pragma "stats", &block | ||
@@ -489,3 +486,3 @@ end | ||
| def synchronous=( mode ) | ||
| def synchronous=(mode) | ||
| set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES | ||
@@ -498,3 +495,3 @@ end | ||
| def temp_store=( mode ) | ||
| def temp_store=(mode) | ||
| set_enum_pragma "temp_store", mode, TEMP_STORE_MODES | ||
@@ -507,3 +504,3 @@ end | ||
| def threads=( count ) | ||
| def threads=(count) | ||
| set_int_pragma "threads", count | ||
@@ -516,3 +513,3 @@ end | ||
| def user_cookie=( cookie ) | ||
| def user_cookie=(cookie) | ||
| set_int_pragma "user_cookie", cookie | ||
@@ -525,15 +522,15 @@ end | ||
| def user_version=( version ) | ||
| def user_version=(version) | ||
| set_int_pragma "user_version", version | ||
| end | ||
| def vdbe_addoptrace=( mode ) | ||
| def vdbe_addoptrace=(mode) | ||
| set_boolean_pragma "vdbe_addoptrace", mode | ||
| end | ||
| def vdbe_debug=( mode ) | ||
| def vdbe_debug=(mode) | ||
| set_boolean_pragma "vdbe_debug", mode | ||
| end | ||
| def vdbe_listing=( mode ) | ||
| def vdbe_listing=(mode) | ||
| set_boolean_pragma "vdbe_listing", mode | ||
@@ -546,3 +543,3 @@ end | ||
| def vdbe_trace=( mode ) | ||
| def vdbe_trace=(mode) | ||
| set_boolean_pragma "vdbe_trace", mode | ||
@@ -555,3 +552,3 @@ end | ||
| def wal_autocheckpoint=( mode ) | ||
| def wal_autocheckpoint=(mode) | ||
| set_int_pragma "wal_autocheckpoint", mode | ||
@@ -564,7 +561,7 @@ end | ||
| def wal_checkpoint=( mode ) | ||
| def wal_checkpoint=(mode) | ||
| set_enum_pragma "wal_checkpoint", mode, WAL_CHECKPOINTS | ||
| end | ||
| def writable_schema=( mode ) | ||
| def writable_schema=(mode) | ||
| set_boolean_pragma "writable_schema", mode | ||
@@ -577,3 +574,3 @@ end | ||
| def table_info table | ||
| stmt = prepare "PRAGMA table_info(#{table})" | ||
| stmt = prepare "PRAGMA table_info(#{table})" | ||
| columns = stmt.columns | ||
@@ -586,10 +583,4 @@ | ||
| stmt.each do |row| | ||
| new_row = Hash[columns.zip(row)] | ||
| new_row = columns.zip(row).to_h | ||
| # FIXME: This should be removed but is required for older versions | ||
| # of rails | ||
| if(Object.const_defined?(:ActiveRecord)) | ||
| new_row['notnull'] = new_row['notnull'].to_s | ||
| end | ||
| tweak_default(new_row) if needs_tweak_default | ||
@@ -600,4 +591,4 @@ | ||
| # case. | ||
| if new_row['type'] | ||
| new_row['type'] = new_row['type'].downcase | ||
| if new_row["type"] | ||
| new_row["type"] = new_row["type"].downcase | ||
| end | ||
@@ -618,31 +609,30 @@ | ||
| # Compares two version strings | ||
| def version_compare(v1, v2) | ||
| v1 = v1.split(".").map { |i| i.to_i } | ||
| v2 = v2.split(".").map { |i| i.to_i } | ||
| parts = [v1.length, v2.length].max | ||
| v1.push 0 while v1.length < parts | ||
| v2.push 0 while v2.length < parts | ||
| v1.zip(v2).each do |a,b| | ||
| return -1 if a < b | ||
| return 1 if a > b | ||
| end | ||
| return 0 | ||
| # Compares two version strings | ||
| def version_compare(v1, v2) | ||
| v1 = v1.split(".").map { |i| i.to_i } | ||
| v2 = v2.split(".").map { |i| i.to_i } | ||
| parts = [v1.length, v2.length].max | ||
| v1.push 0 while v1.length < parts | ||
| v2.push 0 while v2.length < parts | ||
| v1.zip(v2).each do |a, b| | ||
| return -1 if a < b | ||
| return 1 if a > b | ||
| end | ||
| 0 | ||
| end | ||
| # Since SQLite 3.3.8, the table_info pragma has returned the default | ||
| # value of the row as a quoted SQL value. This method essentially | ||
| # unquotes those values. | ||
| def tweak_default(hash) | ||
| case hash["dflt_value"] | ||
| when /^null$/i | ||
| hash["dflt_value"] = nil | ||
| when /^'(.*)'$/m | ||
| hash["dflt_value"] = $1.gsub(/''/, "'") | ||
| when /^"(.*)"$/m | ||
| hash["dflt_value"] = $1.gsub(/""/, '"') | ||
| end | ||
| # Since SQLite 3.3.8, the table_info pragma has returned the default | ||
| # value of the row as a quoted SQL value. This method essentially | ||
| # unquotes those values. | ||
| def tweak_default(hash) | ||
| case hash["dflt_value"] | ||
| when /^null$/i | ||
| hash["dflt_value"] = nil | ||
| when /^'(.*)'$/m | ||
| hash["dflt_value"] = $1.gsub("''", "'") | ||
| when /^"(.*)"$/m | ||
| hash["dflt_value"] = $1.gsub('""', '"') | ||
| end | ||
| end | ||
| end | ||
| end |
+14
-97
@@ -1,6 +0,5 @@ | ||
| require 'sqlite3/constants' | ||
| require 'sqlite3/errors' | ||
| require "sqlite3/constants" | ||
| require "sqlite3/errors" | ||
| module SQLite3 | ||
| # The ResultSet object encapsulates the enumerability of a query's output. | ||
@@ -13,55 +12,6 @@ # It is a simple cursor over the data that the query returns. It will | ||
| class ArrayWithTypes < Array # :nodoc: | ||
| attr_accessor :types | ||
| end | ||
| class ArrayWithTypesAndFields < Array # :nodoc: | ||
| attr_writer :types | ||
| attr_writer :fields | ||
| def types | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `#{self.class}#types` which is deprecated and will be removed in sqlite3 version 2.0.0. Please call the `types` method on the SQLite3::ResultSet object that created this object. | ||
| eowarn | ||
| @types | ||
| end | ||
| def fields | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `#{self.class}#fields` which is deprecated and will be removed in sqlite3 version 2.0.0. Please call the `columns` method on the SQLite3::ResultSet object that created this object. | ||
| eowarn | ||
| @fields | ||
| end | ||
| end | ||
| # The class of which we return an object in case we want a Hash as | ||
| # result. | ||
| class HashWithTypesAndFields < Hash # :nodoc: | ||
| attr_writer :types | ||
| attr_writer :fields | ||
| def types | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `#{self.class}#types` which is deprecated and will be removed in sqlite3 version 2.0.0. Please call the `types` method on the SQLite3::ResultSet object that created this object. | ||
| eowarn | ||
| @types | ||
| end | ||
| def fields | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `#{self.class}#fields` which is deprecated and will be removed in sqlite3 version 2.0.0. Please call the `columns` method on the SQLite3::ResultSet object that created this object. | ||
| eowarn | ||
| @fields | ||
| end | ||
| def [] key | ||
| key = fields[key] if key.is_a? Numeric | ||
| super key | ||
| end | ||
| end | ||
| # Create a new ResultSet attached to the given database, using the | ||
| # given sql text. | ||
| def initialize db, stmt | ||
| @db = db | ||
| @db = db | ||
| @stmt = stmt | ||
@@ -72,6 +22,5 @@ end | ||
| # can be rewound and reiterated. | ||
| def reset( *bind_params ) | ||
| def reset(*bind_params) | ||
| @stmt.reset! | ||
| @stmt.bind_params( *bind_params ) | ||
| @eof = false | ||
| @stmt.bind_params(*bind_params) | ||
| end | ||
@@ -85,5 +34,3 @@ | ||
| # Obtain the next row from the cursor. If there are no more rows to be | ||
| # had, this will return +nil+. If type translation is active on the | ||
| # corresponding database, the values in the row will be translated | ||
| # according to their types. | ||
| # had, this will return +nil+. | ||
| # | ||
@@ -99,26 +46,3 @@ # The returned value will be an array, unless Database#results_as_hash has | ||
| def next | ||
| if @db.results_as_hash | ||
| return next_hash | ||
| end | ||
| row = @stmt.step | ||
| return nil if @stmt.done? | ||
| row = @db.translate_from_db @stmt.types, row | ||
| if row.respond_to?(:fields) | ||
| # FIXME: this can only happen if the translator returns something | ||
| # that responds to `fields`. Since we're removing the translator | ||
| # in 2.0, we can remove this branch in 2.0. | ||
| row = ArrayWithTypes.new(row) | ||
| else | ||
| # FIXME: the `fields` and `types` methods are deprecated on this | ||
| # object for version 2.0, so we can safely remove this branch | ||
| # as well. | ||
| row = ArrayWithTypesAndFields.new(row) | ||
| end | ||
| row.fields = @stmt.columns | ||
| row.types = @stmt.types | ||
| row | ||
| @stmt.step | ||
| end | ||
@@ -129,3 +53,3 @@ | ||
| def each | ||
| while node = self.next | ||
| while (node = self.next) | ||
| yield node | ||
@@ -138,3 +62,3 @@ end | ||
| def each_hash | ||
| while node = next_hash | ||
| while (node = next_hash) | ||
| yield node | ||
@@ -171,16 +95,9 @@ end | ||
| # FIXME: type translation is deprecated, so this can be removed | ||
| # in 2.0 | ||
| row = @db.translate_from_db @stmt.types, row | ||
| # FIXME: this can be switched to a regular hash in 2.0 | ||
| row = HashWithTypesAndFields[*@stmt.columns.zip(row).flatten] | ||
| # FIXME: these methods are deprecated for version 2.0, so we can remove | ||
| # this code in 2.0 | ||
| row.fields = @stmt.columns | ||
| row.types = @stmt.types | ||
| row | ||
| @stmt.columns.zip(row).to_h | ||
| end | ||
| end | ||
| class HashResultSet < ResultSet # :nodoc: | ||
| alias_method :next, :next_hash | ||
| end | ||
| end |
+58
-13
@@ -1,7 +0,7 @@ | ||
| require 'sqlite3/errors' | ||
| require 'sqlite3/resultset' | ||
| require "sqlite3/errors" | ||
| require "sqlite3/resultset" | ||
| class String | ||
| def to_blob | ||
| SQLite3::Blob.new( self ) | ||
| SQLite3::Blob.new(self) | ||
| end | ||
@@ -22,2 +22,19 @@ end | ||
| # call-seq: SQLite3::Statement.new(db, sql) | ||
| # | ||
| # Create a new statement attached to the given Database instance, and which | ||
| # encapsulates the given SQL text. If the text contains more than one | ||
| # statement (i.e., separated by semicolons), then the #remainder property | ||
| # will be set to the trailing text. | ||
| def initialize(db, sql) | ||
| raise ArgumentError, "prepare called on a closed database" if db.closed? | ||
| sql = sql.encode(Encoding::UTF_8) if sql && sql.encoding != Encoding::UTF_8 | ||
| @connection = db | ||
| @columns = nil | ||
| @types = nil | ||
| @remainder = prepare db, sql | ||
| end | ||
| # Binds the given variables to the corresponding placeholders in the SQL | ||
@@ -36,3 +53,3 @@ # text. | ||
| # Statement#bind_params. | ||
| def bind_params( *bind_vars ) | ||
| def bind_params(*bind_vars) | ||
| index = 1 | ||
@@ -63,12 +80,12 @@ bind_vars.flatten.each do |var| | ||
| # See also #bind_params, #execute!. | ||
| def execute( *bind_vars ) | ||
| def execute(*bind_vars) | ||
| reset! if active? || done? | ||
| bind_params(*bind_vars) unless bind_vars.empty? | ||
| @results = ResultSet.new(@connection, self) | ||
| results = @connection.build_result_set self | ||
| step if 0 == column_count | ||
| step if column_count == 0 | ||
| yield @results if block_given? | ||
| @results | ||
| yield results if block_given? | ||
| results | ||
| end | ||
@@ -90,5 +107,5 @@ | ||
| # See also #bind_params, #execute. | ||
| def execute!( *bind_vars, &block ) | ||
| def execute!(*bind_vars, &block) | ||
| execute(*bind_vars) | ||
| block_given? ? each(&block) : to_a | ||
| block ? each(&block) : to_a | ||
| end | ||
@@ -107,3 +124,3 @@ | ||
| get_metadata unless @columns | ||
| return @columns | ||
| @columns | ||
| end | ||
@@ -136,3 +153,31 @@ | ||
| # Returns a Hash containing information about the statement. | ||
| # The contents of the hash are implementation specific and may change in | ||
| # the future without notice. The hash includes information about internal | ||
| # statistics about the statement such as: | ||
| # - +fullscan_steps+: the number of times that SQLite has stepped forward | ||
| # in a table as part of a full table scan | ||
| # - +sorts+: the number of sort operations that have occurred | ||
| # - +autoindexes+: the number of rows inserted into transient indices | ||
| # that were created automatically in order to help joins run faster | ||
| # - +vm_steps+: the number of virtual machine operations executed by the | ||
| # prepared statement | ||
| # - +reprepares+: the number of times that the prepare statement has been | ||
| # automatically regenerated due to schema changes or changes to bound | ||
| # parameters that might affect the query plan | ||
| # - +runs+: the number of times that the prepared statement has been run | ||
| # - +filter_misses+: the number of times that the Bloom filter returned | ||
| # a find, and thus the join step had to be processed as normal | ||
| # - +filter_hits+: the number of times that a join step was bypassed | ||
| # because a Bloom filter returned not-found | ||
| def stat key = nil | ||
| if key | ||
| stat_for(key) | ||
| else | ||
| stats_as_hash | ||
| end | ||
| end | ||
| private | ||
| # A convenience method for obtaining the metadata about the query. Note | ||
@@ -147,3 +192,3 @@ # that this will actually execute the SQL, which means it can be a | ||
| val = column_decltype(column) | ||
| val.nil? ? nil : val.downcase | ||
| val&.downcase | ||
| end | ||
@@ -150,0 +195,0 @@ end |
+17
-20
@@ -1,9 +0,8 @@ | ||
| require 'sqlite3/constants' | ||
| require "sqlite3/constants" | ||
| module SQLite3 | ||
| class Value | ||
| attr_reader :handle | ||
| def initialize( db, handle ) | ||
| def initialize(db, handle) | ||
| @driver = db.driver | ||
@@ -18,10 +17,10 @@ @handle = handle | ||
| def to_blob | ||
| @driver.value_blob( @handle ) | ||
| @driver.value_blob(@handle) | ||
| end | ||
| def length( utf16=false ) | ||
| def length(utf16 = false) | ||
| if utf16 | ||
| @driver.value_bytes16( @handle ) | ||
| @driver.value_bytes16(@handle) | ||
| else | ||
| @driver.value_bytes( @handle ) | ||
| @driver.value_bytes(@handle) | ||
| end | ||
@@ -31,29 +30,27 @@ end | ||
| def to_f | ||
| @driver.value_double( @handle ) | ||
| @driver.value_double(@handle) | ||
| end | ||
| def to_i | ||
| @driver.value_int( @handle ) | ||
| @driver.value_int(@handle) | ||
| end | ||
| def to_int64 | ||
| @driver.value_int64( @handle ) | ||
| @driver.value_int64(@handle) | ||
| end | ||
| def to_s( utf16=false ) | ||
| @driver.value_text( @handle, utf16 ) | ||
| def to_s(utf16 = false) | ||
| @driver.value_text(@handle, utf16) | ||
| end | ||
| def type | ||
| case @driver.value_type( @handle ) | ||
| when Constants::ColumnType::INTEGER then :int | ||
| when Constants::ColumnType::FLOAT then :float | ||
| when Constants::ColumnType::TEXT then :text | ||
| when Constants::ColumnType::BLOB then :blob | ||
| when Constants::ColumnType::NULL then :null | ||
| case @driver.value_type(@handle) | ||
| when Constants::ColumnType::INTEGER then :int | ||
| when Constants::ColumnType::FLOAT then :float | ||
| when Constants::ColumnType::TEXT then :text | ||
| when Constants::ColumnType::BLOB then :blob | ||
| when Constants::ColumnType::NULL then :null | ||
| end | ||
| end | ||
| end | ||
| end |
| module SQLite3 | ||
| VERSION = "1.7.3" | ||
| module VersionProxy | ||
| MAJOR = 1 | ||
| MINOR = 7 | ||
| TINY = 3 | ||
| BUILD = nil | ||
| STRING = [ MAJOR, MINOR, TINY, BUILD ].compact.join( "." ) | ||
| VERSION = ::SQLite3::VERSION | ||
| end | ||
| def self.const_missing(name) | ||
| return super unless name == :Version | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]}: `SQLite::Version` will be removed in sqlite3-ruby version 2.0.0 | ||
| eowarn | ||
| VersionProxy | ||
| end | ||
| VERSION = "2.0.0" | ||
| end |
+18
-22
@@ -1,27 +0,23 @@ | ||
| Copyright (c) 2004, Jamis Buck (jamis@jamisbuck.org) | ||
| All rights reserved. | ||
| Copyright (c) 2004-2024, Jamis Buck, Luis Lavena, Aaron Patterson, Mike Dalessio, et al. | ||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are met: | ||
| Redistribution and use in source and binary forms, with or without modification, are permitted | ||
| provided that the following conditions are met: | ||
| * Redistributions of source code must retain the above copyright notice, | ||
| this list of conditions and the following disclaimer. | ||
| 1. Redistributions of source code must retain the above copyright notice, this list of conditions | ||
| and the following disclaimer. | ||
| * Redistributions in binary form must reproduce the above copyright | ||
| notice, this list of conditions and the following disclaimer in the | ||
| documentation and/or other materials provided with the distribution. | ||
| 2. Redistributions in binary form must reproduce the above copyright notice, this list of | ||
| conditions and the following disclaimer in the documentation and/or other materials provided with | ||
| the distribution. | ||
| * The names of its contributors may not be used to endorse or promote | ||
| products derived from this software without specific prior written | ||
| permission. | ||
| 3. Neither the name of the copyright holder nor the names of its contributors may be used to | ||
| endorse or promote products derived from this software without specific prior written permission. | ||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | ||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR | ||
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | ||
| FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR | ||
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER | ||
| IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF | ||
| THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+75
-4
@@ -14,4 +14,3 @@ # Ruby Interface for SQLite3 | ||
| [](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/sqlite3-ruby.yml) | ||
| [](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/gem-install.yml) | ||
| [](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/ci.yml) | ||
@@ -81,2 +80,74 @@ | ||
| ## Thread Safety | ||
| When `SQLite3.threadsafe?` returns `true`, then SQLite3 has been compiled to | ||
| support running in a multithreaded environment. However, this doesn't mean | ||
| that all classes in the SQLite3 gem can be considered "thread safe". | ||
| When `SQLite3.threadsafe?` returns `true`, it is safe to share only | ||
| `SQLite3::Database` instances among threads without providing your own locking | ||
| mechanism. For example, the following code is fine because only the database | ||
| instance is shared among threads: | ||
| ```ruby | ||
| require 'sqlite3' | ||
| db = SQLite3::Database.new ":memory:" | ||
| latch = Queue.new | ||
| ts = 10.times.map { | ||
| Thread.new { | ||
| latch.pop | ||
| db.execute "SELECT '#{Thread.current.inspect}'" | ||
| } | ||
| } | ||
| 10.times { latch << nil } | ||
| p ts.map(&:value) | ||
| ``` | ||
| Other instances can be shared among threads, but they require that you provide | ||
| your own locking for thread safety. For example, `SQLite3::Statement` objects | ||
| (prepared statements) are mutable, so applications must take care to add | ||
| appropriate locks to avoid data race conditions when sharing these objects | ||
| among threads. | ||
| Lets rewrite the above example but use a prepared statement and safely share | ||
| the prepared statement among threads: | ||
| ```ruby | ||
| db = SQLite3::Database.new ":memory:" | ||
| # Prepare a statement | ||
| stmt = db.prepare "SELECT :inspect" | ||
| stmt_lock = Mutex.new | ||
| latch = Queue.new | ||
| ts = 10.times.map { | ||
| Thread.new { | ||
| latch.pop | ||
| # Add a lock when using the prepared statement. | ||
| # Binding values, and walking over results will mutate the statement, so | ||
| # in order to prevent other threads from "seeing" this thread's data, we | ||
| # must lock when using the statement object | ||
| stmt_lock.synchronize do | ||
| stmt.execute(Thread.current.inspect).to_a | ||
| end | ||
| } | ||
| } | ||
| 10.times { latch << nil } | ||
| p ts.map(&:value) | ||
| stmt.close | ||
| ``` | ||
| It is generally recommended that if applications want to share a database among | ||
| threads, they _only_ share the database instance object. Other objects are | ||
| fine to share, but may require manual locking for thread safety. | ||
| ## Support | ||
@@ -109,5 +180,5 @@ | ||
| ### Dependencies | ||
| The source code of `sqlite` is distributed in the "ruby platform" gem. This code is public domain, see [`LICENSE-DEPENDENCIES`](./LICENSE-DEPENDENCIES) for details. | ||
| The source code of `sqlite` is distributed in the "ruby platform" gem. This code is public domain, | ||
| see https://www.sqlite.org/copyright.html for details. |
| # API Changes | ||
| * SQLite3::Database#execute only accepts an array for bind parameters. | ||
| * SQLite3::ResultSet used to query the database for the first row, regardless | ||
| of whether the user asked for it or not. I have removed that so that rows | ||
| will not be returned until the user asks for them. This is a subtle but | ||
| sometimes important change in behavior. | ||
| 83882d2208ed189361617d5ab8532a325aaf729d | ||
| * SQLite3::Database#trace now takes either a block or an object that responds | ||
| to "call". The previous implementation passed around a VALUE that was cast | ||
| to a void *. This is dangerous because the value could get garbage collected | ||
| before the proc was called. If the user wants data passed around with the | ||
| block, they should use variables available to the closure or create an | ||
| object. | ||
| * SQLite3::Statement#step automatically converts to ruby types, where before | ||
| all values were automatically yielded as strings. This will only be a | ||
| problem for people who were accessing information about the database that | ||
| wasn't previously passed through the pure ruby conversion code. | ||
| * SQLite3::Database#errmsg no longer takes a parameter to return error | ||
| messages as UTF-16. Do people even use that? I opt for staying UTF-8 when | ||
| possible. See test_integration.rb test_errmsg_utf16 | ||
| * SQLite3::Database#authorize same changes as trace | ||
| * test/test_tc_database.rb was removed because we no longer use the Driver | ||
| design pattern. | ||
| # Garbage Collection Strategy | ||
| All statements keep pointers back to their respective database connections. | ||
| The @connection instance variable on the Statement handle keeps the database | ||
| connection alive. Memory allocated for a statement handler will be freed in | ||
| two cases: | ||
| * close is called on the statement | ||
| * The SQLite3::Database object gets garbage collected | ||
| We can't free the memory for the statement in the garbage collection function | ||
| for the statement handler. The reason is because there exists a race | ||
| condition. We cannot guarantee the order in which objects will be garbage | ||
| collected. So, it is possible that a connection and a statement are up for | ||
| garbage collection. If the database connection were to be free'd before the | ||
| statement, then boom. Instead we'll be conservative and free unclosed | ||
| statements when the connection is terminated. |
Sorry, the diff of this file is not supported yet
-10
| source "https://rubygems.org" | ||
| gemspec | ||
| gem("minitest", "5.21.2") | ||
| gem("rake-compiler", "1.2.5") | ||
| gem("rake-compiler-dock", "1.4.0") | ||
| gem("rdoc", "6.6.2") | ||
| gem("ruby_memcheck", "2.3.0") if Gem::Platform.local.os == "linux" |
| require 'time' | ||
| require 'date' | ||
| module SQLite3 | ||
| # The Translator class encapsulates the logic and callbacks necessary for | ||
| # converting string data to a value of some specified type. Every Database | ||
| # instance may have a Translator instance, in order to assist in type | ||
| # translation (Database#type_translation). | ||
| # | ||
| # Further, applications may define their own custom type translation logic | ||
| # by registering translator blocks with the corresponding database's | ||
| # translator instance (Database#translator). | ||
| class Translator | ||
| # Create a new Translator instance. It will be preinitialized with default | ||
| # translators for most SQL data types. | ||
| def initialize | ||
| @translators = Hash.new( proc { |type,value| value } ) | ||
| @type_name_cache = {} | ||
| register_default_translators | ||
| end | ||
| # Add a new translator block, which will be invoked to process type | ||
| # translations to the given type. The type should be an SQL datatype, and | ||
| # may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical | ||
| # information is stripped off and discarded, so type translation decisions | ||
| # are made solely on the "base" type name. | ||
| # | ||
| # The translator block itself should accept two parameters, "type" and | ||
| # "value". In this case, the "type" is the full type name (including | ||
| # parentheses), so the block itself may include logic for changing how a | ||
| # type is translated based on the additional data. The "value" parameter | ||
| # is the (string) data to convert. | ||
| # | ||
| # The block should return the translated value. | ||
| def add_translator( type, &block ) # :yields: type, value | ||
| warn(<<-eowarn) if $VERBOSE | ||
| #{caller[0]} is calling `SQLite3::Translator#add_translator`. Built-in translators are deprecated and will be removed in version 2.0.0. | ||
| eowarn | ||
| @translators[ type_name( type ) ] = block | ||
| end | ||
| # Translate the given string value to a value of the given type. In the | ||
| # absence of an installed translator block for the given type, the value | ||
| # itself is always returned. Further, +nil+ values are never translated, | ||
| # and are always passed straight through regardless of the type parameter. | ||
| def translate( type, value ) | ||
| unless value.nil? | ||
| # FIXME: this is a hack to support Sequel | ||
| if type && %w{ datetime timestamp }.include?(type.downcase) | ||
| @translators[ type_name( type ) ].call( type, value.to_s ) | ||
| else | ||
| @translators[ type_name( type ) ].call( type, value ) | ||
| end | ||
| end | ||
| end | ||
| # A convenience method for working with type names. This returns the "base" | ||
| # type name, without any parenthetical data. | ||
| def type_name( type ) | ||
| @type_name_cache[type] ||= begin | ||
| type = "" if type.nil? | ||
| type = $1 if type =~ /^(.*?)\(/ | ||
| type.upcase | ||
| end | ||
| end | ||
| private :type_name | ||
| # Register the default translators for the current Translator instance. | ||
| # This includes translators for most major SQL data types. | ||
| def register_default_translators | ||
| [ "time", | ||
| "timestamp" ].each { |type| add_translator( type ) { |t, v| Time.parse( v ) } } | ||
| add_translator( "date" ) { |t,v| Date.parse(v) } | ||
| add_translator( "datetime" ) { |t,v| DateTime.parse(v) } | ||
| [ "decimal", | ||
| "float", | ||
| "numeric", | ||
| "double", | ||
| "real", | ||
| "dec", | ||
| "fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } } | ||
| [ "integer", | ||
| "smallint", | ||
| "mediumint", | ||
| "int", | ||
| "bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } } | ||
| [ "bit", | ||
| "bool", | ||
| "boolean" ].each do |type| | ||
| add_translator( type ) do |t,v| | ||
| !( v.strip.gsub(/00+/,"0") == "0" || | ||
| v.downcase == "false" || | ||
| v.downcase == "f" || | ||
| v.downcase == "no" || | ||
| v.downcase == "n" ) | ||
| end | ||
| end | ||
| add_translator( "tinyint" ) do |type, value| | ||
| if type =~ /\(\s*1\s*\)/ | ||
| value.to_i == 1 | ||
| else | ||
| value.to_i | ||
| end | ||
| end | ||
| end | ||
| private :register_default_translators | ||
| end | ||
| end |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| require 'sqlite3' | ||
| require 'minitest/autorun' | ||
| if ENV['GITHUB_ACTIONS'] == 'true' || ENV['CI'] | ||
| $VERBOSE = nil | ||
| end | ||
| puts "info: sqlite3-ruby version: #{SQLite3::VERSION}/#{SQLite3::VersionProxy::STRING}" | ||
| puts "info: sqlite3 version: #{SQLite3::SQLITE_VERSION}/#{SQLite3::SQLITE_LOADED_VERSION}" | ||
| puts "info: sqlcipher?: #{SQLite3.sqlcipher?}" | ||
| puts "info: threadsafe?: #{SQLite3.threadsafe?}" | ||
| unless RUBY_VERSION >= "1.9" | ||
| require 'iconv' | ||
| end | ||
| module SQLite3 | ||
| class TestCase < Minitest::Test | ||
| alias :assert_not_equal :refute_equal | ||
| alias :assert_not_nil :refute_nil | ||
| alias :assert_raise :assert_raises | ||
| def assert_nothing_raised | ||
| yield | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestBackup < SQLite3::TestCase | ||
| def setup | ||
| @sdb = SQLite3::Database.new(':memory:') | ||
| @ddb = SQLite3::Database.new(':memory:') | ||
| @sdb.execute('CREATE TABLE foo (idx, val);'); | ||
| @data = ('A'..'Z').map{|x|x * 40} | ||
| @data.each_with_index do |v, i| | ||
| @sdb.execute('INSERT INTO foo (idx, val) VALUES (?, ?);', [i, v]) | ||
| end | ||
| end | ||
| def test_backup_step | ||
| b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') | ||
| while b.step(1) == SQLite3::Constants::ErrorCode::OK | ||
| assert_not_equal(0, b.remaining) | ||
| end | ||
| assert_equal(0, b.remaining) | ||
| b.finish | ||
| assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) | ||
| end | ||
| def test_backup_all | ||
| b = SQLite3::Backup.new(@ddb, 'main', @sdb, 'main') | ||
| assert_equal(SQLite3::Constants::ErrorCode::DONE, b.step(-1)) | ||
| assert_equal(0, b.remaining) | ||
| b.finish | ||
| assert_equal(@data.length, @ddb.execute('SELECT * FROM foo;').length) | ||
| end | ||
| end if defined?(SQLite3::Backup) | ||
| end |
| # -*- coding: utf-8 -*- | ||
| require 'helper' | ||
| module SQLite3 | ||
| class TestCollation < SQLite3::TestCase | ||
| class Comparator | ||
| attr_reader :calls | ||
| def initialize | ||
| @calls = [] | ||
| end | ||
| def compare left, right | ||
| @calls << [left, right] | ||
| left <=> right | ||
| end | ||
| end | ||
| def setup | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @create = "create table ex(id int, data string)" | ||
| @db.execute(@create); | ||
| [ [1, 'hello'], [2, 'world'] ].each do |vals| | ||
| @db.execute('insert into ex (id, data) VALUES (?, ?)', vals) | ||
| end | ||
| end | ||
| def test_custom_collation | ||
| comparator = Comparator.new | ||
| @db.collation 'foo', comparator | ||
| assert_equal comparator, @db.collations['foo'] | ||
| @db.execute('select data from ex order by 1 collate foo') | ||
| assert_equal 1, comparator.calls.length | ||
| end | ||
| def test_remove_collation | ||
| comparator = Comparator.new | ||
| @db.collation 'foo', comparator | ||
| @db.collation 'foo', nil | ||
| assert_nil @db.collations['foo'] | ||
| assert_raises(SQLite3::SQLException) do | ||
| @db.execute('select data from ex order by 1 collate foo') | ||
| end | ||
| end | ||
| if RUBY_VERSION >= '1.9.1' | ||
| def test_encoding | ||
| comparator = Comparator.new | ||
| @db.collation 'foo', comparator | ||
| @db.execute('select data from ex order by 1 collate foo') | ||
| a, b = *comparator.calls.first | ||
| assert_equal Encoding.find('UTF-8'), a.encoding | ||
| assert_equal Encoding.find('UTF-8'), b.encoding | ||
| end | ||
| def test_encoding_default_internal | ||
| warn_before = $-w | ||
| $-w = false | ||
| before_enc = Encoding.default_internal | ||
| Encoding.default_internal = 'EUC-JP' | ||
| comparator = Comparator.new | ||
| @db.collation 'foo', comparator | ||
| @db.execute('select data from ex order by 1 collate foo') | ||
| a, b = *comparator.calls.first | ||
| assert_equal Encoding.find('EUC-JP'), a.encoding | ||
| assert_equal Encoding.find('EUC-JP'), b.encoding | ||
| ensure | ||
| Encoding.default_internal = before_enc | ||
| $-w = warn_before | ||
| end | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestDatabaseFlags < SQLite3::TestCase | ||
| def setup | ||
| File.unlink 'test-flags.db' if File.exist?('test-flags.db') | ||
| @db = SQLite3::Database.new('test-flags.db') | ||
| @db.execute("CREATE TABLE foos (id integer)") | ||
| @db.close | ||
| end | ||
| def teardown | ||
| @db.close unless @db.closed? | ||
| File.unlink 'test-flags.db' if File.exist?('test-flags.db') | ||
| end | ||
| def test_open_database_flags_constants | ||
| defined_to_date = [:READONLY, :READWRITE, :CREATE, :DELETEONCLOSE, | ||
| :EXCLUSIVE, :MAIN_DB, :TEMP_DB, :TRANSIENT_DB, | ||
| :MAIN_JOURNAL, :TEMP_JOURNAL, :SUBJOURNAL, | ||
| :MASTER_JOURNAL, :NOMUTEX, :FULLMUTEX] | ||
| if SQLite3::SQLITE_VERSION_NUMBER > 3007002 | ||
| defined_to_date += [:AUTOPROXY, :SHAREDCACHE, :PRIVATECACHE, :WAL] | ||
| end | ||
| if SQLite3::SQLITE_VERSION_NUMBER > 3007007 | ||
| defined_to_date += [:URI] | ||
| end | ||
| if SQLite3::SQLITE_VERSION_NUMBER > 3007013 | ||
| defined_to_date += [:MEMORY] | ||
| end | ||
| assert defined_to_date.sort == SQLite3::Constants::Open.constants.sort | ||
| end | ||
| def test_open_database_flags_conflicts_with_readonly | ||
| assert_raise(RuntimeError) do | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readonly => true) | ||
| end | ||
| end | ||
| def test_open_database_flags_conflicts_with_readwrite | ||
| assert_raise(RuntimeError) do | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => 2, :readwrite => true) | ||
| end | ||
| end | ||
| def test_open_database_readonly_flags | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) | ||
| assert @db.readonly? | ||
| end | ||
| def test_open_database_readwrite_flags | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) | ||
| assert !@db.readonly? | ||
| end | ||
| def test_open_database_readonly_flags_cant_open | ||
| File.unlink 'test-flags.db' | ||
| assert_raise(SQLite3::CantOpenException) do | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY) | ||
| end | ||
| end | ||
| def test_open_database_readwrite_flags_cant_open | ||
| File.unlink 'test-flags.db' | ||
| assert_raise(SQLite3::CantOpenException) do | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE) | ||
| end | ||
| end | ||
| def test_open_database_misuse_flags | ||
| assert_raise(SQLite3::MisuseException) do | ||
| flags = SQLite3::Constants::Open::READONLY | SQLite3::Constants::Open::READWRITE # <== incompatible flags | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => flags) | ||
| end | ||
| end | ||
| def test_open_database_create_flags | ||
| File.unlink 'test-flags.db' | ||
| flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => flags) do |db| | ||
| db.execute("CREATE TABLE foos (id integer)") | ||
| db.execute("INSERT INTO foos (id) VALUES (12)") | ||
| end | ||
| assert File.exist?('test-flags.db') | ||
| end | ||
| def test_open_database_exotic_flags | ||
| flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE | ||
| exotic_flags = SQLite3::Constants::Open::NOMUTEX | SQLite3::Constants::Open::TEMP_DB | ||
| @db = SQLite3::Database.new('test-flags.db', :flags => flags | exotic_flags) | ||
| @db.execute("INSERT INTO foos (id) VALUES (12)") | ||
| assert @db.changes == 1 | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestDatabaseReadonly < SQLite3::TestCase | ||
| def setup | ||
| File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') | ||
| @db = SQLite3::Database.new('test-readonly.db') | ||
| @db.execute("CREATE TABLE foos (id integer)") | ||
| @db.close | ||
| end | ||
| def teardown | ||
| @db.close unless @db.closed? | ||
| File.unlink 'test-readonly.db' if File.exist?('test-readonly.db') | ||
| end | ||
| def test_open_readonly_database | ||
| @db = SQLite3::Database.new('test-readonly.db', :readonly => true) | ||
| assert @db.readonly? | ||
| end | ||
| def test_open_readonly_not_exists_database | ||
| File.unlink 'test-readonly.db' | ||
| assert_raise(SQLite3::CantOpenException) do | ||
| @db = SQLite3::Database.new('test-readonly.db', :readonly => true) | ||
| end | ||
| end | ||
| def test_insert_readonly_database | ||
| @db = SQLite3::Database.new('test-readonly.db', :readonly => true) | ||
| assert_raise(SQLite3::ReadOnlyException) do | ||
| @db.execute("INSERT INTO foos (id) VALUES (12)") | ||
| end | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestDatabaseReadwrite < SQLite3::TestCase | ||
| def setup | ||
| File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') | ||
| @db = SQLite3::Database.new('test-readwrite.db') | ||
| @db.execute("CREATE TABLE foos (id integer)") | ||
| @db.close | ||
| end | ||
| def teardown | ||
| @db.close unless @db.closed? | ||
| File.unlink 'test-readwrite.db' if File.exist?('test-readwrite.db') | ||
| end | ||
| def test_open_readwrite_database | ||
| @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) | ||
| assert !@db.readonly? | ||
| end | ||
| def test_open_readwrite_readonly_database | ||
| assert_raise(RuntimeError) do | ||
| @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true, :readonly => true) | ||
| end | ||
| end | ||
| def test_open_readwrite_not_exists_database | ||
| File.unlink 'test-readwrite.db' | ||
| assert_raise(SQLite3::CantOpenException) do | ||
| @db = SQLite3::Database.new('test-readwrite.db', :readonly => true) | ||
| end | ||
| end | ||
| def test_insert_readwrite_database | ||
| @db = SQLite3::Database.new('test-readwrite.db', :readwrite => true) | ||
| @db.execute("INSERT INTO foos (id) VALUES (12)") | ||
| assert @db.changes == 1 | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| require 'tempfile' | ||
| require 'pathname' | ||
| module SQLite3 | ||
| class TestDatabase < SQLite3::TestCase | ||
| attr_reader :db | ||
| def setup | ||
| @db = SQLite3::Database.new(':memory:') | ||
| super | ||
| end | ||
| def teardown | ||
| @db.close unless @db.closed? | ||
| end | ||
| def test_segv | ||
| assert_raises { SQLite3::Database.new 1 } | ||
| end | ||
| def test_db_filename | ||
| tf = nil | ||
| assert_equal '', @db.filename('main') | ||
| tf = Tempfile.new 'thing' | ||
| @db = SQLite3::Database.new tf.path | ||
| assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename('main')) | ||
| ensure | ||
| tf.unlink if tf | ||
| end | ||
| def test_filename | ||
| tf = nil | ||
| assert_equal '', @db.filename | ||
| tf = Tempfile.new 'thing' | ||
| @db = SQLite3::Database.new tf.path | ||
| assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename) | ||
| ensure | ||
| tf.unlink if tf | ||
| end | ||
| def test_filename_with_attachment | ||
| tf = nil | ||
| assert_equal '', @db.filename | ||
| tf = Tempfile.new 'thing' | ||
| @db.execute "ATTACH DATABASE '#{tf.path}' AS 'testing'" | ||
| assert_equal File.realdirpath(tf.path), File.realdirpath(@db.filename('testing')) | ||
| ensure | ||
| tf.unlink if tf | ||
| end | ||
| def test_filename_to_path | ||
| tf = Tempfile.new 'thing' | ||
| pn = Pathname tf.path | ||
| db = SQLite3::Database.new pn | ||
| assert_equal pn.realdirpath.to_s, File.realdirpath(db.filename) | ||
| ensure | ||
| tf.close! if tf | ||
| db.close if db | ||
| end | ||
| def test_error_code | ||
| begin | ||
| db.execute 'SELECT' | ||
| rescue SQLite3::SQLException => e | ||
| end | ||
| assert_equal 1, e.code | ||
| end | ||
| def test_extended_error_code | ||
| db.extended_result_codes = true | ||
| db.execute 'CREATE TABLE "employees" ("token" integer NOT NULL)' | ||
| begin | ||
| db.execute 'INSERT INTO employees (token) VALUES (NULL)' | ||
| rescue SQLite3::ConstraintException => e | ||
| end | ||
| assert_equal 1299, e.code | ||
| end | ||
| def test_bignum | ||
| num = 4907021672125087844 | ||
| db.execute 'CREATE TABLE "employees" ("token" integer(8), "name" varchar(20) NOT NULL)' | ||
| db.execute "INSERT INTO employees(name, token) VALUES('employee-1', ?)", [num] | ||
| rows = db.execute 'select token from employees' | ||
| assert_equal num, rows.first.first | ||
| end | ||
| def test_blob | ||
| @db.execute("CREATE TABLE blobs ( id INTEGER, hash BLOB(10) )") | ||
| blob = Blob.new("foo\0bar") | ||
| @db.execute("INSERT INTO blobs VALUES (0, ?)", [blob]) | ||
| assert_equal [[0, blob, blob.length, blob.length*2]], @db.execute("SELECT id, hash, length(hash), length(hex(hash)) FROM blobs") | ||
| end | ||
| def test_get_first_row | ||
| assert_equal [1], @db.get_first_row('SELECT 1') | ||
| end | ||
| def test_get_first_row_with_type_translation_and_hash_results | ||
| @db.results_as_hash = true | ||
| capture_io do # hush translation deprecation warnings | ||
| @db.type_translation = true | ||
| assert_equal({"1"=>1}, @db.get_first_row('SELECT 1')) | ||
| end | ||
| end | ||
| def test_execute_with_type_translation_and_hash | ||
| rows = [] | ||
| @db.results_as_hash = true | ||
| capture_io do # hush translation deprecation warnings | ||
| @db.type_translation = true | ||
| @db.execute('SELECT 1') { |row| rows << row } | ||
| end | ||
| assert_equal({"1"=>1}, rows.first) | ||
| end | ||
| def test_encoding | ||
| assert @db.encoding, 'database has encoding' | ||
| end | ||
| def test_changes | ||
| @db.execute("CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, number integer)") | ||
| assert_equal 0, @db.changes | ||
| @db.execute("INSERT INTO items (number) VALUES (10)") | ||
| assert_equal 1, @db.changes | ||
| @db.execute_batch( | ||
| "UPDATE items SET number = (number + :nn) WHERE (number = :n)", | ||
| {"nn" => 20, "n" => 10}) | ||
| assert_equal 1, @db.changes | ||
| assert_equal [[30]], @db.execute("select number from items") | ||
| end | ||
| def test_batch_last_comment_is_processed | ||
| # FIXME: nil as a successful return value is kinda dumb | ||
| assert_nil @db.execute_batch <<-eosql | ||
| CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT); | ||
| -- omg | ||
| eosql | ||
| end | ||
| def test_execute_batch2 | ||
| @db.results_as_hash = true | ||
| return_value = @db.execute_batch2 <<-eosql | ||
| CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT, name string); | ||
| INSERT INTO items (name) VALUES ("foo"); | ||
| INSERT INTO items (name) VALUES ("bar"); | ||
| SELECT * FROM items; | ||
| eosql | ||
| assert_equal return_value, [{"id"=>"1","name"=>"foo"}, {"id"=>"2", "name"=>"bar"}] | ||
| return_value = @db.execute_batch2('SELECT * FROM items;') do |result| | ||
| result["id"] = result["id"].to_i | ||
| result | ||
| end | ||
| assert_equal return_value, [{"id"=>1,"name"=>"foo"}, {"id"=>2, "name"=>"bar"}] | ||
| return_value = @db.execute_batch2('INSERT INTO items (name) VALUES ("oof")') | ||
| assert_equal return_value, [] | ||
| return_value = @db.execute_batch2( | ||
| 'CREATE TABLE employees (id integer PRIMARY KEY AUTOINCREMENT, name string, age integer(3)); | ||
| INSERT INTO employees (age) VALUES (30); | ||
| INSERT INTO employees (age) VALUES (40); | ||
| INSERT INTO employees (age) VALUES (20); | ||
| SELECT age FROM employees;') do |result| | ||
| result["age"] = result["age"].to_i | ||
| result | ||
| end | ||
| assert_equal return_value, [{"age"=>30}, {"age"=>40}, {"age"=>20}] | ||
| return_value = @db.execute_batch2('SELECT name FROM employees'); | ||
| assert_equal return_value, [{"name"=>nil}, {"name"=>nil}, {"name"=>nil}] | ||
| @db.results_as_hash = false | ||
| return_value = @db.execute_batch2( | ||
| 'CREATE TABLE managers (id integer PRIMARY KEY AUTOINCREMENT, age integer(3)); | ||
| INSERT INTO managers (age) VALUES (50); | ||
| INSERT INTO managers (age) VALUES (60); | ||
| SELECT id, age from managers;') do |result| | ||
| result = result.map do |res| | ||
| res.to_i | ||
| end | ||
| result | ||
| end | ||
| assert_equal return_value, [[1, 50], [2, 60]] | ||
| assert_raises (RuntimeError) do | ||
| # "names" is not a valid column | ||
| @db.execute_batch2 'INSERT INTO items (names) VALUES ("bazz")' | ||
| end | ||
| end | ||
| def test_new | ||
| db = SQLite3::Database.new(':memory:') | ||
| assert_instance_of(SQLite3::Database, db) | ||
| ensure | ||
| db.close if db | ||
| end | ||
| def test_open | ||
| db = SQLite3::Database.open(':memory:') | ||
| assert_instance_of(SQLite3::Database, db) | ||
| ensure | ||
| db.close if db | ||
| end | ||
| def test_open_returns_block_result | ||
| result = SQLite3::Database.open(':memory:') do |db| | ||
| :foo | ||
| end | ||
| assert_equal :foo, result | ||
| end | ||
| def test_new_yields_self | ||
| thing = nil | ||
| SQLite3::Database.new(':memory:') do |db| | ||
| thing = db | ||
| end | ||
| assert_instance_of(SQLite3::Database, thing) | ||
| end | ||
| def test_open_yields_self | ||
| thing = nil | ||
| SQLite3::Database.open(':memory:') do |db| | ||
| thing = db | ||
| end | ||
| assert_instance_of(SQLite3::Database, thing) | ||
| end | ||
| def test_new_with_options | ||
| # determine if Ruby is running on Big Endian platform | ||
| utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" | ||
| if RUBY_VERSION >= "1.9" | ||
| db = SQLite3::Database.new(':memory:'.encode(utf16), :utf16 => true) | ||
| else | ||
| db = SQLite3::Database.new(Iconv.conv(utf16, 'UTF-8', ':memory:'), | ||
| :utf16 => true) | ||
| end | ||
| assert_instance_of(SQLite3::Database, db) | ||
| ensure | ||
| db.close if db | ||
| end | ||
| def test_close | ||
| db = SQLite3::Database.new(':memory:') | ||
| db.close | ||
| assert db.closed? | ||
| end | ||
| def test_block_closes_self | ||
| thing = nil | ||
| SQLite3::Database.new(':memory:') do |db| | ||
| thing = db | ||
| assert !thing.closed? | ||
| end | ||
| assert thing.closed? | ||
| end | ||
| def test_open_with_block_closes_self | ||
| thing = nil | ||
| SQLite3::Database.open(':memory:') do |db| | ||
| thing = db | ||
| assert !thing.closed? | ||
| end | ||
| assert thing.closed? | ||
| end | ||
| def test_block_closes_self_even_raised | ||
| thing = nil | ||
| begin | ||
| SQLite3::Database.new(':memory:') do |db| | ||
| thing = db | ||
| raise | ||
| end | ||
| rescue | ||
| end | ||
| assert thing.closed? | ||
| end | ||
| def test_open_with_block_closes_self_even_raised | ||
| thing = nil | ||
| begin | ||
| SQLite3::Database.open(':memory:') do |db| | ||
| thing = db | ||
| raise | ||
| end | ||
| rescue | ||
| end | ||
| assert thing.closed? | ||
| end | ||
| def test_prepare | ||
| db = SQLite3::Database.new(':memory:') | ||
| stmt = db.prepare('select "hello world"') | ||
| assert_instance_of(SQLite3::Statement, stmt) | ||
| ensure | ||
| stmt.close if stmt | ||
| end | ||
| def test_block_prepare_does_not_double_close | ||
| db = SQLite3::Database.new(':memory:') | ||
| r = db.prepare('select "hello world"') do |stmt| | ||
| stmt.close | ||
| :foo | ||
| end | ||
| assert_equal :foo, r | ||
| end | ||
| def test_total_changes | ||
| db = SQLite3::Database.new(':memory:') | ||
| db.execute("create table foo ( a integer primary key, b text )") | ||
| db.execute("insert into foo (b) values ('hello')") | ||
| assert_equal 1, db.total_changes | ||
| end | ||
| def test_execute_returns_list_of_hash | ||
| db = SQLite3::Database.new(':memory:', :results_as_hash => true) | ||
| db.execute("create table foo ( a integer primary key, b text )") | ||
| db.execute("insert into foo (b) values ('hello')") | ||
| rows = db.execute("select * from foo") | ||
| assert_equal [{"a"=>1, "b"=>"hello"}], rows | ||
| end | ||
| def test_execute_yields_hash | ||
| db = SQLite3::Database.new(':memory:', :results_as_hash => true) | ||
| db.execute("create table foo ( a integer primary key, b text )") | ||
| db.execute("insert into foo (b) values ('hello')") | ||
| db.execute("select * from foo") do |row| | ||
| assert_equal({"a"=>1, "b"=>"hello"}, row) | ||
| end | ||
| end | ||
| def test_table_info | ||
| db = SQLite3::Database.new(':memory:', :results_as_hash => true) | ||
| db.execute("create table foo ( a integer primary key, b text )") | ||
| info = [{ | ||
| "name" => "a", | ||
| "pk" => 1, | ||
| "notnull" => 0, | ||
| "type" => "integer", | ||
| "dflt_value" => nil, | ||
| "cid" => 0 | ||
| }, | ||
| { | ||
| "name" => "b", | ||
| "pk" => 0, | ||
| "notnull" => 0, | ||
| "type" => "text", | ||
| "dflt_value" => nil, | ||
| "cid" => 1 | ||
| }] | ||
| assert_equal info, db.table_info('foo') | ||
| end | ||
| def test_total_changes_closed | ||
| db = SQLite3::Database.new(':memory:') | ||
| db.close | ||
| assert_raise(SQLite3::Exception) do | ||
| db.total_changes | ||
| end | ||
| end | ||
| def test_trace_requires_opendb | ||
| @db.close | ||
| assert_raise(SQLite3::Exception) do | ||
| @db.trace { |x| } | ||
| end | ||
| end | ||
| def test_trace_with_block | ||
| result = nil | ||
| @db.trace { |sql| result = sql } | ||
| @db.execute "select 'foo'" | ||
| assert_equal "select 'foo'", result | ||
| end | ||
| def test_trace_with_object | ||
| obj = Class.new { | ||
| attr_accessor :result | ||
| def call sql; @result = sql end | ||
| }.new | ||
| @db.trace(obj) | ||
| @db.execute "select 'foo'" | ||
| assert_equal "select 'foo'", obj.result | ||
| end | ||
| def test_trace_takes_nil | ||
| @db.trace(nil) | ||
| @db.execute "select 'foo'" | ||
| end | ||
| def test_last_insert_row_id_closed | ||
| @db.close | ||
| assert_raise(SQLite3::Exception) do | ||
| @db.last_insert_row_id | ||
| end | ||
| end | ||
| def test_define_function | ||
| called_with = nil | ||
| @db.define_function("hello") do |value| | ||
| called_with = value | ||
| end | ||
| @db.execute("select hello(10)") | ||
| assert_equal 10, called_with | ||
| end | ||
| def test_call_func_arg_type | ||
| called_with = nil | ||
| @db.define_function("hello") do |b, c, d| | ||
| called_with = [b, c, d] | ||
| nil | ||
| end | ||
| @db.execute("select hello(2.2, 'foo', NULL)") | ||
| assert_in_delta(2.2, called_with[0], 0.0001) | ||
| assert_equal("foo", called_with[1]) | ||
| assert_nil(called_with[2]) | ||
| end | ||
| def test_define_varargs | ||
| called_with = nil | ||
| @db.define_function("hello") do |*args| | ||
| called_with = args | ||
| nil | ||
| end | ||
| @db.execute("select hello(2.2, 'foo', NULL)") | ||
| assert_in_delta(2.2, called_with[0], 0.0001) | ||
| assert_equal("foo", called_with[1]) | ||
| assert_nil(called_with[2]) | ||
| end | ||
| def test_call_func_blob | ||
| called_with = nil | ||
| @db.define_function("hello") do |a, b| | ||
| called_with = [a, b, a.length] | ||
| nil | ||
| end | ||
| blob = Blob.new("a\0fine\0kettle\0of\0fish") | ||
| @db.execute("select hello(?, length(?))", [blob, blob]) | ||
| assert_equal [blob, blob.length, 21], called_with | ||
| end | ||
| def test_function_return | ||
| @db.define_function("hello") { |a| 10 } | ||
| assert_equal [10], @db.execute("select hello('world')").first | ||
| end | ||
| def test_function_return_types | ||
| [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| | ||
| @db.define_function("hello") { |a| thing } | ||
| assert_equal [thing], @db.execute("select hello('world')").first | ||
| end | ||
| end | ||
| def test_function_gc_segfault | ||
| @db.create_function("bug", -1) { |func, *values| func.result = values.join } | ||
| # With a lot of data and a lot of threads, try to induce a GC segfault. | ||
| params = Array.new(127, "?" * 28000) | ||
| proc = Proc.new { | ||
| db.execute("select bug(#{Array.new(params.length, "?").join(",")})", params) | ||
| } | ||
| m = Mutex.new | ||
| 30.times.map { Thread.new { m.synchronize { proc.call } } }.each(&:join) | ||
| end | ||
| def test_function_return_type_round_trip | ||
| [10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing| | ||
| @db.define_function("hello") { |a| a } | ||
| assert_equal [thing], @db.execute("select hello(hello(?))", [thing]).first | ||
| end | ||
| end | ||
| def test_define_function_closed | ||
| @db.close | ||
| assert_raise(SQLite3::Exception) do | ||
| @db.define_function('foo') { } | ||
| end | ||
| end | ||
| def test_inerrupt_closed | ||
| @db.close | ||
| assert_raise(SQLite3::Exception) do | ||
| @db.interrupt | ||
| end | ||
| end | ||
| def test_define_aggregate | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| @db.execute "insert into foo ( b ) values ( 'foo' )" | ||
| @db.execute "insert into foo ( b ) values ( 'bar' )" | ||
| @db.execute "insert into foo ( b ) values ( 'baz' )" | ||
| acc = Class.new { | ||
| attr_reader :sum | ||
| alias :finalize :sum | ||
| def initialize | ||
| @sum = 0 | ||
| end | ||
| def step a | ||
| @sum += a | ||
| end | ||
| }.new | ||
| @db.define_aggregator("accumulate", acc) | ||
| value = @db.get_first_value( "select accumulate(a) from foo" ) | ||
| assert_equal 6, value | ||
| end | ||
| def test_authorizer_ok | ||
| statements = [] | ||
| @db.authorizer = Class.new { | ||
| def call action, a, b, c, d; true end | ||
| }.new | ||
| statements << @db.prepare("select 'fooooo'") | ||
| @db.authorizer = Class.new { | ||
| def call action, a, b, c, d; 0 end | ||
| }.new | ||
| statements << @db.prepare("select 'fooooo'") | ||
| ensure | ||
| statements.each(&:close) | ||
| end | ||
| def test_authorizer_ignore | ||
| @db.authorizer = Class.new { | ||
| def call action, a, b, c, d; nil end | ||
| }.new | ||
| stmt = @db.prepare("select 'fooooo'") | ||
| assert_nil stmt.step | ||
| ensure | ||
| stmt.close if stmt | ||
| end | ||
| def test_authorizer_fail | ||
| @db.authorizer = Class.new { | ||
| def call action, a, b, c, d; false end | ||
| }.new | ||
| assert_raises(SQLite3::AuthorizationException) do | ||
| @db.prepare("select 'fooooo'") | ||
| end | ||
| end | ||
| def test_remove_auth | ||
| @db.authorizer = Class.new { | ||
| def call action, a, b, c, d; false end | ||
| }.new | ||
| assert_raises(SQLite3::AuthorizationException) do | ||
| @db.prepare("select 'fooooo'") | ||
| end | ||
| @db.authorizer = nil | ||
| s = @db.prepare("select 'fooooo'") | ||
| ensure | ||
| s.close if s | ||
| end | ||
| def test_close_with_open_statements | ||
| s = @db.prepare("select 'foo'") | ||
| assert_raises(SQLite3::BusyException) do | ||
| @db.close | ||
| end | ||
| ensure | ||
| s.close if s | ||
| end | ||
| def test_execute_with_empty_bind_params | ||
| assert_equal [['foo']], @db.execute("select 'foo'", []) | ||
| end | ||
| def test_query_with_named_bind_params | ||
| resultset = @db.query("select :n", {'n' => 'foo'}) | ||
| assert_equal [['foo']], resultset.to_a | ||
| ensure | ||
| resultset.close if resultset | ||
| end | ||
| def test_execute_with_named_bind_params | ||
| assert_equal [['foo']], @db.execute("select :n", {'n' => 'foo'}) | ||
| end | ||
| def test_strict_mode | ||
| unless Gem::Requirement.new(">= 3.29.0").satisfied_by?(Gem::Version.new(SQLite3::SQLITE_VERSION)) | ||
| skip("strict mode feature not available in #{SQLite3::SQLITE_VERSION}") | ||
| end | ||
| db = SQLite3::Database.new(':memory:') | ||
| db.execute('create table numbers (val int);') | ||
| db.execute('create index index_numbers_nope ON numbers ("nope");') # nothing raised | ||
| db = SQLite3::Database.new(':memory:', :strict => true) | ||
| db.execute('create table numbers (val int);') | ||
| error = assert_raises SQLite3::SQLException do | ||
| db.execute('create index index_numbers_nope ON numbers ("nope");') | ||
| end | ||
| assert_includes error.message, "no such column: nope" | ||
| end | ||
| def test_load_extension_with_nonstring_argument | ||
| db = SQLite3::Database.new(':memory:') | ||
| skip("extensions are not enabled") unless db.respond_to?(:load_extension) | ||
| assert_raises(TypeError) { db.load_extension(1) } | ||
| assert_raises(TypeError) { db.load_extension(Pathname.new("foo.so")) } | ||
| end | ||
| def test_raw_float_infinity | ||
| # https://github.com/sparklemotion/sqlite3-ruby/issues/396 | ||
| skip if SQLite3::SQLITE_LOADED_VERSION >= "3.43.0" | ||
| db = SQLite3::Database.new ":memory:" | ||
| db.execute("create table foo (temperature float)") | ||
| db.execute("insert into foo values (?)", 37.5) | ||
| db.execute("insert into foo values (?)", Float::INFINITY) | ||
| assert_equal Float::INFINITY, db.execute("select avg(temperature) from foo").first.first | ||
| end | ||
| def test_default_transaction_mode | ||
| tf = Tempfile.new 'database_default_transaction_mode' | ||
| SQLite3::Database.new(tf.path) do |db| | ||
| db.execute("create table foo (score int)") | ||
| db.execute("insert into foo values (?)", 1) | ||
| end | ||
| test_cases = [ | ||
| {mode: nil, read: true, write: true}, | ||
| {mode: :deferred, read: true, write: true}, | ||
| {mode: :immediate, read: true, write: false}, | ||
| {mode: :exclusive, read: false, write: false}, | ||
| ] | ||
| test_cases.each do |item| | ||
| db = SQLite3::Database.new tf.path, default_transaction_mode: item[:mode] | ||
| db2 = SQLite3::Database.new tf.path | ||
| db.transaction do | ||
| sql_for_read_test = "select * from foo" | ||
| if item[:read] | ||
| assert_nothing_raised{ db2.execute(sql_for_read_test) } | ||
| else | ||
| assert_raises(SQLite3::BusyException){ db2.execute(sql_for_read_test) } | ||
| end | ||
| sql_for_write_test = "insert into foo values (2)" | ||
| if item[:write] | ||
| assert_nothing_raised{ db2.execute(sql_for_write_test) } | ||
| else | ||
| assert_raises(SQLite3::BusyException){ db2.execute(sql_for_write_test) } | ||
| end | ||
| end | ||
| ensure | ||
| db.close if db && !db.closed? | ||
| db2.close if db2 && !db2.closed? | ||
| end | ||
| ensure | ||
| tf.unlink if tf | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestDeprecated < SQLite3::TestCase | ||
| def setup | ||
| super | ||
| @warn_before = $-w | ||
| $-w = false | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @db.execute 'CREATE TABLE test_table (name text, age int)' | ||
| end | ||
| def teardown | ||
| super | ||
| $-w = @warn_before | ||
| @db.close | ||
| end | ||
| def test_query_with_many_bind_params_not_nil | ||
| rs = @db.query('select ?, ?', 1, 2) | ||
| assert_equal [[1, 2]], rs.to_a | ||
| rs.close | ||
| end | ||
| def test_execute_with_many_bind_params_not_nil | ||
| assert_equal [[1, 2]], @db.execute("select ?, ?", 1, 2).to_a | ||
| end | ||
| def test_query_with_many_bind_params | ||
| rs = @db.query("select ?, ?", nil, 1) | ||
| assert_equal [[nil, 1]], rs.to_a | ||
| rs.close | ||
| end | ||
| def test_query_with_nil_bind_params | ||
| rs = @db.query("select 'foo'", nil) | ||
| assert_equal [['foo']], rs.to_a | ||
| rs.close | ||
| end | ||
| def test_execute_with_many_bind_params | ||
| assert_equal [[nil, 1]], @db.execute("select ?, ?", nil, 1) | ||
| end | ||
| def test_execute_with_nil_bind_params | ||
| assert_equal [['foo']], @db.execute("select 'foo'", nil) | ||
| end | ||
| end | ||
| end |
| # -*- coding: utf-8 -*- | ||
| require 'helper' | ||
| module SQLite3 | ||
| class TestEncoding < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @create = "create table ex(id int, data string)" | ||
| @insert = "insert into ex(id, data) values (?, ?)" | ||
| @db.execute(@create); | ||
| end | ||
| def teardown | ||
| @db.close | ||
| end | ||
| def test_select_encoding_on_utf_16 | ||
| str = "foo" | ||
| utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" | ||
| db = SQLite3::Database.new(':memory:'.encode(utf16)) | ||
| db.execute @create | ||
| db.execute "insert into ex (id, data) values (1, \"#{str}\")" | ||
| stmt = db.prepare 'select * from ex where data = ?' | ||
| ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each do |enc| | ||
| stmt.bind_param 1, str.encode(enc) | ||
| assert_equal 1, stmt.to_a.length | ||
| stmt.reset! | ||
| end | ||
| stmt.close | ||
| end | ||
| def test_insert_encoding | ||
| str = "foo" | ||
| utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" | ||
| db = SQLite3::Database.new(':memory:'.encode(utf16)) | ||
| db.execute @create | ||
| stmt = db.prepare @insert | ||
| ['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each_with_index do |enc,i| | ||
| stmt.bind_param 1, i | ||
| stmt.bind_param 2, str.encode(enc) | ||
| stmt.to_a | ||
| stmt.reset! | ||
| end | ||
| stmt.close | ||
| db.execute('select data from ex').flatten.each do |s| | ||
| assert_equal str, s | ||
| end | ||
| end | ||
| def test_default_internal_is_honored | ||
| warn_before = $-w | ||
| $-w = false | ||
| before_enc = Encoding.default_internal | ||
| str = "壁に耳あり、障子に目あり" | ||
| stmt = @db.prepare('insert into ex(data) values (?)') | ||
| stmt.bind_param 1, str | ||
| stmt.step | ||
| stmt.close | ||
| Encoding.default_internal = 'EUC-JP' | ||
| string = @db.execute('select data from ex').first.first | ||
| assert_equal Encoding.default_internal, string.encoding | ||
| assert_equal str.encode('EUC-JP'), string | ||
| assert_equal str, string.encode(str.encoding) | ||
| ensure | ||
| Encoding.default_internal = before_enc | ||
| $-w = warn_before | ||
| end | ||
| def test_blob_is_binary | ||
| str = "猫舌" | ||
| @db.execute('create table foo(data text)') | ||
| stmt = @db.prepare('insert into foo(data) values (?)') | ||
| stmt.bind_param(1, SQLite3::Blob.new(str)) | ||
| stmt.step | ||
| stmt.close | ||
| string = @db.execute('select data from foo').first.first | ||
| assert_equal Encoding.find('ASCII-8BIT'), string.encoding | ||
| assert_equal str, string.force_encoding('UTF-8') | ||
| end | ||
| def test_blob_is_ascii8bit | ||
| str = "猫舌" | ||
| @db.execute('create table foo(data text)') | ||
| stmt = @db.prepare('insert into foo(data) values (?)') | ||
| stmt.bind_param(1, str.dup.force_encoding("ASCII-8BIT")) | ||
| stmt.step | ||
| stmt.close | ||
| string = @db.execute('select data from foo').first.first | ||
| assert_equal Encoding.find('ASCII-8BIT'), string.encoding | ||
| assert_equal str, string.force_encoding('UTF-8') | ||
| end | ||
| def test_blob_with_eucjp | ||
| str = "猫舌".encode("EUC-JP") | ||
| @db.execute('create table foo(data text)') | ||
| stmt = @db.prepare('insert into foo(data) values (?)') | ||
| stmt.bind_param(1, SQLite3::Blob.new(str)) | ||
| stmt.step | ||
| stmt.close | ||
| string = @db.execute('select data from foo').first.first | ||
| assert_equal Encoding.find('ASCII-8BIT'), string.encoding | ||
| assert_equal str, string.force_encoding('EUC-JP') | ||
| end | ||
| def test_db_with_eucjp | ||
| db = SQLite3::Database.new(':memory:'.encode('EUC-JP')) | ||
| assert_equal(Encoding.find('UTF-8'), db.encoding) | ||
| end | ||
| def test_db_with_utf16 | ||
| utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" | ||
| db = SQLite3::Database.new(':memory:'.encode(utf16)) | ||
| assert_equal(Encoding.find(utf16), db.encoding) | ||
| end | ||
| def test_statement_eucjp | ||
| str = "猫舌" | ||
| @db.execute("insert into ex(data) values ('#{str}')".encode('EUC-JP')) | ||
| row = @db.execute("select data from ex") | ||
| assert_equal @db.encoding, row.first.first.encoding | ||
| assert_equal str, row.first.first | ||
| end | ||
| def test_statement_utf8 | ||
| str = "猫舌" | ||
| @db.execute("insert into ex(data) values ('#{str}')") | ||
| row = @db.execute("select data from ex") | ||
| assert_equal @db.encoding, row.first.first.encoding | ||
| assert_equal str, row.first.first | ||
| end | ||
| def test_encoding | ||
| assert_equal Encoding.find("UTF-8"), @db.encoding | ||
| end | ||
| def test_utf_8 | ||
| str = "猫舌" | ||
| @db.execute(@insert, [10, str]) | ||
| row = @db.execute("select data from ex") | ||
| assert_equal @db.encoding, row.first.first.encoding | ||
| assert_equal str, row.first.first | ||
| end | ||
| def test_euc_jp | ||
| str = "猫舌".encode('EUC-JP') | ||
| @db.execute(@insert, [10, str]) | ||
| row = @db.execute("select data from ex") | ||
| assert_equal @db.encoding, row.first.first.encoding | ||
| assert_equal str.encode('UTF-8'), row.first.first | ||
| end | ||
| end if RUBY_VERSION >= '1.9.1' | ||
| end |
| require 'helper' | ||
| class TC_Integration_Aggregate < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(":memory:") | ||
| @db.transaction do | ||
| @db.execute "create table foo ( a integer primary key, b text, c integer )" | ||
| @db.execute "insert into foo ( b, c ) values ( 'foo', 10 )" | ||
| @db.execute "insert into foo ( b, c ) values ( 'bar', 11 )" | ||
| @db.execute "insert into foo ( b, c ) values ( 'bar', 12 )" | ||
| end | ||
| end | ||
| def teardown | ||
| @db.close | ||
| end | ||
| def test_create_aggregate_without_block | ||
| step = proc do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| final = proc { |ctx| ctx.result = ctx[:sum] } | ||
| @db.create_aggregate( "accumulate", 1, step, final ) | ||
| value = @db.get_first_value( "select accumulate(a) from foo" ) | ||
| assert_equal 6, value | ||
| # calling #get_first_value twice don't add up to the latest result | ||
| value = @db.get_first_value( "select accumulate(a) from foo" ) | ||
| assert_equal 6, value | ||
| end | ||
| def test_create_aggregate_with_block | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| value = @db.get_first_value( "select accumulate(a) from foo" ) | ||
| assert_equal 6, value | ||
| end | ||
| def test_create_aggregate_with_group_by | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| values = @db.execute( "select b, accumulate(c) from foo group by b order by b" ) | ||
| assert_equal "bar", values[0][0] | ||
| assert_equal 23, values[0][1] | ||
| assert_equal "foo", values[1][0] | ||
| assert_equal 10, values[1][1] | ||
| end | ||
| def test_create_aggregate_with_the_same_function_twice_in_a_query | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| values = @db.get_first_row( "select accumulate(a), accumulate(c) from foo" ) | ||
| assert_equal 6, values[0] | ||
| assert_equal 33, values[1] | ||
| end | ||
| def test_create_aggregate_with_two_different_functions | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| @db.create_aggregate( "multiply", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 1 | ||
| ctx[:sum] *= a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| GC.start | ||
| values = @db.get_first_row( "select accumulate(a), multiply(c) from foo" ) | ||
| assert_equal 6, values[0] | ||
| assert_equal 1320, values[1] | ||
| value = @db.get_first_value( "select accumulate(c) from foo") | ||
| assert_equal 33, value | ||
| value = @db.get_first_value( "select multiply(a) from foo") | ||
| assert_equal 6, value | ||
| end | ||
| def test_create_aggregate_overwrite_function | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| value = @db.get_first_value( "select accumulate(c) from foo") | ||
| assert_equal 33, value | ||
| GC.start | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 1 | ||
| ctx[:sum] *= a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| value = @db.get_first_value( "select accumulate(c) from foo") | ||
| assert_equal 1320, value | ||
| end | ||
| def test_create_aggregate_overwrite_function_with_different_arity | ||
| @db.create_aggregate( "accumulate", -1 ) do | ||
| step do |ctx,*args| | ||
| ctx[:sum] ||= 0 | ||
| args.each { |a| ctx[:sum] += a.to_i } | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| @db.create_aggregate( "accumulate", 2 ) do | ||
| step do |ctx,a,b| | ||
| ctx[:sum] ||= 1 | ||
| ctx[:sum] *= (a.to_i + b.to_i) | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] } | ||
| end | ||
| GC.start | ||
| values = @db.get_first_row( "select accumulate(c), accumulate(a,c) from foo") | ||
| assert_equal 33, values[0] | ||
| assert_equal 2145, values[1] | ||
| end | ||
| def test_create_aggregate_with_invalid_arity | ||
| assert_raise ArgumentError do | ||
| @db.create_aggregate( "accumulate", 1000 ) do | ||
| step {|ctx,*args| } | ||
| finalize { |ctx| } | ||
| end | ||
| end | ||
| end | ||
| class CustomException < Exception | ||
| end | ||
| def test_create_aggregate_with_exception_in_step | ||
| @db.create_aggregate( "raiseexception", 1 ) do | ||
| step do |ctx,a| | ||
| raise CustomException.new( "bogus aggregate handler" ) | ||
| end | ||
| finalize { |ctx| ctx.result = 42 } | ||
| end | ||
| assert_raise CustomException do | ||
| @db.get_first_value( "select raiseexception(a) from foo") | ||
| end | ||
| end | ||
| def test_create_aggregate_with_exception_in_finalize | ||
| @db.create_aggregate( "raiseexception", 1 ) do | ||
| step do |ctx,a| | ||
| raise CustomException.new( "bogus aggregate handler" ) | ||
| end | ||
| finalize do |ctx| | ||
| raise CustomException.new( "bogus aggregate handler" ) | ||
| end | ||
| end | ||
| assert_raise CustomException do | ||
| @db.get_first_value( "select raiseexception(a) from foo") | ||
| end | ||
| end | ||
| def test_create_aggregate_with_no_data | ||
| @db.create_aggregate( "accumulate", 1 ) do | ||
| step do |ctx,a| | ||
| ctx[:sum] ||= 0 | ||
| ctx[:sum] += a.to_i | ||
| end | ||
| finalize { |ctx| ctx.result = ctx[:sum] || 0 } | ||
| end | ||
| value = @db.get_first_value( | ||
| "select accumulate(a) from foo where a = 100" ) | ||
| assert_equal 0, value | ||
| end | ||
| class AggregateHandler | ||
| class << self | ||
| def arity; 1; end | ||
| def text_rep; SQLite3::Constants::TextRep::ANY; end | ||
| def name; "multiply"; end | ||
| end | ||
| def step(ctx, a) | ||
| ctx[:buffer] ||= 1 | ||
| ctx[:buffer] *= a.to_i | ||
| end | ||
| def finalize(ctx); ctx.result = ctx[:buffer]; end | ||
| end | ||
| def test_aggregate_initialized_twice | ||
| initialized = 0 | ||
| handler = Class.new(AggregateHandler) do | ||
| define_method(:initialize) do | ||
| initialized += 1 | ||
| super() | ||
| end | ||
| end | ||
| @db.create_aggregate_handler handler | ||
| @db.get_first_value( "select multiply(a) from foo" ) | ||
| @db.get_first_value( "select multiply(a) from foo" ) | ||
| assert_equal 2, initialized | ||
| end | ||
| def test_create_aggregate_handler_call_with_wrong_arity | ||
| @db.create_aggregate_handler AggregateHandler | ||
| assert_raise(SQLite3::SQLException) do | ||
| @db.get_first_value( "select multiply(a,c) from foo" ) | ||
| end | ||
| end | ||
| class RaiseExceptionStepAggregateHandler | ||
| class << self | ||
| def arity; 1; end | ||
| def text_rep; SQLite3::Constants::TextRep::ANY; end | ||
| def name; "raiseexception"; end | ||
| end | ||
| def step(ctx, a) | ||
| raise CustomException.new( "bogus aggregate handler" ) | ||
| end | ||
| def finalize(ctx); ctx.result = nil; end | ||
| end | ||
| def test_create_aggregate_handler_with_exception_step | ||
| @db.create_aggregate_handler RaiseExceptionStepAggregateHandler | ||
| assert_raise CustomException do | ||
| @db.get_first_value( "select raiseexception(a) from foo") | ||
| end | ||
| end | ||
| class RaiseExceptionNewAggregateHandler | ||
| class << self | ||
| def name; "raiseexception"; end | ||
| end | ||
| def initialize | ||
| raise CustomException.new( "bogus aggregate handler" ) | ||
| end | ||
| def step(ctx, a); end | ||
| def finalize(ctx); ctx.result = nil; end | ||
| end | ||
| def test_create_aggregate_handler_with_exception_new | ||
| @db.create_aggregate_handler RaiseExceptionNewAggregateHandler | ||
| assert_raise CustomException do | ||
| @db.get_first_value( "select raiseexception(a) from foo") | ||
| end | ||
| end | ||
| def test_create_aggregate_handler | ||
| @db.create_aggregate_handler AggregateHandler | ||
| value = @db.get_first_value( "select multiply(a) from foo" ) | ||
| assert_equal 6, value | ||
| end | ||
| class AccumulateAggregator | ||
| def step(*args) | ||
| @sum ||= 0 | ||
| args.each { |a| @sum += a.to_i } | ||
| end | ||
| def finalize | ||
| @sum | ||
| end | ||
| end | ||
| class AccumulateAggregator2 | ||
| def step(a, b) | ||
| @sum ||= 1 | ||
| @sum *= (a.to_i + b.to_i) | ||
| end | ||
| def finalize | ||
| @sum | ||
| end | ||
| end | ||
| def test_define_aggregator_with_two_different_arities | ||
| @db.define_aggregator( "accumulate", AccumulateAggregator.new ) | ||
| @db.define_aggregator( "accumulate", AccumulateAggregator2.new ) | ||
| GC.start | ||
| values = @db.get_first_row( "select accumulate(c), accumulate(a,c) from foo") | ||
| assert_equal 33, values[0] | ||
| assert_equal 2145, values[1] | ||
| end | ||
| end |
| require 'helper' | ||
| class TC_OpenClose < SQLite3::TestCase | ||
| def test_create_close | ||
| begin | ||
| db = SQLite3::Database.new( "test-create.db" ) | ||
| assert File.exist?( "test-create.db" ) | ||
| assert_nothing_raised { db.close } | ||
| ensure | ||
| File.delete( "test-create.db" ) rescue nil | ||
| end | ||
| end | ||
| def test_open_close | ||
| begin | ||
| File.open( "test-open.db", "w" ) { |f| } | ||
| assert File.exist?( "test-open.db" ) | ||
| db = SQLite3::Database.new( "test-open.db" ) | ||
| assert_nothing_raised { db.close } | ||
| ensure | ||
| File.delete( "test-open.db" ) rescue nil | ||
| end | ||
| end | ||
| def test_bad_open | ||
| assert_raise( SQLite3::CantOpenException ) do | ||
| SQLite3::Database.new( "." ) | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| require 'thread' | ||
| require 'benchmark' | ||
| class TC_Integration_Pending < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new("test.db") | ||
| @db.transaction do | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| @db.execute "insert into foo ( b ) values ( 'foo' )" | ||
| @db.execute "insert into foo ( b ) values ( 'bar' )" | ||
| @db.execute "insert into foo ( b ) values ( 'baz' )" | ||
| end | ||
| end | ||
| def teardown | ||
| @db.close | ||
| File.delete( "test.db" ) | ||
| end | ||
| def test_busy_handler_outwait | ||
| skip("not working in 1.9") if RUBY_VERSION >= '1.9' | ||
| busy = Mutex.new | ||
| busy.lock | ||
| handler_call_count = 0 | ||
| t = Thread.new(busy) do |locker| | ||
| begin | ||
| db2 = SQLite3::Database.open( "test.db" ) | ||
| db2.transaction( :exclusive ) do | ||
| locker.lock | ||
| end | ||
| ensure | ||
| db2.close if db2 | ||
| end | ||
| end | ||
| @db.busy_handler do |data,count| | ||
| handler_call_count += 1 | ||
| busy.unlock | ||
| true | ||
| end | ||
| assert_nothing_raised do | ||
| @db.execute "insert into foo (b) values ( 'from 2' )" | ||
| end | ||
| t.join | ||
| assert_equal 1, handler_call_count | ||
| end | ||
| def test_busy_handler_impatient | ||
| busy = Mutex.new | ||
| busy.lock | ||
| handler_call_count = 0 | ||
| t = Thread.new do | ||
| begin | ||
| db2 = SQLite3::Database.open( "test.db" ) | ||
| db2.transaction( :exclusive ) do | ||
| busy.lock | ||
| end | ||
| ensure | ||
| db2.close if db2 | ||
| end | ||
| end | ||
| sleep 1 | ||
| @db.busy_handler do | ||
| handler_call_count += 1 | ||
| false | ||
| end | ||
| assert_raise( SQLite3::BusyException ) do | ||
| @db.execute "insert into foo (b) values ( 'from 2' )" | ||
| end | ||
| busy.unlock | ||
| t.join | ||
| assert_equal 1, handler_call_count | ||
| end | ||
| def test_busy_timeout | ||
| @db.busy_timeout 1000 | ||
| busy = Mutex.new | ||
| busy.lock | ||
| t = Thread.new do | ||
| begin | ||
| db2 = SQLite3::Database.open( "test.db" ) | ||
| db2.transaction( :exclusive ) do | ||
| busy.lock | ||
| end | ||
| ensure | ||
| db2.close if db2 | ||
| end | ||
| end | ||
| sleep 1 | ||
| time = Benchmark.measure do | ||
| assert_raise( SQLite3::BusyException ) do | ||
| @db.execute "insert into foo (b) values ( 'from 2' )" | ||
| end | ||
| end | ||
| busy.unlock | ||
| t.join | ||
| assert time.real*1000 >= 1000 | ||
| end | ||
| end |
| require 'helper' | ||
| class TC_ResultSet < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(":memory:") | ||
| @db.transaction do | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| @db.execute "insert into foo ( b ) values ( 'foo' )" | ||
| @db.execute "insert into foo ( b ) values ( 'bar' )" | ||
| @db.execute "insert into foo ( b ) values ( 'baz' )" | ||
| end | ||
| @stmt = @db.prepare( "select * from foo where a in ( ?, ? )" ) | ||
| @result = @stmt.execute | ||
| end | ||
| def teardown | ||
| @stmt.close | ||
| @db.close | ||
| end | ||
| def test_reset_unused | ||
| assert_nothing_raised { @result.reset } | ||
| assert @result.to_a.empty? | ||
| end | ||
| def test_reset_used | ||
| @result.to_a | ||
| assert_nothing_raised { @result.reset } | ||
| assert @result.to_a.empty? | ||
| end | ||
| def test_reset_with_bind | ||
| @result.to_a | ||
| assert_nothing_raised { @result.reset( 1, 2 ) } | ||
| assert_equal 2, @result.to_a.length | ||
| end | ||
| def test_eof_inner | ||
| @result.reset( 1 ) | ||
| assert !@result.eof? | ||
| end | ||
| def test_eof_edge | ||
| @result.reset( 1 ) | ||
| @result.next # to first row | ||
| @result.next # to end of result set | ||
| assert @result.eof? | ||
| end | ||
| def test_next_eof | ||
| @result.reset( 1 ) | ||
| assert_not_nil @result.next | ||
| assert_nil @result.next | ||
| end | ||
| def test_next_no_type_translation_no_hash | ||
| @result.reset( 1 ) | ||
| assert_equal [ 1, "foo" ], @result.next | ||
| end | ||
| def test_next_type_translation | ||
| @result.reset( 1 ) | ||
| assert_equal [ 1, "foo" ], @result.next | ||
| end | ||
| def test_next_type_translation_with_untyped_column | ||
| @db.query( "select count(*) from foo" ) do |result| | ||
| assert_equal [3], result.next | ||
| end | ||
| end | ||
| def test_type_translation_with_null_column | ||
| time = '1974-07-25 14:39:00' | ||
| @db.execute "create table bar ( a integer, b time, c string )" | ||
| @db.execute "insert into bar (a, b, c) values (NULL, '#{time}', 'hello')" | ||
| @db.execute "insert into bar (a, b, c) values (1, NULL, 'hello')" | ||
| @db.execute "insert into bar (a, b, c) values (2, '#{time}', NULL)" | ||
| @db.query( "select * from bar" ) do |result| | ||
| assert_equal [nil, time, 'hello'], result.next | ||
| assert_equal [1, nil, 'hello'], result.next | ||
| assert_equal [2, time, nil], result.next | ||
| end | ||
| end | ||
| def test_real_translation | ||
| @db.execute('create table foo_real(a real)') | ||
| @db.execute('insert into foo_real values (42)' ) | ||
| @db.query('select a, sum(a), typeof(a), typeof(sum(a)) from foo_real') do |result| | ||
| result = result.next | ||
| assert result[0].is_a?(Float) | ||
| assert result[1].is_a?(Float) | ||
| assert result[2].is_a?(String) | ||
| assert result[3].is_a?(String) | ||
| end | ||
| end | ||
| def test_next_results_as_hash | ||
| @db.results_as_hash = true | ||
| @result.reset( 1 ) | ||
| hash = @result.next | ||
| assert_equal( { "a" => 1, "b" => "foo" }, | ||
| hash ) | ||
| assert_equal hash[@result.columns[0]], 1 | ||
| assert_equal hash[@result.columns[1]], "foo" | ||
| end | ||
| def test_each | ||
| called = 0 | ||
| @result.reset( 1, 2 ) | ||
| @result.each { |row| called += 1 } | ||
| assert_equal 2, called | ||
| end | ||
| def test_enumerable | ||
| @result.reset( 1, 2 ) | ||
| assert_equal 2, @result.to_a.length | ||
| end | ||
| def test_types | ||
| assert_equal [ "integer", "text" ], @result.types | ||
| end | ||
| def test_columns | ||
| assert_equal [ "a", "b" ], @result.columns | ||
| end | ||
| def test_close | ||
| stmt = @db.prepare( "select * from foo" ) | ||
| result = stmt.execute | ||
| assert !result.closed? | ||
| result.close | ||
| assert result.closed? | ||
| assert stmt.closed? | ||
| assert_raise( SQLite3::Exception ) { result.reset } | ||
| assert_raise( SQLite3::Exception ) { result.next } | ||
| assert_raise( SQLite3::Exception ) { result.each } | ||
| assert_raise( SQLite3::Exception ) { result.close } | ||
| assert_raise( SQLite3::Exception ) { result.types } | ||
| assert_raise( SQLite3::Exception ) { result.columns } | ||
| end | ||
| end |
| require 'helper' | ||
| class TC_Statement < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(":memory:") | ||
| @db.transaction do | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| @db.execute "insert into foo ( b ) values ( 'foo' )" | ||
| @db.execute "insert into foo ( b ) values ( 'bar' )" | ||
| @db.execute "insert into foo ( b ) values ( 'baz' )" | ||
| end | ||
| @stmt = @db.prepare( "select * from foo where a in ( ?, :named )" ) | ||
| end | ||
| def teardown | ||
| @stmt.close | ||
| @db.close | ||
| end | ||
| def test_remainder_empty | ||
| assert_equal "", @stmt.remainder | ||
| end | ||
| def test_remainder_nonempty | ||
| called = false | ||
| @db.prepare( "select * from foo;\n blah" ) do |stmt| | ||
| called = true | ||
| assert_equal "\n blah", stmt.remainder | ||
| end | ||
| assert called | ||
| end | ||
| def test_bind_params_empty | ||
| assert_nothing_raised { @stmt.bind_params } | ||
| assert @stmt.execute!.empty? | ||
| end | ||
| def test_bind_params_array | ||
| @stmt.bind_params 1, 2 | ||
| assert_equal 2, @stmt.execute!.length | ||
| end | ||
| def test_bind_params_hash | ||
| @stmt.bind_params ":named" => 2 | ||
| assert_equal 1, @stmt.execute!.length | ||
| end | ||
| def test_bind_params_hash_without_colon | ||
| @stmt.bind_params "named" => 2 | ||
| assert_equal 1, @stmt.execute!.length | ||
| end | ||
| def test_bind_params_hash_as_symbol | ||
| @stmt.bind_params :named => 2 | ||
| assert_equal 1, @stmt.execute!.length | ||
| end | ||
| def test_bind_params_mixed | ||
| @stmt.bind_params( 1, ":named" => 2 ) | ||
| assert_equal 2, @stmt.execute!.length | ||
| end | ||
| def test_bind_param_by_index | ||
| @stmt.bind_params( 1, 2 ) | ||
| assert_equal 2, @stmt.execute!.length | ||
| end | ||
| def test_bind_param_by_name_bad | ||
| assert_raise( SQLite3::Exception ) { @stmt.bind_param( "@named", 2 ) } | ||
| end | ||
| def test_bind_param_by_name_good | ||
| @stmt.bind_param( ":named", 2 ) | ||
| assert_equal 1, @stmt.execute!.length | ||
| end | ||
| def test_bind_param_with_various_types | ||
| @db.transaction do | ||
| @db.execute "create table all_types ( a integer primary key, b float, c string, d integer )" | ||
| @db.execute "insert into all_types ( b, c, d ) values ( 1.5, 'hello', 68719476735 )" | ||
| end | ||
| assert_equal 1, @db.execute( "select * from all_types where b = ?", 1.5 ).length | ||
| assert_equal 1, @db.execute( "select * from all_types where c = ?", 'hello').length | ||
| assert_equal 1, @db.execute( "select * from all_types where d = ?", 68719476735).length | ||
| end | ||
| def test_execute_no_bind_no_block | ||
| assert_instance_of SQLite3::ResultSet, @stmt.execute | ||
| end | ||
| def test_execute_with_bind_no_block | ||
| assert_instance_of SQLite3::ResultSet, @stmt.execute( 1, 2 ) | ||
| end | ||
| def test_execute_no_bind_with_block | ||
| called = false | ||
| @stmt.execute { |row| called = true } | ||
| assert called | ||
| end | ||
| def test_execute_with_bind_with_block | ||
| called = 0 | ||
| @stmt.execute( 1, 2 ) { |row| called += 1 } | ||
| assert_equal 1, called | ||
| end | ||
| def test_reexecute | ||
| r = @stmt.execute( 1, 2 ) | ||
| assert_equal 2, r.to_a.length | ||
| assert_nothing_raised { r = @stmt.execute( 1, 2 ) } | ||
| assert_equal 2, r.to_a.length | ||
| end | ||
| def test_execute_bang_no_bind_no_block | ||
| assert @stmt.execute!.empty? | ||
| end | ||
| def test_execute_bang_with_bind_no_block | ||
| assert_equal 2, @stmt.execute!( 1, 2 ).length | ||
| end | ||
| def test_execute_bang_no_bind_with_block | ||
| called = 0 | ||
| @stmt.execute! { |row| called += 1 } | ||
| assert_equal 0, called | ||
| end | ||
| def test_execute_bang_with_bind_with_block | ||
| called = 0 | ||
| @stmt.execute!( 1, 2 ) { |row| called += 1 } | ||
| assert_equal 2, called | ||
| end | ||
| def test_columns | ||
| c1 = @stmt.columns | ||
| c2 = @stmt.columns | ||
| assert_same c1, c2 | ||
| assert_equal 2, c1.length | ||
| end | ||
| def test_columns_computed | ||
| called = false | ||
| @db.prepare( "select count(*) from foo" ) do |stmt| | ||
| called = true | ||
| assert_equal [ "count(*)" ], stmt.columns | ||
| end | ||
| assert called | ||
| end | ||
| def test_types | ||
| t1 = @stmt.types | ||
| t2 = @stmt.types | ||
| assert_same t1, t2 | ||
| assert_equal 2, t1.length | ||
| end | ||
| def test_types_computed | ||
| called = false | ||
| @db.prepare( "select count(*) from foo" ) do |stmt| | ||
| called = true | ||
| assert_equal [ nil ], stmt.types | ||
| end | ||
| assert called | ||
| end | ||
| def test_close | ||
| stmt = @db.prepare( "select * from foo" ) | ||
| assert !stmt.closed? | ||
| stmt.close | ||
| assert stmt.closed? | ||
| assert_raise( SQLite3::Exception ) { stmt.execute } | ||
| assert_raise( SQLite3::Exception ) { stmt.execute! } | ||
| assert_raise( SQLite3::Exception ) { stmt.close } | ||
| assert_raise( SQLite3::Exception ) { stmt.bind_params 5 } | ||
| assert_raise( SQLite3::Exception ) { stmt.bind_param 1, 5 } | ||
| assert_raise( SQLite3::Exception ) { stmt.columns } | ||
| assert_raise( SQLite3::Exception ) { stmt.types } | ||
| end | ||
| def test_committing_tx_with_statement_active | ||
| called = false | ||
| @db.prepare( "select count(*) from foo" ) do |stmt| | ||
| called = true | ||
| count = stmt.execute!.first.first.to_i | ||
| @db.transaction do | ||
| @db.execute "insert into foo ( b ) values ( 'hello' )" | ||
| end | ||
| new_count = stmt.execute!.first.first.to_i | ||
| assert_equal new_count, count+1 | ||
| end | ||
| assert called | ||
| end | ||
| end |
| require 'helper' | ||
| class TC_Database_Integration < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(":memory:") | ||
| @db.transaction do | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| @db.execute "insert into foo ( b ) values ( 'foo' )" | ||
| @db.execute "insert into foo ( b ) values ( 'bar' )" | ||
| @db.execute "insert into foo ( b ) values ( 'baz' )" | ||
| end | ||
| end | ||
| def teardown | ||
| @db.close | ||
| end | ||
| def test_table_info_with_type_translation_active | ||
| assert_nothing_raised { @db.table_info("foo") } | ||
| end | ||
| def test_table_info_with_defaults_for_version_3_3_8_and_higher | ||
| @db.transaction do | ||
| @db.execute "create table defaults_test ( a string default NULL, b string default 'Hello', c string default '--- []\n' )" | ||
| data = @db.table_info( "defaults_test" ) | ||
| assert_equal({"name" => "a", "type" => "string", "dflt_value" => nil, "notnull" => 0, "cid" => 0, "pk" => 0}, | ||
| data[0]) | ||
| assert_equal({"name" => "b", "type" => "string", "dflt_value" => "Hello", "notnull" => 0, "cid" => 1, "pk" => 0}, | ||
| data[1]) | ||
| assert_equal({"name" => "c", "type" => "string", "dflt_value" => "--- []\n", "notnull" => 0, "cid" => 2, "pk" => 0}, | ||
| data[2]) | ||
| end | ||
| end | ||
| def test_table_info_without_defaults_for_version_3_3_8_and_higher | ||
| @db.transaction do | ||
| @db.execute "create table no_defaults_test ( a integer default 1, b integer )" | ||
| data = @db.table_info( "no_defaults_test" ) | ||
| assert_equal({"name" => "a", "type" => "integer", "dflt_value" => "1", "notnull" => 0, "cid" => 0, "pk" => 0}, | ||
| data[0]) | ||
| assert_equal({"name" => "b", "type" => "integer", "dflt_value" => nil, "notnull" => 0, "cid" => 1, "pk" => 0}, | ||
| data[1]) | ||
| end | ||
| end | ||
| def test_complete_fail | ||
| assert !@db.complete?( "select * from foo" ) | ||
| end | ||
| def test_complete_success | ||
| assert @db.complete?( "select * from foo;" ) | ||
| end | ||
| # FIXME: do people really need UTF16 sql statements? | ||
| #def test_complete_fail_utf16 | ||
| # assert !@db.complete?( "select * from foo".to_utf16(false), true ) | ||
| #end | ||
| # FIXME: do people really need UTF16 sql statements? | ||
| #def test_complete_success_utf16 | ||
| # assert @db.complete?( "select * from foo;".to_utf16(true), true ) | ||
| #end | ||
| def test_errmsg | ||
| assert_equal "not an error", @db.errmsg | ||
| end | ||
| # FIXME: do people really need UTF16 error messages? | ||
| #def test_errmsg_utf16 | ||
| # msg = Iconv.conv('UTF-16', 'UTF-8', 'not an error') | ||
| # assert_equal msg, @db.errmsg(true) | ||
| #end | ||
| def test_errcode | ||
| assert_equal 0, @db.errcode | ||
| end | ||
| def test_trace | ||
| result = nil | ||
| @db.trace { |sql| result = sql } | ||
| @db.execute "select * from foo" | ||
| assert_equal "select * from foo", result | ||
| end | ||
| def test_authorizer_okay | ||
| @db.authorizer { |type,a,b,c,d| 0 } | ||
| rows = @db.execute "select * from foo" | ||
| assert_equal 3, rows.length | ||
| end | ||
| def test_authorizer_error | ||
| @db.authorizer { |type,a,b,c,d| 1 } | ||
| assert_raise( SQLite3::AuthorizationException ) do | ||
| @db.execute "select * from foo" | ||
| end | ||
| end | ||
| def test_authorizer_silent | ||
| @db.authorizer { |type,a,b,c,d| 2 } | ||
| rows = @db.execute "select * from foo" | ||
| assert rows.empty? | ||
| end | ||
| def test_prepare_invalid_syntax | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.prepare "select from foo" | ||
| end | ||
| end | ||
| def test_prepare_invalid_column | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.prepare "select k from foo" | ||
| end | ||
| end | ||
| def test_prepare_invalid_table | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.prepare "select * from barf" | ||
| end | ||
| end | ||
| def test_prepare_no_block | ||
| stmt = @db.prepare "select * from foo" | ||
| assert stmt.respond_to?(:execute) | ||
| stmt.close | ||
| end | ||
| def test_prepare_with_block | ||
| called = false | ||
| @db.prepare "select * from foo" do |stmt| | ||
| called = true | ||
| assert stmt.respond_to?(:execute) | ||
| end | ||
| assert called | ||
| end | ||
| def test_execute_no_block_no_bind_no_match | ||
| rows = @db.execute( "select * from foo where a > 100" ) | ||
| assert rows.empty? | ||
| end | ||
| def test_execute_with_block_no_bind_no_match | ||
| called = false | ||
| @db.execute( "select * from foo where a > 100" ) do |row| | ||
| called = true | ||
| end | ||
| assert !called | ||
| end | ||
| def test_execute_no_block_with_bind_no_match | ||
| rows = @db.execute( "select * from foo where a > ?", 100 ) | ||
| assert rows.empty? | ||
| end | ||
| def test_execute_with_block_with_bind_no_match | ||
| called = false | ||
| @db.execute( "select * from foo where a > ?", 100 ) do |row| | ||
| called = true | ||
| end | ||
| assert !called | ||
| end | ||
| def test_execute_no_block_no_bind_with_match | ||
| rows = @db.execute( "select * from foo where a = 1" ) | ||
| assert_equal 1, rows.length | ||
| end | ||
| def test_execute_with_block_no_bind_with_match | ||
| called = 0 | ||
| @db.execute( "select * from foo where a = 1" ) do |row| | ||
| called += 1 | ||
| end | ||
| assert_equal 1, called | ||
| end | ||
| def test_execute_no_block_with_bind_with_match | ||
| rows = @db.execute( "select * from foo where a = ?", 1 ) | ||
| assert_equal 1, rows.length | ||
| end | ||
| def test_execute_with_block_with_bind_with_match | ||
| called = 0 | ||
| @db.execute( "select * from foo where a = ?", 1 ) do |row| | ||
| called += 1 | ||
| end | ||
| assert_equal 1, called | ||
| end | ||
| def test_execute2_no_block_no_bind_no_match | ||
| columns, *rows = @db.execute2( "select * from foo where a > 100" ) | ||
| assert rows.empty? | ||
| assert_equal [ "a", "b" ], columns | ||
| end | ||
| def test_execute2_with_block_no_bind_no_match | ||
| called = 0 | ||
| @db.execute2( "select * from foo where a > 100" ) do |row| | ||
| assert [ "a", "b" ], row unless called == 0 | ||
| called += 1 | ||
| end | ||
| assert_equal 1, called | ||
| end | ||
| def test_execute2_no_block_with_bind_no_match | ||
| columns, *rows = @db.execute2( "select * from foo where a > ?", 100 ) | ||
| assert rows.empty? | ||
| assert_equal [ "a", "b" ], columns | ||
| end | ||
| def test_execute2_with_block_with_bind_no_match | ||
| called = 0 | ||
| @db.execute2( "select * from foo where a > ?", 100 ) do |row| | ||
| assert_equal [ "a", "b" ], row unless called == 0 | ||
| called += 1 | ||
| end | ||
| assert_equal 1, called | ||
| end | ||
| def test_execute2_no_block_no_bind_with_match | ||
| columns, *rows = @db.execute2( "select * from foo where a = 1" ) | ||
| assert_equal 1, rows.length | ||
| assert_equal [ "a", "b" ], columns | ||
| end | ||
| def test_execute2_with_block_no_bind_with_match | ||
| called = 0 | ||
| @db.execute2( "select * from foo where a = 1" ) do |row| | ||
| assert_equal [ 1, "foo" ], row unless called == 0 | ||
| called += 1 | ||
| end | ||
| assert_equal 2, called | ||
| end | ||
| def test_execute2_no_block_with_bind_with_match | ||
| columns, *rows = @db.execute2( "select * from foo where a = ?", 1 ) | ||
| assert_equal 1, rows.length | ||
| assert_equal [ "a", "b" ], columns | ||
| end | ||
| def test_execute2_with_block_with_bind_with_match | ||
| called = 0 | ||
| @db.execute2( "select * from foo where a = ?", 1 ) do | ||
| called += 1 | ||
| end | ||
| assert_equal 2, called | ||
| end | ||
| def test_execute_batch_empty | ||
| assert_nothing_raised { @db.execute_batch "" } | ||
| end | ||
| def test_execute_batch_no_bind | ||
| @db.transaction do | ||
| @db.execute_batch <<-SQL | ||
| create table bar ( a, b, c ); | ||
| insert into bar values ( 'one', 2, 'three' ); | ||
| insert into bar values ( 'four', 5, 'six' ); | ||
| insert into bar values ( 'seven', 8, 'nine' ); | ||
| SQL | ||
| end | ||
| rows = @db.execute( "select * from bar" ) | ||
| assert_equal 3, rows.length | ||
| end | ||
| def test_execute_batch_with_bind | ||
| @db.execute_batch( <<-SQL, [1] ) | ||
| create table bar ( a, b, c ); | ||
| insert into bar values ( 'one', 2, ? ); | ||
| insert into bar values ( 'four', 5, ? ); | ||
| insert into bar values ( 'seven', 8, ? ); | ||
| SQL | ||
| rows = @db.execute( "select * from bar" ).map { |a,b,c| c } | ||
| assert_equal [1, 1, 1], rows | ||
| end | ||
| def test_query_no_block_no_bind_no_match | ||
| result = @db.query( "select * from foo where a > 100" ) | ||
| assert_nil result.next | ||
| result.close | ||
| end | ||
| def test_query_with_block_no_bind_no_match | ||
| r = nil | ||
| @db.query( "select * from foo where a > 100" ) do |result| | ||
| assert_nil result.next | ||
| r = result | ||
| end | ||
| assert r.closed? | ||
| end | ||
| def test_query_no_block_with_bind_no_match | ||
| result = @db.query( "select * from foo where a > ?", 100 ) | ||
| assert_nil result.next | ||
| result.close | ||
| end | ||
| def test_query_with_block_with_bind_no_match | ||
| r = nil | ||
| @db.query( "select * from foo where a > ?", 100 ) do |result| | ||
| assert_nil result.next | ||
| r = result | ||
| end | ||
| assert r.closed? | ||
| end | ||
| def test_query_no_block_no_bind_with_match | ||
| result = @db.query( "select * from foo where a = 1" ) | ||
| assert_not_nil result.next | ||
| assert_nil result.next | ||
| result.close | ||
| end | ||
| def test_query_with_block_no_bind_with_match | ||
| r = nil | ||
| @db.query( "select * from foo where a = 1" ) do |result| | ||
| assert_not_nil result.next | ||
| assert_nil result.next | ||
| r = result | ||
| end | ||
| assert r.closed? | ||
| end | ||
| def test_query_no_block_with_bind_with_match | ||
| result = @db.query( "select * from foo where a = ?", 1 ) | ||
| assert_not_nil result.next | ||
| assert_nil result.next | ||
| result.close | ||
| end | ||
| def test_query_with_block_with_bind_with_match | ||
| r = nil | ||
| @db.query( "select * from foo where a = ?", 1 ) do |result| | ||
| assert_not_nil result.next | ||
| assert_nil result.next | ||
| r = result | ||
| end | ||
| assert r.closed? | ||
| end | ||
| def test_get_first_row_no_bind_no_match | ||
| result = @db.get_first_row( "select * from foo where a=100" ) | ||
| assert_nil result | ||
| end | ||
| def test_get_first_row_no_bind_with_match | ||
| result = @db.get_first_row( "select * from foo where a=1" ) | ||
| assert_equal [ 1, "foo" ], result | ||
| end | ||
| def test_get_first_row_with_bind_no_match | ||
| result = @db.get_first_row( "select * from foo where a=?", 100 ) | ||
| assert_nil result | ||
| end | ||
| def test_get_first_row_with_bind_with_match | ||
| result = @db.get_first_row( "select * from foo where a=?", 1 ) | ||
| assert_equal [ 1, "foo" ], result | ||
| end | ||
| def test_get_first_value_no_bind_no_match | ||
| result = @db.get_first_value( "select b, a from foo where a=100" ) | ||
| assert_nil result | ||
| @db.results_as_hash = true | ||
| result = @db.get_first_value( "select b, a from foo where a=100" ) | ||
| assert_nil result | ||
| end | ||
| def test_get_first_value_no_bind_with_match | ||
| result = @db.get_first_value( "select b, a from foo where a=1" ) | ||
| assert_equal "foo", result | ||
| @db.results_as_hash = true | ||
| result = @db.get_first_value( "select b, a from foo where a=1" ) | ||
| assert_equal "foo", result | ||
| end | ||
| def test_get_first_value_with_bind_no_match | ||
| result = @db.get_first_value( "select b, a from foo where a=?", 100 ) | ||
| assert_nil result | ||
| @db.results_as_hash = true | ||
| result = @db.get_first_value( "select b, a from foo where a=?", 100 ) | ||
| assert_nil result | ||
| end | ||
| def test_get_first_value_with_bind_with_match | ||
| result = @db.get_first_value( "select b, a from foo where a=?", 1 ) | ||
| assert_equal "foo", result | ||
| @db.results_as_hash = true | ||
| result = @db.get_first_value( "select b, a from foo where a=?", 1 ) | ||
| assert_equal "foo", result | ||
| end | ||
| def test_last_insert_row_id | ||
| @db.execute "insert into foo ( b ) values ( 'test' )" | ||
| assert_equal 4, @db.last_insert_row_id | ||
| @db.execute "insert into foo ( b ) values ( 'again' )" | ||
| assert_equal 5, @db.last_insert_row_id | ||
| end | ||
| def test_changes | ||
| @db.execute "insert into foo ( b ) values ( 'test' )" | ||
| assert_equal 1, @db.changes | ||
| @db.execute "delete from foo where 1=1" | ||
| assert_equal 4, @db.changes | ||
| end | ||
| def test_total_changes | ||
| assert_equal 3, @db.total_changes | ||
| @db.execute "insert into foo ( b ) values ( 'test' )" | ||
| @db.execute "delete from foo where 1=1" | ||
| assert_equal 8, @db.total_changes | ||
| end | ||
| def test_transaction_nest | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.transaction do | ||
| @db.transaction do | ||
| end | ||
| end | ||
| end | ||
| end | ||
| def test_transaction_rollback | ||
| @db.transaction | ||
| @db.execute_batch <<-SQL | ||
| insert into foo (b) values ( 'test1' ); | ||
| insert into foo (b) values ( 'test2' ); | ||
| insert into foo (b) values ( 'test3' ); | ||
| insert into foo (b) values ( 'test4' ); | ||
| SQL | ||
| assert_equal 7, @db.get_first_value("select count(*) from foo").to_i | ||
| @db.rollback | ||
| assert_equal 3, @db.get_first_value("select count(*) from foo").to_i | ||
| end | ||
| def test_transaction_commit | ||
| @db.transaction | ||
| @db.execute_batch <<-SQL | ||
| insert into foo (b) values ( 'test1' ); | ||
| insert into foo (b) values ( 'test2' ); | ||
| insert into foo (b) values ( 'test3' ); | ||
| insert into foo (b) values ( 'test4' ); | ||
| SQL | ||
| assert_equal 7, @db.get_first_value("select count(*) from foo").to_i | ||
| @db.commit | ||
| assert_equal 7, @db.get_first_value("select count(*) from foo").to_i | ||
| end | ||
| def test_transaction_rollback_in_block | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.transaction do | ||
| @db.rollback | ||
| end | ||
| end | ||
| end | ||
| def test_transaction_commit_in_block | ||
| assert_raise( SQLite3::SQLException ) do | ||
| @db.transaction do | ||
| @db.commit | ||
| end | ||
| end | ||
| end | ||
| def test_transaction_active | ||
| assert !@db.transaction_active? | ||
| @db.transaction | ||
| assert @db.transaction_active? | ||
| @db.commit | ||
| assert !@db.transaction_active? | ||
| end | ||
| def test_transaction_implicit_rollback | ||
| assert !@db.transaction_active? | ||
| @db.transaction | ||
| @db.execute('create table bar (x CHECK(1 = 0))') | ||
| assert @db.transaction_active? | ||
| assert_raises( SQLite3::ConstraintException ) do | ||
| @db.execute("insert or rollback into bar (x) VALUES ('x')") | ||
| end | ||
| assert !@db.transaction_active? | ||
| end | ||
| def test_interrupt | ||
| @db.create_function( "abort", 1 ) do |func,x| | ||
| @db.interrupt | ||
| func.result = x | ||
| end | ||
| assert_raise( SQLite3::InterruptException ) do | ||
| @db.execute "select abort(a) from foo" | ||
| end | ||
| end | ||
| def test_create_function | ||
| @db.create_function( "munge", 1 ) do |func,x| | ||
| func.result = ">>>#{x}<<<" | ||
| end | ||
| value = @db.get_first_value( "select munge(b) from foo where a=1" ) | ||
| assert_match( />>>.*<<</, value ) | ||
| end | ||
| def test_bind_array_parameter | ||
| result = @db.get_first_value( "select b from foo where a=? and b=?", | ||
| [ 1, "foo" ] ) | ||
| assert_equal "foo", result | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestPragmas < SQLite3::TestCase | ||
| def setup | ||
| super | ||
| @db = SQLite3::Database.new(":memory:") | ||
| end | ||
| def test_get_boolean_pragma | ||
| refute(@db.get_boolean_pragma("read_uncommitted")) | ||
| end | ||
| def test_set_boolean_pragma | ||
| @db.set_boolean_pragma("read_uncommitted", 1) | ||
| assert(@db.get_boolean_pragma("read_uncommitted")) | ||
| ensure | ||
| @db.set_boolean_pragma("read_uncommitted", 0) | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestResultSet < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new ':memory:' | ||
| super | ||
| end | ||
| def teardown | ||
| super | ||
| @db.close | ||
| end | ||
| def test_each_hash | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| list = ('a'..'z').to_a | ||
| list.each do |t| | ||
| @db.execute "insert into foo (b) values (\"#{t}\")" | ||
| end | ||
| rs = @db.prepare('select * from foo').execute | ||
| rs.each_hash do |hash| | ||
| assert_equal list[hash['a'] - 1], hash['b'] | ||
| end | ||
| rs.close | ||
| end | ||
| def test_next_hash | ||
| @db.execute "create table foo ( a integer primary key, b text )" | ||
| list = ('a'..'z').to_a | ||
| list.each do |t| | ||
| @db.execute "insert into foo (b) values (\"#{t}\")" | ||
| end | ||
| rs = @db.prepare('select * from foo').execute | ||
| rows = [] | ||
| while row = rs.next_hash | ||
| rows << row | ||
| end | ||
| rows.each do |hash| | ||
| assert_equal list[hash['a'] - 1], hash['b'] | ||
| end | ||
| rs.close | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestSQLite3 < SQLite3::TestCase | ||
| def test_libversion | ||
| assert_not_nil SQLite3.libversion | ||
| end | ||
| def test_threadsafe | ||
| assert_not_nil SQLite3.threadsafe | ||
| end | ||
| def test_threadsafe? | ||
| if SQLite3.threadsafe > 0 | ||
| assert SQLite3.threadsafe? | ||
| else | ||
| refute SQLite3.threadsafe? | ||
| end | ||
| end | ||
| def test_version_strings | ||
| skip if SQLite3::VERSION.include?("test") # see set-version-to-timestamp rake task | ||
| assert_equal(SQLite3::VERSION, SQLite3::VersionProxy::STRING) | ||
| end | ||
| def test_compiled_version_and_loaded_version | ||
| assert_equal(SQLite3::SQLITE_VERSION, SQLite3::SQLITE_LOADED_VERSION) | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestStatementExecute < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @db.execute_batch( | ||
| "CREATE TABLE items (id integer PRIMARY KEY, number integer)") | ||
| end | ||
| def teardown | ||
| @db.close | ||
| end | ||
| def test_execute_insert | ||
| ps = @db.prepare("INSERT INTO items (number) VALUES (:n)") | ||
| ps.execute('n'=>10) | ||
| assert_equal 1, @db.get_first_value("SELECT count(*) FROM items") | ||
| ps.close | ||
| end | ||
| def test_execute_update | ||
| @db.execute("INSERT INTO items (number) VALUES (?)", [10]) | ||
| ps = @db.prepare("UPDATE items SET number = :new WHERE number = :old") | ||
| ps.execute('old'=>10, 'new'=>20) | ||
| assert_equal 20, @db.get_first_value("SELECT number FROM items") | ||
| ps.close | ||
| end | ||
| def test_execute_delete | ||
| @db.execute("INSERT INTO items (number) VALUES (?)", [20]) | ||
| ps = @db.prepare("DELETE FROM items WHERE number = :n") | ||
| ps.execute('n' => 20) | ||
| assert_equal 0, @db.get_first_value("SELECT count(*) FROM items") | ||
| ps.close | ||
| end | ||
| end | ||
| end |
| require 'helper' | ||
| module SQLite3 | ||
| class TestStatement < SQLite3::TestCase | ||
| def setup | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @stmt = SQLite3::Statement.new(@db, "select 'foo'") | ||
| end | ||
| def teardown | ||
| @stmt.close if !@stmt.closed? | ||
| @db.close | ||
| end | ||
| def test_double_close_does_not_segv | ||
| @db.execute 'CREATE TABLE "things" ("number" float NOT NULL)' | ||
| stmt = @db.prepare 'INSERT INTO things (number) VALUES (?)' | ||
| assert_raises(SQLite3::ConstraintException) { stmt.execute(nil) } | ||
| stmt.close | ||
| assert_raises(SQLite3::Exception) { stmt.close } | ||
| end | ||
| def test_raises_type_error | ||
| assert_raises(TypeError) do | ||
| SQLite3::Statement.new( @db, nil ) | ||
| end | ||
| end | ||
| def test_insert_duplicate_records | ||
| @db.execute 'CREATE TABLE "things" ("name" varchar(20) CONSTRAINT "index_things_on_name" UNIQUE)' | ||
| stmt = @db.prepare("INSERT INTO things(name) VALUES(?)") | ||
| stmt.execute('ruby') | ||
| exception = assert_raises(SQLite3::ConstraintException) { stmt.execute('ruby') } | ||
| # SQLite 3.8.2 returns new error message: | ||
| # UNIQUE constraint failed: *table_name*.*column_name* | ||
| # Older versions of SQLite return: | ||
| # column *column_name* is not unique | ||
| assert_match(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/, exception.message) | ||
| stmt.close | ||
| end | ||
| ### | ||
| # This method may not exist depending on how sqlite3 was compiled | ||
| def test_database_name | ||
| @db.execute('create table foo(text BLOB)') | ||
| @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) | ||
| stmt = @db.prepare('select text from foo') | ||
| if stmt.respond_to?(:database_name) | ||
| assert_equal 'main', stmt.database_name(0) | ||
| end | ||
| stmt.close | ||
| end | ||
| def test_prepare_blob | ||
| @db.execute('create table foo(text BLOB)') | ||
| stmt = @db.prepare('insert into foo(text) values (?)') | ||
| stmt.bind_param(1, SQLite3::Blob.new('hello')) | ||
| stmt.step | ||
| stmt.close | ||
| end | ||
| def test_select_blob | ||
| @db.execute('create table foo(text BLOB)') | ||
| @db.execute('insert into foo(text) values (?)',SQLite3::Blob.new('hello')) | ||
| assert_equal 'hello', @db.execute('select * from foo').first.first | ||
| end | ||
| def test_new | ||
| assert @stmt | ||
| end | ||
| def test_new_closed_handle | ||
| @db = SQLite3::Database.new(':memory:') | ||
| @db.close | ||
| assert_raises(ArgumentError) do | ||
| SQLite3::Statement.new(@db, 'select "foo"') | ||
| end | ||
| end | ||
| def test_new_with_remainder | ||
| stmt = SQLite3::Statement.new(@db, "select 'foo';bar") | ||
| assert_equal 'bar', stmt.remainder | ||
| stmt.close | ||
| end | ||
| def test_empty_remainder | ||
| assert_equal '', @stmt.remainder | ||
| end | ||
| def test_close | ||
| @stmt.close | ||
| assert @stmt.closed? | ||
| end | ||
| def test_double_close | ||
| @stmt.close | ||
| assert_raises(SQLite3::Exception) do | ||
| @stmt.close | ||
| end | ||
| end | ||
| def test_bind_param_string | ||
| stmt = SQLite3::Statement.new(@db, "select ?") | ||
| stmt.bind_param(1, "hello") | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal ['hello'], result | ||
| stmt.close | ||
| end | ||
| def test_bind_param_int | ||
| stmt = SQLite3::Statement.new(@db, "select ?") | ||
| stmt.bind_param(1, 10) | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal [10], result | ||
| stmt.close | ||
| end | ||
| def test_bind_nil | ||
| stmt = SQLite3::Statement.new(@db, "select ?") | ||
| stmt.bind_param(1, nil) | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal [nil], result | ||
| stmt.close | ||
| end | ||
| def test_bind_blob | ||
| @db.execute('create table foo(text BLOB)') | ||
| stmt = SQLite3::Statement.new(@db, 'insert into foo(text) values (?)') | ||
| stmt.bind_param(1, SQLite3::Blob.new('hello')) | ||
| stmt.execute | ||
| row = @db.execute('select * from foo') | ||
| stmt.close | ||
| assert_equal ['hello'], row.first | ||
| capture_io do # hush deprecation warning | ||
| assert_equal ['blob'], row.first.types | ||
| end | ||
| end | ||
| def test_bind_64 | ||
| stmt = SQLite3::Statement.new(@db, "select ?") | ||
| stmt.bind_param(1, 2 ** 31) | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal [2 ** 31], result | ||
| stmt.close | ||
| end | ||
| def test_bind_double | ||
| stmt = SQLite3::Statement.new(@db, "select ?") | ||
| stmt.bind_param(1, 2.2) | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal [2.2], result | ||
| stmt.close | ||
| end | ||
| def test_named_bind | ||
| stmt = SQLite3::Statement.new(@db, "select :foo") | ||
| stmt.bind_param(':foo', 'hello') | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal ['hello'], result | ||
| stmt.close | ||
| end | ||
| def test_named_bind_no_colon | ||
| stmt = SQLite3::Statement.new(@db, "select :foo") | ||
| stmt.bind_param('foo', 'hello') | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal ['hello'], result | ||
| stmt.close | ||
| end | ||
| def test_named_bind_symbol | ||
| stmt = SQLite3::Statement.new(@db, "select :foo") | ||
| stmt.bind_param(:foo, 'hello') | ||
| result = nil | ||
| stmt.each { |x| result = x } | ||
| assert_equal ['hello'], result | ||
| stmt.close | ||
| end | ||
| def test_named_bind_not_found | ||
| stmt = SQLite3::Statement.new(@db, "select :foo") | ||
| assert_raises(SQLite3::Exception) do | ||
| stmt.bind_param('bar', 'hello') | ||
| end | ||
| stmt.close | ||
| end | ||
| def test_each | ||
| r = nil | ||
| @stmt.each do |row| | ||
| r = row | ||
| end | ||
| assert_equal(['foo'], r) | ||
| end | ||
| def test_reset! | ||
| r = [] | ||
| @stmt.each { |row| r << row } | ||
| @stmt.reset! | ||
| @stmt.each { |row| r << row } | ||
| assert_equal [['foo'], ['foo']], r | ||
| end | ||
| def test_step | ||
| r = @stmt.step | ||
| assert_equal ['foo'], r | ||
| end | ||
| def test_step_twice | ||
| assert_not_nil @stmt.step | ||
| assert !@stmt.done? | ||
| assert_nil @stmt.step | ||
| assert @stmt.done? | ||
| @stmt.reset! | ||
| assert !@stmt.done? | ||
| end | ||
| def test_step_never_moves_past_done | ||
| 10.times { @stmt.step } | ||
| @stmt.done? | ||
| end | ||
| def test_column_count | ||
| assert_equal 1, @stmt.column_count | ||
| end | ||
| def test_column_name | ||
| assert_equal "'foo'", @stmt.column_name(0) | ||
| assert_nil @stmt.column_name(10) | ||
| end | ||
| def test_bind_parameter_count | ||
| stmt = SQLite3::Statement.new(@db, "select ?, ?, ?") | ||
| assert_equal 3, stmt.bind_parameter_count | ||
| stmt.close | ||
| end | ||
| def test_execute_with_varargs | ||
| stmt = @db.prepare('select ?, ?') | ||
| assert_equal [[nil, nil]], stmt.execute(nil, nil).to_a | ||
| stmt.close | ||
| end | ||
| def test_execute_with_hash | ||
| stmt = @db.prepare('select :n, :h') | ||
| assert_equal [[10, nil]], stmt.execute('n' => 10, 'h' => nil).to_a | ||
| stmt.close | ||
| end | ||
| def test_with_error | ||
| @db.execute('CREATE TABLE "employees" ("name" varchar(20) NOT NULL CONSTRAINT "index_employees_on_name" UNIQUE)') | ||
| stmt = @db.prepare("INSERT INTO Employees(name) VALUES(?)") | ||
| stmt.execute('employee-1') | ||
| stmt.execute('employee-1') rescue SQLite3::ConstraintException | ||
| stmt.reset! | ||
| assert stmt.execute('employee-2') | ||
| stmt.close | ||
| end | ||
| def test_clear_bindings! | ||
| stmt = @db.prepare('select ?, ?') | ||
| stmt.bind_param 1, "foo" | ||
| stmt.bind_param 2, "bar" | ||
| # We can't fetch bound parameters back out of sqlite3, so just call | ||
| # the clear_bindings! method and assert that nil is returned | ||
| stmt.clear_bindings! | ||
| while x = stmt.step | ||
| assert_equal [nil, nil], x | ||
| end | ||
| stmt.close | ||
| end | ||
| end | ||
| end |