extralite 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +57 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +0 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +18 -0
- data/TODO.md +0 -0
- data/ext/extralite/extconf.rb +115 -0
- data/ext/extralite/extralite.c +375 -0
- data/ext/extralite/extralite_ext.c +5 -0
- data/ext/sqlite3/sqlite3.h +12264 -0
- data/extralite.gemspec +30 -0
- data/lib/extralite.rb +1 -0
- data/lib/extralite/version.rb +3 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6467de8ba44e1e77c57965e0360a18f9e2cf97084f0c507d0b1946e2cb0bb153
|
4
|
+
data.tar.gz: a12610ea4fab0207d5360f5410428ab7d8ce004c98ecd05fcb13785bf173c196
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c17e8b73c0e94718a80bdcce58c98072c640b952959b441c7053f367984650688ee25ce5d7e0c6c4706d9d13d688f6e006c336e78c0c803d35af3a9cab15885
|
7
|
+
data.tar.gz: 194b2b8e9d469927e08bb6bc9b9e88238fb2b289bace6501c736817cfdee29e4c13d20d420a1a344c107ce039786b703826a11091a8f2eb3cc366dde6f922d46
|
data/.gitignore
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
lib/extralite_ext.*
|
13
|
+
|
14
|
+
# Used by dotenv library to load environment variables.
|
15
|
+
# .env
|
16
|
+
|
17
|
+
# Ignore Byebug command history file.
|
18
|
+
.byebug_history
|
19
|
+
|
20
|
+
## Specific to RubyMotion:
|
21
|
+
.dat*
|
22
|
+
.repl_history
|
23
|
+
build/
|
24
|
+
*.bridgesupport
|
25
|
+
build-iPhoneOS/
|
26
|
+
build-iPhoneSimulator/
|
27
|
+
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
29
|
+
#
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
33
|
+
#
|
34
|
+
# vendor/Pods/
|
35
|
+
|
36
|
+
## Documentation cache and generated files:
|
37
|
+
/.yardoc/
|
38
|
+
/_yardoc/
|
39
|
+
/doc/
|
40
|
+
/rdoc/
|
41
|
+
|
42
|
+
## Environment normalization:
|
43
|
+
/.bundle/
|
44
|
+
/vendor/bundle
|
45
|
+
/lib/bundler/man/
|
46
|
+
|
47
|
+
# for a library or gem, you might want to ignore these files since the code is
|
48
|
+
# intended to run in multiple environments; otherwise, check them in:
|
49
|
+
# Gemfile.lock
|
50
|
+
# .ruby-version
|
51
|
+
# .ruby-gemset
|
52
|
+
|
53
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
54
|
+
.rvmrc
|
55
|
+
|
56
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
57
|
+
# .rubocop-https?--*
|
data/CHANGELOG.md
ADDED
File without changes
|
data/Gemfile
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Digital Fabric
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
## Extralite
|
2
|
+
|
3
|
+
Extralite is an extra-lightweight SQLite3 wrapper for Ruby. It provides a single
|
4
|
+
class with a minimal set of methods to interact with an SQLite3 database.
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
- A variety of ways to get back query results: row as hash, row as array, single
|
9
|
+
column, single value.
|
10
|
+
- Iterate over records with a block, or collect records into an array.
|
11
|
+
- Parameter binding.
|
12
|
+
- Get last insert rowid.
|
13
|
+
- Get number of rows changed by last query.
|
14
|
+
|
15
|
+
### Usage
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'extralite'
|
19
|
+
|
20
|
+
# open a database
|
21
|
+
db = Extralite::Database.new('mydb')
|
22
|
+
|
23
|
+
# get query results as array of hashes
|
24
|
+
db.query_hash('select 1 as foo') #=> [{ :foo => 1 }]
|
25
|
+
# or iterate over results
|
26
|
+
db.query_hash('select 1 as foo') { |r| p r }
|
27
|
+
# { :foo => 1 }
|
28
|
+
|
29
|
+
# get query results as array of arrays
|
30
|
+
db.query_ary('select 1, 2, 3') #=> [[1, 2, 3]]
|
31
|
+
# or iterate over results
|
32
|
+
db.query_ary('select 1, 2, 3') { |r| p r }
|
33
|
+
# [1, 2, 3]
|
34
|
+
|
35
|
+
# get single column query results as array of values
|
36
|
+
db.query_single_column('select 42') #=> [42]
|
37
|
+
# or iterate over results
|
38
|
+
db.query_single_column('select 42') { |v| p v }
|
39
|
+
# 42
|
40
|
+
|
41
|
+
# get single value from first row of results
|
42
|
+
db.query_single_value("select 'foo'") #=> "foo"
|
43
|
+
|
44
|
+
# parameter binding (works for all query_xxx methods)
|
45
|
+
db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
46
|
+
|
47
|
+
# get last insert rowid
|
48
|
+
rowid = db.last_insert_id()
|
49
|
+
```
|
50
|
+
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/clean"
|
5
|
+
|
6
|
+
require "rake/extensiontask"
|
7
|
+
Rake::ExtensionTask.new("extralite_ext") do |ext|
|
8
|
+
ext.ext_dir = "ext/extralite"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :recompile => [:clean, :compile]
|
12
|
+
|
13
|
+
task :default => [:compile, :test]
|
14
|
+
task :test do
|
15
|
+
exec 'ruby test/run.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
CLEAN.include "**/*.o", "**/*.so", "**/*.so.*", "**/*.a", "**/*.bundle", "**/*.jar", "pkg", "tmp"
|
data/TODO.md
ADDED
File without changes
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'rubygems'
|
4
|
+
# require 'mkmf'
|
5
|
+
|
6
|
+
# # $CFLAGS << "-Wdiscarded-qualifier"
|
7
|
+
# # $CFLAGS << " -Wno-comment"
|
8
|
+
# # $CFLAGS << " -Wno-unused-result"
|
9
|
+
# # $CFLAGS << " -Wno-dangling-else"
|
10
|
+
# # $CFLAGS << " -Wno-parentheses"
|
11
|
+
|
12
|
+
# dir_config 'extralite_ext'
|
13
|
+
# create_makefile 'extralite_ext'
|
14
|
+
|
15
|
+
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
16
|
+
|
17
|
+
require 'mkmf'
|
18
|
+
|
19
|
+
# :stopdoc:
|
20
|
+
|
21
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
22
|
+
|
23
|
+
ldflags = cppflags = nil
|
24
|
+
if RbConfig::CONFIG["host_os"] =~ /darwin/
|
25
|
+
begin
|
26
|
+
if with_config('sqlcipher')
|
27
|
+
brew_prefix = `brew --prefix sqlcipher`.chomp
|
28
|
+
ldflags = "#{brew_prefix}/lib"
|
29
|
+
cppflags = "#{brew_prefix}/include/sqlcipher"
|
30
|
+
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
31
|
+
else
|
32
|
+
brew_prefix = `brew --prefix sqlite3`.chomp
|
33
|
+
ldflags = "#{brew_prefix}/lib"
|
34
|
+
cppflags = "#{brew_prefix}/include"
|
35
|
+
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
36
|
+
end
|
37
|
+
|
38
|
+
# pkg_config should be less error prone than parsing compiler
|
39
|
+
# commandline options, but we need to set default ldflags and cpp flags
|
40
|
+
# in case the user doesn't have pkg-config installed
|
41
|
+
ENV['PKG_CONFIG_PATH'] ||= pkg_conf
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if with_config('sqlcipher')
|
47
|
+
pkg_config("sqlcipher")
|
48
|
+
else
|
49
|
+
pkg_config("sqlite3")
|
50
|
+
end
|
51
|
+
|
52
|
+
# --with-sqlite3-{dir,include,lib}
|
53
|
+
if with_config('sqlcipher')
|
54
|
+
$CFLAGS << ' -DUSING_SQLCIPHER'
|
55
|
+
dir_config("sqlcipher", cppflags, ldflags)
|
56
|
+
else
|
57
|
+
dir_config("sqlite3", cppflags, ldflags)
|
58
|
+
end
|
59
|
+
|
60
|
+
if RbConfig::CONFIG["host_os"] =~ /mswin/
|
61
|
+
$CFLAGS << ' -W3'
|
62
|
+
end
|
63
|
+
|
64
|
+
if RUBY_VERSION < '2.7'
|
65
|
+
$CFLAGS << ' -DTAINTING_SUPPORT'
|
66
|
+
end
|
67
|
+
|
68
|
+
def asplode missing
|
69
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
70
|
+
abort "#{missing} is missing. Install SQLite3 from " +
|
71
|
+
"http://www.sqlite.org/ first."
|
72
|
+
else
|
73
|
+
abort <<-error
|
74
|
+
#{missing} is missing. Try 'brew install sqlite3',
|
75
|
+
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
|
76
|
+
and check your shared library search path (the
|
77
|
+
location where your sqlite3 shared library is located).
|
78
|
+
error
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
asplode('sqlite3.h') unless find_header 'sqlite3.h'
|
83
|
+
find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
|
84
|
+
|
85
|
+
have_library 'dl' # for static builds
|
86
|
+
|
87
|
+
if with_config('sqlcipher')
|
88
|
+
asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
|
89
|
+
else
|
90
|
+
asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
|
91
|
+
end
|
92
|
+
|
93
|
+
# Functions defined in 1.9 but not 1.8
|
94
|
+
have_func('rb_proc_arity')
|
95
|
+
|
96
|
+
# Functions defined in 2.1 but not 2.0
|
97
|
+
have_func('rb_integer_pack')
|
98
|
+
|
99
|
+
# These functions may not be defined
|
100
|
+
have_func('sqlite3_initialize')
|
101
|
+
have_func('sqlite3_backup_init')
|
102
|
+
have_func('sqlite3_column_database_name')
|
103
|
+
have_func('sqlite3_enable_load_extension')
|
104
|
+
have_func('sqlite3_load_extension')
|
105
|
+
|
106
|
+
unless have_func('sqlite3_open_v2')
|
107
|
+
abort "Please use a newer version of SQLite3"
|
108
|
+
end
|
109
|
+
|
110
|
+
have_func('sqlite3_prepare_v2')
|
111
|
+
have_type('sqlite3_int64', 'sqlite3.h')
|
112
|
+
have_type('sqlite3_uint64', 'sqlite3.h')
|
113
|
+
|
114
|
+
dir_config('extralite_ext')
|
115
|
+
create_makefile('extralite_ext')
|
@@ -0,0 +1,375 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include "ruby.h"
|
3
|
+
#include "../sqlite3/sqlite3.h"
|
4
|
+
|
5
|
+
VALUE cError;
|
6
|
+
|
7
|
+
typedef struct Database_t {
|
8
|
+
sqlite3 *sqlite3_db;
|
9
|
+
} Database_t;
|
10
|
+
|
11
|
+
static size_t Database_size(const void *ptr) {
|
12
|
+
return sizeof(Database_t);
|
13
|
+
}
|
14
|
+
|
15
|
+
static void Database_free(void *ptr) {
|
16
|
+
Database_t *db = ptr;
|
17
|
+
if (db->sqlite3_db) {
|
18
|
+
sqlite3_close(db->sqlite3_db);
|
19
|
+
}
|
20
|
+
// close db
|
21
|
+
free(ptr);
|
22
|
+
}
|
23
|
+
|
24
|
+
static const rb_data_type_t Database_type = {
|
25
|
+
"Database",
|
26
|
+
{0, Database_free, Database_size,},
|
27
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
28
|
+
};
|
29
|
+
|
30
|
+
static VALUE Database_allocate(VALUE klass) {
|
31
|
+
Database_t *db = ALLOC(Database_t);
|
32
|
+
db->sqlite3_db = 0;
|
33
|
+
return TypedData_Wrap_Struct(klass, &Database_type, db);
|
34
|
+
}
|
35
|
+
|
36
|
+
#define GetDatabase(obj, database) \
|
37
|
+
TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
|
38
|
+
|
39
|
+
|
40
|
+
VALUE Database_initialize(VALUE self, VALUE path) {
|
41
|
+
int rc;
|
42
|
+
Database_t *db;
|
43
|
+
GetDatabase(self, db);
|
44
|
+
|
45
|
+
rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
|
46
|
+
if (rc) {
|
47
|
+
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db->sqlite3_db));
|
48
|
+
sqlite3_close(db->sqlite3_db);
|
49
|
+
// TODO: raise error
|
50
|
+
return Qfalse;
|
51
|
+
}
|
52
|
+
|
53
|
+
return Qnil;
|
54
|
+
}
|
55
|
+
|
56
|
+
inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
57
|
+
switch (type) {
|
58
|
+
case SQLITE_NULL:
|
59
|
+
return Qnil;
|
60
|
+
case SQLITE_INTEGER:
|
61
|
+
return INT2NUM(sqlite3_column_int(stmt, col));
|
62
|
+
case SQLITE_FLOAT:
|
63
|
+
return DBL2NUM(sqlite3_column_double(stmt, col));
|
64
|
+
case SQLITE_TEXT:
|
65
|
+
return rb_str_new_cstr((char *)sqlite3_column_text(stmt, col));
|
66
|
+
case SQLITE_BLOB:
|
67
|
+
rb_raise(cError, "BLOB reading not yet implemented");
|
68
|
+
default:
|
69
|
+
rb_raise(cError, "Unknown column type: %d", type);
|
70
|
+
}
|
71
|
+
|
72
|
+
return Qnil;
|
73
|
+
}
|
74
|
+
|
75
|
+
static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
76
|
+
switch (TYPE(value)) {
|
77
|
+
case T_NIL:
|
78
|
+
sqlite3_bind_null(stmt, pos);
|
79
|
+
return;
|
80
|
+
case T_FIXNUM:
|
81
|
+
sqlite3_bind_int(stmt, pos, NUM2INT(value));
|
82
|
+
return;
|
83
|
+
case T_FLOAT:
|
84
|
+
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
85
|
+
return;
|
86
|
+
case T_TRUE:
|
87
|
+
sqlite3_bind_int(stmt, pos, 1);
|
88
|
+
return;
|
89
|
+
case T_FALSE:
|
90
|
+
sqlite3_bind_int(stmt, pos, 0);
|
91
|
+
return;
|
92
|
+
case T_STRING:
|
93
|
+
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
94
|
+
return;
|
95
|
+
default:
|
96
|
+
rb_raise(cError, "Cannot bind parameter at position %d", pos);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
static inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
101
|
+
if (argc > 1) {
|
102
|
+
for (int i = 1; i < argc; i++) {
|
103
|
+
bind_parameter_value(stmt, i, argv[i]);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
static inline VALUE get_column_names(sqlite3_stmt *stmt, int column_count) {
|
109
|
+
VALUE arr = rb_ary_new2(column_count);
|
110
|
+
for (int i = 0; i < column_count; i++) {
|
111
|
+
VALUE name = ID2SYM(rb_intern(sqlite3_column_name(stmt, i)));
|
112
|
+
rb_ary_push(arr, name);
|
113
|
+
}
|
114
|
+
return arr;
|
115
|
+
}
|
116
|
+
|
117
|
+
static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE column_names) {
|
118
|
+
VALUE row = rb_hash_new();
|
119
|
+
for (int i = 0; i < column_count; i++) {
|
120
|
+
VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
|
121
|
+
rb_hash_aset(row, RARRAY_AREF(column_names, i), value);
|
122
|
+
}
|
123
|
+
return row;
|
124
|
+
}
|
125
|
+
|
126
|
+
VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
|
127
|
+
int rc;
|
128
|
+
sqlite3_stmt* stmt;
|
129
|
+
int column_count;
|
130
|
+
Database_t *db;
|
131
|
+
VALUE result = self;
|
132
|
+
int yield_to_block = rb_block_given_p();
|
133
|
+
VALUE row;
|
134
|
+
VALUE column_names;
|
135
|
+
VALUE sql;
|
136
|
+
|
137
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
138
|
+
sql = argv[0];
|
139
|
+
GetDatabase(self, db);
|
140
|
+
|
141
|
+
rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
|
142
|
+
if (rc) {
|
143
|
+
sqlite3_finalize(stmt);
|
144
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
145
|
+
return Qnil;
|
146
|
+
}
|
147
|
+
|
148
|
+
bind_all_parameters(stmt, argc, argv);
|
149
|
+
column_count = sqlite3_column_count(stmt);
|
150
|
+
column_names = get_column_names(stmt, column_count);
|
151
|
+
|
152
|
+
// block not given, so prepare the array of records to be returned
|
153
|
+
if (!yield_to_block) result = rb_ary_new();
|
154
|
+
|
155
|
+
step:
|
156
|
+
rc = sqlite3_step(stmt);
|
157
|
+
switch (rc) {
|
158
|
+
case SQLITE_ROW:
|
159
|
+
row = row_to_hash(stmt, column_count, column_names);
|
160
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
161
|
+
goto step;
|
162
|
+
case SQLITE_DONE:
|
163
|
+
break;
|
164
|
+
case SQLITE_BUSY:
|
165
|
+
rb_raise(cError, "Database is busy");
|
166
|
+
case SQLITE_ERROR:
|
167
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
168
|
+
default:
|
169
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
170
|
+
}
|
171
|
+
sqlite3_finalize(stmt);
|
172
|
+
RB_GC_GUARD(column_names);
|
173
|
+
RB_GC_GUARD(row);
|
174
|
+
RB_GC_GUARD(result);
|
175
|
+
return result;
|
176
|
+
}
|
177
|
+
|
178
|
+
static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
|
179
|
+
VALUE row = rb_ary_new2(column_count);
|
180
|
+
for (int i = 0; i < column_count; i++) {
|
181
|
+
VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
|
182
|
+
rb_ary_push(row, value);
|
183
|
+
}
|
184
|
+
return row;
|
185
|
+
}
|
186
|
+
|
187
|
+
VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
|
188
|
+
int rc;
|
189
|
+
sqlite3_stmt* stmt;
|
190
|
+
int column_count;
|
191
|
+
Database_t *db;
|
192
|
+
VALUE result = self;
|
193
|
+
int yield_to_block = rb_block_given_p();
|
194
|
+
VALUE row;
|
195
|
+
VALUE sql;
|
196
|
+
|
197
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
198
|
+
sql = argv[0];
|
199
|
+
GetDatabase(self, db);
|
200
|
+
|
201
|
+
rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
|
202
|
+
if (rc) {
|
203
|
+
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
|
204
|
+
sqlite3_finalize(stmt);
|
205
|
+
// TODO: raise error
|
206
|
+
return Qfalse;
|
207
|
+
}
|
208
|
+
|
209
|
+
bind_all_parameters(stmt, argc, argv);
|
210
|
+
column_count = sqlite3_column_count(stmt);
|
211
|
+
|
212
|
+
// block not given, so prepare the array of records to be returned
|
213
|
+
if (!yield_to_block) result = rb_ary_new();
|
214
|
+
step:
|
215
|
+
rc = sqlite3_step(stmt);
|
216
|
+
switch (rc) {
|
217
|
+
case SQLITE_ROW:
|
218
|
+
row = row_to_ary(stmt, column_count);
|
219
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
220
|
+
goto step;
|
221
|
+
case SQLITE_DONE:
|
222
|
+
break;
|
223
|
+
case SQLITE_BUSY:
|
224
|
+
rb_raise(cError, "Database is busy");
|
225
|
+
case SQLITE_ERROR:
|
226
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
227
|
+
default:
|
228
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
229
|
+
}
|
230
|
+
sqlite3_finalize(stmt);
|
231
|
+
RB_GC_GUARD(row);
|
232
|
+
RB_GC_GUARD(result);
|
233
|
+
return result;
|
234
|
+
}
|
235
|
+
|
236
|
+
VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
|
237
|
+
int rc;
|
238
|
+
sqlite3_stmt* stmt;
|
239
|
+
int column_count;
|
240
|
+
Database_t *db;
|
241
|
+
VALUE result = self;
|
242
|
+
int yield_to_block = rb_block_given_p();
|
243
|
+
VALUE sql;
|
244
|
+
VALUE value;
|
245
|
+
|
246
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
247
|
+
sql = argv[0];
|
248
|
+
GetDatabase(self, db);
|
249
|
+
|
250
|
+
rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
|
251
|
+
if (rc) {
|
252
|
+
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
|
253
|
+
sqlite3_finalize(stmt);
|
254
|
+
// TODO: raise error
|
255
|
+
return Qfalse;
|
256
|
+
}
|
257
|
+
|
258
|
+
bind_all_parameters(stmt, argc, argv);
|
259
|
+
column_count = sqlite3_column_count(stmt);
|
260
|
+
if (column_count != 1)
|
261
|
+
rb_raise(cError, "Expected query result to have 1 column");
|
262
|
+
|
263
|
+
// block not given, so prepare the array of records to be returned
|
264
|
+
if (!yield_to_block) result = rb_ary_new();
|
265
|
+
step:
|
266
|
+
rc = sqlite3_step(stmt);
|
267
|
+
switch (rc) {
|
268
|
+
case SQLITE_ROW:
|
269
|
+
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
270
|
+
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
271
|
+
goto step;
|
272
|
+
case SQLITE_DONE:
|
273
|
+
break;
|
274
|
+
case SQLITE_BUSY:
|
275
|
+
rb_raise(cError, "Database is busy");
|
276
|
+
case SQLITE_ERROR:
|
277
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
278
|
+
default:
|
279
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
280
|
+
}
|
281
|
+
|
282
|
+
sqlite3_finalize(stmt);
|
283
|
+
RB_GC_GUARD(value);
|
284
|
+
RB_GC_GUARD(result);
|
285
|
+
return result;
|
286
|
+
}
|
287
|
+
|
288
|
+
VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
289
|
+
int rc;
|
290
|
+
sqlite3_stmt* stmt;
|
291
|
+
int column_count;
|
292
|
+
Database_t *db;
|
293
|
+
VALUE sql;
|
294
|
+
VALUE value = Qnil;
|
295
|
+
|
296
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
297
|
+
sql = argv[0];
|
298
|
+
GetDatabase(self, db);
|
299
|
+
|
300
|
+
rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
|
301
|
+
if (rc) {
|
302
|
+
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
|
303
|
+
sqlite3_finalize(stmt);
|
304
|
+
// TODO: raise error
|
305
|
+
return Qfalse;
|
306
|
+
}
|
307
|
+
|
308
|
+
bind_all_parameters(stmt, argc, argv);
|
309
|
+
column_count = sqlite3_column_count(stmt);
|
310
|
+
if (column_count != 1)
|
311
|
+
rb_raise(cError, "Expected query result to have 1 column");
|
312
|
+
|
313
|
+
rc = sqlite3_step(stmt);
|
314
|
+
switch (rc) {
|
315
|
+
case SQLITE_ROW:
|
316
|
+
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
317
|
+
break;
|
318
|
+
case SQLITE_BUSY:
|
319
|
+
rb_raise(cError, "Database is busy");
|
320
|
+
case SQLITE_ERROR:
|
321
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
322
|
+
default:
|
323
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
324
|
+
}
|
325
|
+
|
326
|
+
sqlite3_finalize(stmt);
|
327
|
+
RB_GC_GUARD(value);
|
328
|
+
return value;
|
329
|
+
}
|
330
|
+
|
331
|
+
VALUE Database_last_insert_rowid(VALUE self) {
|
332
|
+
Database_t *db;
|
333
|
+
GetDatabase(self, db);
|
334
|
+
|
335
|
+
return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
|
336
|
+
}
|
337
|
+
|
338
|
+
VALUE Database_changes(VALUE self) {
|
339
|
+
Database_t *db;
|
340
|
+
GetDatabase(self, db);
|
341
|
+
|
342
|
+
return INT2NUM(sqlite3_changes(db->sqlite3_db));
|
343
|
+
}
|
344
|
+
|
345
|
+
VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
346
|
+
const char *db_name;
|
347
|
+
const char *filename;
|
348
|
+
Database_t *db;
|
349
|
+
GetDatabase(self, db);
|
350
|
+
|
351
|
+
rb_check_arity(argc, 0, 1);
|
352
|
+
db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
|
353
|
+
filename = sqlite3_db_filename(db->sqlite3_db, db_name);
|
354
|
+
return filename ? rb_str_new_cstr(filename) : Qnil;
|
355
|
+
}
|
356
|
+
|
357
|
+
void Init_Extralite() {
|
358
|
+
VALUE mExtralite = rb_define_module("Extralite");
|
359
|
+
VALUE cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
|
360
|
+
rb_define_alloc_func(cDatabase, Database_allocate);
|
361
|
+
|
362
|
+
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
363
|
+
|
364
|
+
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
365
|
+
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
366
|
+
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
367
|
+
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
368
|
+
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
369
|
+
|
370
|
+
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
371
|
+
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
372
|
+
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
373
|
+
|
374
|
+
cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
|
375
|
+
}
|