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 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
+ }