New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

sqlite3

Package Overview
Dependencies
Maintainers
4
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sqlite3 - rubygems Package Compare versions

Comparing version
1.7.3
to
2.0.0
+20
ext/sqlite3/timespec.h
#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"

@@ -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

@@ -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();

@@ -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, &currentTime);
if (!timespecisset(&ctx->stmt_deadline)) {
// Set stmt_deadline if not already set
ctx->stmt_deadline = currentTime;
} else if (timespecafter(&currentTime, &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
#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

@@ -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);

@@ -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()));
}

@@ -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 @@

@@ -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,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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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.

@@ -14,4 +14,3 @@ # Ruby Interface for SQLite3

[![Unit tests](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/sqlite3-ruby.yml/badge.svg)](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/sqlite3-ruby.yml)
[![Native packages](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/gem-install.yml/badge.svg)](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/gem-install.yml)
[![Test suite](https://github.com/sparklemotion/sqlite3-ruby/actions/workflows/ci.yml/badge.svg)](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

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