extralite 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }