do_sqlite3 0.9.9 → 0.9.10

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.
data/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- pkg
2
- *.db
3
- spec/test.db
1
+ test.db
2
+ test.db-journal
3
+ tmp/
@@ -5,8 +5,11 @@ Manifest.txt
5
5
  README.txt
6
6
  Rakefile
7
7
  TODO
8
- ext/do_sqlite3_ext.c
9
- ext/extconf.rb
8
+ buildfile
9
+ ext-java/src/main/java/DoSqlite3ExtService.java
10
+ ext-java/src/main/java/do_sqlite3/Sqlite3DriverDefinition.java
11
+ ext/do_sqlite3_ext/do_sqlite3_ext.c
12
+ ext/do_sqlite3_ext/extconf.rb
10
13
  lib/do_sqlite3.rb
11
14
  lib/do_sqlite3/transaction.rb
12
15
  lib/do_sqlite3/version.rb
data/Rakefile CHANGED
@@ -1,30 +1,32 @@
1
+ require 'pathname'
1
2
  require 'rubygems'
2
3
  require 'spec/rake/spectask'
3
- require 'pathname'
4
+ require 'lib/do_sqlite3/version'
4
5
 
5
- ROOT = Pathname(__FILE__).dirname.expand_path
6
6
 
7
- require "lib/do_sqlite3/version"
8
7
 
9
- JRUBY = (RUBY_PLATFORM =~ /java/) rescue nil
10
- WINDOWS = (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) rescue nil
11
- # don't use SUDO with JRuby, for the moment, although this behaviour
12
- # is not entirely correct.
8
+ ROOT = Pathname(__FILE__).dirname.expand_path
9
+ JRUBY = RUBY_PLATFORM =~ /java/
10
+ WINDOWS = Gem.win_platform?
13
11
  SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
14
12
 
15
13
  AUTHOR = "Bernerd Schaefer"
16
14
  EMAIL = "bj.schaefer@gmail.com"
17
15
  GEM_NAME = "do_sqlite3"
18
16
  GEM_VERSION = DataObjects::Sqlite3::VERSION
19
- GEM_DEPENDENCIES = [["data_objects", GEM_VERSION]]
17
+ GEM_DEPENDENCIES = if JRUBY
18
+ [["data_objects", GEM_VERSION], ["do_jdbc", GEM_VERSION], ["jdbc-sqlite3", ">=3.5.8"]]
19
+ else
20
+ [["data_objects", GEM_VERSION]]
21
+ end
20
22
 
21
23
  # TODO: remove duplicates from here that are already listed in .gitignore
22
24
  clean = %w(o bundle log a gem dSYM obj pdb lib def exp DS_Store)
23
25
 
24
- unless ENV["WINDOWS"]
25
- GEM_EXTRAS = { :extensions => %w[ ext/extconf.rb ], :has_rdoc => false }
26
+ if WINDOWS
27
+ GEM_EXTRAS = { :has_rdoc => false }
26
28
  else
27
- GEM_EXTRAS = {}
29
+ GEM_EXTRAS = { :extensions => 'ext/do_sqlite3_ext/extconf.rb', :has_rdoc => false }
28
30
  end
29
31
 
30
32
  GEM_CLEAN = ["**/*.{#{clean.join(",")}}", 'ext/Makefile']
@@ -33,7 +35,10 @@ PROJECT_NAME = "dorb"
33
35
  PROJECT_URL = "http://rubyforge.org/projects/dorb"
34
36
  PROJECT_DESCRIPTION = PROJECT_SUMMARY = "A DataObject.rb driver for Sqlite3"
35
37
 
36
- DRIVER = true
38
+ JAVA_DRIVER = true
39
+
40
+ # RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
41
+ RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
37
42
 
38
43
  if (tasks_dir = ROOT.parent + 'tasks').directory?
39
44
  require tasks_dir + 'hoe'
@@ -45,15 +50,20 @@ if (tasks_dir = ROOT.parent + 'tasks').directory?
45
50
  end
46
51
  end
47
52
 
53
+ def sudo_gem(cmd)
54
+ sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
55
+ end
56
+
48
57
  # Installation
49
58
 
59
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
50
60
  task :install => [ :package ] do
51
- sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
61
+ sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
52
62
  end
53
63
 
54
- desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
64
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
55
65
  task :uninstall => [ :clobber ] do
56
- sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
66
+ sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
57
67
  end
58
68
 
59
69
  desc 'Run specifications'
@@ -63,7 +73,7 @@ Spec::Rake::SpecTask.new(:spec) do |t|
63
73
  t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
64
74
 
65
75
  begin
66
- t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
76
+ t.rcov = RUN_RCOV
67
77
  t.rcov_opts << '--exclude' << 'spec'
68
78
  t.rcov_opts << '--text-summary'
69
79
  t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
@@ -110,8 +120,6 @@ CCROOT = ROOT.parent
110
120
 
111
121
  SQLITE_VERSION = '3_6_6_2'
112
122
 
113
- MINGW_PATH = ENV['MINGW_PATH'] || "/usr/i586-mingw32msvc"
114
-
115
123
  if (tasks_dir = ROOT.parent + 'tasks').directory?
116
124
  require tasks_dir + 'win32'
117
125
 
@@ -119,8 +127,6 @@ if (tasks_dir = ROOT.parent + 'tasks').directory?
119
127
 
120
128
  namespace :build do
121
129
 
122
- task :externals => "win32:sqlite3"
123
-
124
130
  namespace :win32 do
125
131
 
126
132
  desc "Creates cross compiled sqlite3 libraries"
@@ -162,43 +168,25 @@ if (tasks_dir = ROOT.parent + 'tasks').directory?
162
168
  file "#{STASH}/sqlitedll-#{SQLITE_VERSION}.zip" => STASH do |t|
163
169
  download_file(STASH, "http://www.sqlite.org/#{File.basename(t.name)}")
164
170
  end
165
-
166
- # The extension. This is a task so it's rebuilt each time it is run, so we're sure to have a windows
167
- # compiled extension.
168
- task "lib/do_sqlite3_ext.so" => [:clean, "build:externals"] + FileList["ext/extconf.rb", "ext/*.c", "ext/*.h"] do
169
- when_writing "Creating compiled extension" do
170
- cd('ext') do
171
- ruby " -I #{CROSS}/lib/ruby/1.8/i386-mingw32/ extconf.rb -- --with-sqlite3-dir=#{SQLITE_DIR}"
172
- sh 'make'
173
- end
174
- mv 'ext/do_sqlite3_ext.so', 'lib'
175
- end
176
- end
177
-
178
- task :extension => "lib/do_sqlite3_ext.so"
179
-
180
171
  end
181
172
  end
182
173
 
183
- namespace :gem do
184
- namespace :win32 do
185
- desc "Package pre-compiled win32 gem"
186
- task :package => "pkg/#{GEM_NAME}-#{GEM_VERSION}-x86-mswin32-60.gem"
187
-
188
- file "pkg/#{GEM_NAME}-#{GEM_VERSION}-x86-mswin32-60.gem" => ["build:win32:extension", "pkg"] + HOE.spec.files do |t|
189
- spec = HOE.spec.dup
190
- spec.extensions = nil
191
- spec.files += Dir['lib/**.so']
192
- spec.platform = 'x86-mswin32-60'
193
- spec.post_install_message = <<-eos
194
- Now download http://www.sqlite.org/sqlitedll-#{SQLITE_VERSION}.zip
195
- And place the dll somewhere in your PATH, for example C:\\ruby\\bin
196
- eos
197
- when_writing "Building win32 do_sqlite3 gem" do
198
- Gem::Builder.new(spec).build
199
- end
200
- mv File.basename(t.name), t.name
201
- end
202
- end
174
+ task :cross => 'build:win32:sqlite3'
175
+ end
176
+
177
+ begin
178
+ gem('rake-compiler')
179
+ require 'rake/extensiontask'
180
+ Rake::ExtensionTask.new('do_sqlite3_ext', HOE.spec) do |ext|
181
+ ext.cross_compile = true # enable cross compilation (requires cross compile toolchain)
182
+ ext.cross_platform = 'i386-mswin32'
183
+ ext.cross_config_options << "--with-sqlite3-dir=#{SQLITE_DIR}"
184
+ end
185
+ rescue LoadError
186
+ warn "To cross-compile, install rake-compiler (gem install rake-compiler)"
187
+ if tasks_dir.directory?
188
+ require tasks_dir + 'ext_helper'
189
+ setup_extension('do_sqlite3_ext', HOE.spec)
203
190
  end
204
191
  end
192
+
@@ -0,0 +1,26 @@
1
+ # Apache Buildr buildfile for do_derby
2
+ # see http://incubator.apache.org/buildr/ for more information on Apache Buildr
3
+ require 'buildr'
4
+ require 'pathname'
5
+
6
+ VERSION_NUMBER = '1.0'
7
+ JDBC_SUPPORT = ['data_objects:jdbc:jar:1.0']
8
+ repositories.remote << 'http://www.ibiblio.org/maven2/'
9
+
10
+ define 'do_sqlite3' do
11
+ project.version = VERSION_NUMBER
12
+ project.group = 'data_objects.rb'
13
+
14
+ manifest['Copyright'] = 'Alex Coles (C) 2008'
15
+
16
+ compile.using :target => '1.5', :lint => 'all', :deprecation => 'true'
17
+
18
+ define 'ext-java' do
19
+ package :jar
20
+
21
+ jdbc_support_jar = file('../../do_jdbc/lib/do_jdbc_internal.jar')
22
+ jdbc_support = artifact('data_objects:jdbc:jar:1.0').from(jdbc_support_jar)
23
+
24
+ compile.with JDBC_SUPPORT
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ import data_objects.drivers.DriverDefinition;
2
+ import do_sqlite3.Sqlite3DriverDefinition;
3
+
4
+ public class DoSqlite3ExtService extends AbstractDataObjectsExtService {
5
+
6
+ private final static DriverDefinition driver = new Sqlite3DriverDefinition();
7
+ public final static String RUBY_MODULE_NAME = "Sqlite3";
8
+ public final static String RUBY_ERROR_NAME = "Sqlite3Error";
9
+
10
+ public String getModuleName() {
11
+ return RUBY_MODULE_NAME;
12
+ }
13
+
14
+ @Override
15
+ public String getErrorName() {
16
+ return RUBY_ERROR_NAME;
17
+ }
18
+
19
+ public DriverDefinition getDriverDefinition() {
20
+ return driver;
21
+ }
22
+
23
+ }
@@ -0,0 +1,21 @@
1
+ package do_sqlite3;
2
+
3
+ import data_objects.drivers.AbstractDriverDefinition;
4
+
5
+ public class Sqlite3DriverDefinition extends AbstractDriverDefinition {
6
+
7
+ public boolean supportsJdbcGeneratedKeys()
8
+ {
9
+ return false;
10
+ }
11
+
12
+ //@Override
13
+ public String quoteString(String str) {
14
+ StringBuffer quotedValue = new StringBuffer(str.length() + 2);
15
+ quotedValue.append("\'");
16
+ quotedValue.append(str.replaceAll("'", "''"));
17
+ quotedValue.append("\'");
18
+ return quotedValue.toString();
19
+ }
20
+
21
+ }
@@ -0,0 +1,584 @@
1
+ #include <ruby.h>
2
+ #include <string.h>
3
+ #include <math.h>
4
+ #include <time.h>
5
+ #include <locale.h>
6
+ #include <sqlite3.h>
7
+
8
+ #define ID_CONST_GET rb_intern("const_get")
9
+ #define ID_PATH rb_intern("path")
10
+ #define ID_NEW rb_intern("new")
11
+ #define ID_ESCAPE rb_intern("escape_sql")
12
+
13
+ #define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
14
+ #define TAINTED_STRING(name, length) rb_tainted_str_new(name, length)
15
+ #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
16
+ #define SQLITE3_CLASS(klass, parent) (rb_define_class_under(mSqlite3, klass, parent))
17
+
18
+ #define TRUE_CLASS CONST_GET(rb_mKernel, "TrueClass")
19
+
20
+ #ifndef RSTRING_PTR
21
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
22
+ #endif
23
+
24
+ #ifndef RSTRING_LEN
25
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
26
+ #endif
27
+
28
+ #ifndef RARRAY_LEN
29
+ #define RARRAY_LEN(a) RARRAY(a)->len
30
+ #endif
31
+
32
+ #ifdef _WIN32
33
+ #define do_int64 signed __int64
34
+ #else
35
+ #define do_int64 signed long long int
36
+ #endif
37
+
38
+ #ifndef HAVE_SQLITE3_PREPARE_V2
39
+ #define sqlite3_prepare_v2 sqlite3_prepare
40
+ #endif
41
+
42
+ // To store rb_intern values
43
+ static ID ID_NEW_DATE;
44
+ static ID ID_LOGGER;
45
+ static ID ID_DEBUG;
46
+ static ID ID_LEVEL;
47
+
48
+ static VALUE mDO;
49
+ static VALUE cDO_Quoting;
50
+ static VALUE cDO_Connection;
51
+ static VALUE cDO_Command;
52
+ static VALUE cDO_Result;
53
+ static VALUE cDO_Reader;
54
+
55
+ static VALUE rb_cDate;
56
+ static VALUE rb_cDateTime;
57
+
58
+ #ifndef RUBY_19_COMPATIBILITY
59
+ static VALUE rb_cRational;
60
+ #endif
61
+
62
+ static VALUE rb_cBigDecimal;
63
+
64
+ static VALUE mSqlite3;
65
+ static VALUE cConnection;
66
+ static VALUE cCommand;
67
+ static VALUE cResult;
68
+ static VALUE cReader;
69
+
70
+ static VALUE eSqlite3Error;
71
+
72
+ // Find the greatest common denominator and reduce the provided numerator and denominator.
73
+ // This replaces calles to Rational.reduce! which does the same thing, but really slowly.
74
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
75
+ do_int64 a, b, c = 0;
76
+ a = *numerator;
77
+ b = *denominator;
78
+ while ( a != 0 ) {
79
+ c = a; a = b % a; b = c;
80
+ }
81
+ *numerator = *numerator / b;
82
+ *denominator = *denominator / b;
83
+ }
84
+
85
+ // Generate the date integer which Date.civil_to_jd returns
86
+ static int jd_from_date(int year, int month, int day) {
87
+ int a, b;
88
+ if ( month <= 2 ) {
89
+ year -= 1;
90
+ month += 12;
91
+ }
92
+ a = year / 100;
93
+ b = 2 - a + (a / 4);
94
+ return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
95
+ }
96
+
97
+ static void data_objects_debug(VALUE string, struct timeval* start) {
98
+ struct timeval stop;
99
+ char *message;
100
+
101
+ char *query = RSTRING_PTR(string);
102
+ int length = RSTRING_LEN(string);
103
+ char total_time[32];
104
+ do_int64 duration = 0;
105
+
106
+ VALUE logger = rb_funcall(mSqlite3, ID_LOGGER, 0);
107
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
108
+
109
+ if (0 == log_level) {
110
+ gettimeofday(&stop, NULL);
111
+
112
+ duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
113
+ if(stop.tv_usec < start->tv_usec) {
114
+ duration += 1000000;
115
+ }
116
+
117
+ snprintf(total_time, 32, "%.6f", duration / 1000000.0);
118
+ message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
119
+ snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
120
+ rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
121
+ }
122
+ }
123
+
124
+
125
+ static VALUE parse_date(char *date) {
126
+ int year, month, day;
127
+ int jd, ajd;
128
+ VALUE rational;
129
+
130
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
131
+
132
+ jd = jd_from_date(year, month, day);
133
+
134
+ // Math from Date.jd_to_ajd
135
+ ajd = jd * 2 - 1;
136
+ rational = rb_funcall(rb_cRational, rb_intern("new!"), 2, INT2NUM(ajd), INT2NUM(2));
137
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
138
+ }
139
+
140
+ // Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
141
+ static VALUE seconds_to_offset(do_int64 num) {
142
+ do_int64 den = 86400;
143
+ reduce(&num, &den);
144
+ return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
145
+ }
146
+
147
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
148
+ do_int64 seconds = 0;
149
+
150
+ seconds += hour_offset * 3600;
151
+ seconds += minute_offset * 60;
152
+
153
+ return seconds_to_offset(seconds);
154
+ }
155
+
156
+ static VALUE parse_date_time(char *date) {
157
+ VALUE ajd, offset;
158
+
159
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
160
+ int jd;
161
+ do_int64 num, den;
162
+
163
+ long int gmt_offset;
164
+ int is_dst;
165
+
166
+ time_t rawtime;
167
+ struct tm * timeinfo;
168
+
169
+ int tokens_read, max_tokens;
170
+
171
+ if ( strcmp(date, "") == 0 ) {
172
+ return Qnil;
173
+ }
174
+
175
+ if (0 != strchr(date, '.')) {
176
+ // This is a datetime with sub-second precision
177
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
178
+ max_tokens = 9;
179
+ } else {
180
+ // This is a datetime second precision
181
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
182
+ max_tokens = 8;
183
+ }
184
+
185
+ if (max_tokens == tokens_read) {
186
+ // We read the Date, Time, and Timezone info
187
+ minute_offset *= hour_offset < 0 ? -1 : 1;
188
+ } else if ((max_tokens - 1) == tokens_read) {
189
+ // We read the Date and Time, but no Minute Offset
190
+ minute_offset = 0;
191
+ } else if (tokens_read == 3) {
192
+ return parse_date(date);
193
+ } else if (tokens_read >= (max_tokens - 3)) {
194
+ // We read the Date and Time, default to the current locale's offset
195
+
196
+ // Get localtime
197
+ time(&rawtime);
198
+ timeinfo = localtime(&rawtime);
199
+
200
+ is_dst = timeinfo->tm_isdst * 3600;
201
+
202
+ // Reset to GM Time
203
+ timeinfo = gmtime(&rawtime);
204
+
205
+ gmt_offset = mktime(timeinfo) - rawtime;
206
+
207
+ if ( is_dst > 0 )
208
+ gmt_offset -= is_dst;
209
+
210
+ hour_offset = -(gmt_offset / 3600);
211
+ minute_offset = -(gmt_offset % 3600 / 60);
212
+
213
+ } else {
214
+ // Something went terribly wrong
215
+ rb_raise(eSqlite3Error, "Couldn't parse date: %s", date);
216
+ }
217
+
218
+ jd = jd_from_date(year, month, day);
219
+
220
+ // Generate ajd with fractional days for the time
221
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
222
+ num = (hour * 1440) + (min * 24);
223
+
224
+ // Modify the numerator so when we apply the timezone everything works out
225
+ num -= (hour_offset * 1440) + (minute_offset * 24);
226
+
227
+ den = (24 * 1440);
228
+ reduce(&num, &den);
229
+
230
+ num = (num * 86400) + (sec * den);
231
+ den = den * 86400;
232
+ reduce(&num, &den);
233
+
234
+ num = (jd * den) + num;
235
+
236
+ num = num * 2;
237
+ num = num - den;
238
+ den = den * 2;
239
+
240
+ reduce(&num, &den);
241
+
242
+ ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
243
+ offset = timezone_to_offset(hour_offset, minute_offset);
244
+
245
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
246
+ }
247
+
248
+ static VALUE parse_time(char *date) {
249
+
250
+ int year, month, day, hour, min, sec, usec;
251
+ char subsec[7];
252
+
253
+ if (0 != strchr(date, '.')) {
254
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
255
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
256
+ sscanf(subsec, "%d", &usec);
257
+ } else {
258
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
259
+ usec = 0;
260
+ }
261
+
262
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
263
+ }
264
+
265
+ static VALUE typecast(sqlite3_stmt *stmt, int i, VALUE ruby_class) {
266
+ char *ruby_type;
267
+ VALUE ruby_value = Qnil;
268
+ int original_type = sqlite3_column_type(stmt, i);
269
+ int length = sqlite3_column_bytes(stmt, i);
270
+ if ( original_type == SQLITE_NULL ) {
271
+ return ruby_value;
272
+ }
273
+
274
+ if ( original_type == SQLITE_BLOB ) {
275
+ return TAINTED_STRING((char*)sqlite3_column_blob(stmt, i), length);
276
+ }
277
+
278
+ if(ruby_class == Qnil) {
279
+ switch(original_type) {
280
+ case SQLITE_INTEGER: {
281
+ ruby_type = "Integer";
282
+ break;
283
+ }
284
+ case SQLITE_FLOAT: {
285
+ ruby_type = "Float";
286
+ break;
287
+ }
288
+ default: {
289
+ ruby_type = "String";
290
+ break;
291
+ }
292
+ }
293
+ } else {
294
+ ruby_type = rb_class2name(ruby_class);
295
+ }
296
+
297
+ if ( strcmp(ruby_type, "Class") == 0) {
298
+ return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING((char*)sqlite3_column_text(stmt, i), length));
299
+ } else if ( strcmp(ruby_type, "Object") == 0 ) {
300
+ return rb_marshal_load(rb_str_new2((char*)sqlite3_column_text(stmt, i)));
301
+ } else if ( strcmp(ruby_type, "TrueClass") == 0 ) {
302
+ return strcmp((char*)sqlite3_column_text(stmt, i), "t") == 0 ? Qtrue : Qfalse;
303
+ } else if ( strcmp(ruby_type, "Integer") == 0 || strcmp(ruby_type, "Fixnum") == 0 || strcmp(ruby_type, "Bignum") == 0 ) {
304
+ return LL2NUM(sqlite3_column_int64(stmt, i));
305
+ } else if ( strcmp(ruby_type, "BigDecimal") == 0 ) {
306
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING((char*)sqlite3_column_text(stmt, i), length));
307
+ } else if ( strcmp(ruby_type, "Float") == 0 ) {
308
+ return rb_float_new(sqlite3_column_double(stmt, i));
309
+ } else if ( strcmp(ruby_type, "Date") == 0 ) {
310
+ return parse_date((char*)sqlite3_column_text(stmt, i));
311
+ } else if ( strcmp(ruby_type, "DateTime") == 0 ) {
312
+ return parse_date_time((char*)sqlite3_column_text(stmt, i));
313
+ } else if ( strcmp(ruby_type, "Time") == 0 ) {
314
+ return parse_time((char*)sqlite3_column_text(stmt, i));
315
+ } else {
316
+ return TAINTED_STRING((char*)sqlite3_column_text(stmt, i), length);
317
+ }
318
+ }
319
+
320
+
321
+ /****** Public API ******/
322
+
323
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
324
+ int ret;
325
+ VALUE path;
326
+ sqlite3 *db;
327
+
328
+ path = rb_funcall(uri, ID_PATH, 0);
329
+ ret = sqlite3_open(StringValuePtr(path), &db);
330
+
331
+ if ( ret != SQLITE_OK ) {
332
+ rb_raise(eSqlite3Error, sqlite3_errmsg(db));
333
+ }
334
+
335
+ rb_iv_set(self, "@uri", uri);
336
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
337
+
338
+ return Qtrue;
339
+ }
340
+
341
+ static VALUE cConnection_dispose(VALUE self) {
342
+ sqlite3 *db;
343
+ Data_Get_Struct(rb_iv_get(self, "@connection"), sqlite3, db);
344
+ sqlite3_close(db);
345
+ return Qtrue;
346
+ }
347
+
348
+ static VALUE cCommand_set_types(VALUE self, VALUE array) {
349
+ rb_iv_set(self, "@field_types", array);
350
+ return array;
351
+ }
352
+
353
+ static VALUE cCommand_quote_boolean(VALUE self, VALUE value) {
354
+ return rb_tainted_str_new2(value == Qtrue ? "'t'" : "'f'");
355
+ }
356
+
357
+ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
358
+ const char *source = StringValuePtr(string);
359
+ char *escaped_with_quotes;
360
+
361
+ // Wrap the escaped string in single-quotes, this is DO's convention
362
+ escaped_with_quotes = sqlite3_mprintf("%Q", source);
363
+
364
+ return rb_tainted_str_new2(escaped_with_quotes);
365
+ }
366
+
367
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args) {
368
+ VALUE query = rb_iv_get(klass, "@text");
369
+ if ( count > 0 ) {
370
+ int i;
371
+ VALUE array = rb_ary_new();
372
+ for ( i = 0; i < count; i++) {
373
+ rb_ary_push(array, (VALUE)args[i]);
374
+ }
375
+ query = rb_funcall(klass, ID_ESCAPE, 1, array);
376
+ }
377
+ return query;
378
+ }
379
+
380
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
381
+ sqlite3 *db;
382
+ char *error_message;
383
+ int status;
384
+ int affected_rows;
385
+ int insert_id;
386
+ VALUE conn_obj;
387
+ VALUE query;
388
+ struct timeval start;
389
+
390
+ query = build_query_from_args(self, argc, argv);
391
+
392
+ conn_obj = rb_iv_get(self, "@connection");
393
+ Data_Get_Struct(rb_iv_get(conn_obj, "@connection"), sqlite3, db);
394
+
395
+ gettimeofday(&start, NULL);
396
+ status = sqlite3_exec(db, StringValuePtr(query), 0, 0, &error_message);
397
+
398
+ if ( status != SQLITE_OK ) {
399
+ rb_raise(eSqlite3Error, "%s\nQuery: %s", sqlite3_errmsg(db), StringValuePtr(query));
400
+ }
401
+ data_objects_debug(query, &start);
402
+
403
+ affected_rows = sqlite3_changes(db);
404
+ insert_id = sqlite3_last_insert_rowid(db);
405
+
406
+ return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(insert_id));
407
+ }
408
+
409
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
410
+ sqlite3 *db;
411
+ sqlite3_stmt *sqlite3_reader;
412
+ int status;
413
+ int field_count;
414
+ int i;
415
+ VALUE reader;
416
+ VALUE conn_obj;
417
+ VALUE query;
418
+ VALUE field_names, field_types;
419
+ struct timeval start;
420
+
421
+ conn_obj = rb_iv_get(self, "@connection");
422
+ Data_Get_Struct(rb_iv_get(conn_obj, "@connection"), sqlite3, db);
423
+
424
+ query = build_query_from_args(self, argc, argv);
425
+
426
+ gettimeofday(&start, NULL);
427
+ status = sqlite3_prepare_v2(db, StringValuePtr(query), -1, &sqlite3_reader, 0);
428
+ data_objects_debug(query, &start);
429
+
430
+ if ( status != SQLITE_OK ) {
431
+ rb_raise(eSqlite3Error, "%s\nQuery: %s", sqlite3_errmsg(db), StringValuePtr(query));
432
+ }
433
+
434
+ field_count = sqlite3_column_count(sqlite3_reader);
435
+
436
+ reader = rb_funcall(cReader, ID_NEW, 0);
437
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, sqlite3_reader));
438
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
439
+
440
+ field_names = rb_ary_new();
441
+ field_types = rb_iv_get(self, "@field_types");
442
+
443
+ // if ( field_types == Qnil ) {
444
+ // field_types = rb_ary_new();
445
+ // }
446
+
447
+ if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
448
+ field_types = rb_ary_new();
449
+ } else if (RARRAY_LEN(field_types) != field_count) {
450
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
451
+ // and error
452
+ rb_funcall(reader, rb_intern("close"), 0);
453
+ rb_raise(eSqlite3Error, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
454
+ }
455
+
456
+ for ( i = 0; i < field_count; i++ ) {
457
+ rb_ary_push(field_names, rb_str_new2((char *)sqlite3_column_name(sqlite3_reader, i)));
458
+ }
459
+
460
+ rb_iv_set(reader, "@fields", field_names);
461
+ rb_iv_set(reader, "@field_types", field_types);
462
+
463
+ return reader;
464
+ }
465
+
466
+ static VALUE cReader_close(VALUE self) {
467
+ VALUE reader_obj = rb_iv_get(self, "@reader");
468
+
469
+ if ( reader_obj != Qnil ) {
470
+ sqlite3_stmt *reader;
471
+ Data_Get_Struct(reader_obj, sqlite3_stmt, reader);
472
+ sqlite3_finalize(reader);
473
+ rb_iv_set(self, "@reader", Qnil);
474
+ return Qtrue;
475
+ }
476
+ else {
477
+ return Qfalse;
478
+ }
479
+ }
480
+
481
+ static VALUE cReader_next(VALUE self) {
482
+ sqlite3_stmt *reader;
483
+ int field_count;
484
+ int result;
485
+ int i;
486
+ int ft_length;
487
+ VALUE arr = rb_ary_new();
488
+ VALUE field_types;
489
+ VALUE value;
490
+
491
+ Data_Get_Struct(rb_iv_get(self, "@reader"), sqlite3_stmt, reader);
492
+ field_count = NUM2INT(rb_iv_get(self, "@field_count"));
493
+
494
+ field_types = rb_iv_get(self, "@field_types");
495
+ ft_length = RARRAY_LEN(field_types);
496
+
497
+ result = sqlite3_step(reader);
498
+
499
+ rb_iv_set(self, "@state", INT2NUM(result));
500
+
501
+ if ( result != SQLITE_ROW ) {
502
+ return Qnil;
503
+ }
504
+
505
+ for ( i = 0; i < field_count; i++ ) {
506
+ value = typecast(reader, i, rb_ary_entry(field_types, i));
507
+ rb_ary_push(arr, value);
508
+ }
509
+
510
+ rb_iv_set(self, "@values", arr);
511
+
512
+ return Qtrue;
513
+ }
514
+
515
+ static VALUE cReader_values(VALUE self) {
516
+ VALUE state = rb_iv_get(self, "@state");
517
+ if ( state == Qnil || NUM2INT(state) != SQLITE_ROW ) {
518
+ rb_raise(eSqlite3Error, "Reader is not initialized");
519
+ }
520
+ else {
521
+ return rb_iv_get(self, "@values");
522
+ }
523
+ }
524
+
525
+ static VALUE cReader_fields(VALUE self) {
526
+ return rb_iv_get(self, "@fields");
527
+ }
528
+
529
+ void Init_do_sqlite3_ext() {
530
+ rb_require("bigdecimal");
531
+ rb_require("date");
532
+
533
+ // Get references classes needed for Date/Time parsing
534
+ rb_cDate = CONST_GET(rb_mKernel, "Date");
535
+ rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
536
+ rb_cTime = CONST_GET(rb_mKernel, "Time");
537
+ rb_cRational = CONST_GET(rb_mKernel, "Rational");
538
+ rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
539
+
540
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
541
+
542
+ #ifdef RUBY_LESS_THAN_186
543
+ ID_NEW_DATE = rb_intern("new0");
544
+ #else
545
+ ID_NEW_DATE = rb_intern("new!");
546
+ #endif
547
+ ID_LOGGER = rb_intern("logger");
548
+ ID_DEBUG = rb_intern("debug");
549
+ ID_LEVEL = rb_intern("level");
550
+
551
+ // Get references to the DataObjects module and its classes
552
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
553
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
554
+ cDO_Connection = CONST_GET(mDO, "Connection");
555
+ cDO_Command = CONST_GET(mDO, "Command");
556
+ cDO_Result = CONST_GET(mDO, "Result");
557
+ cDO_Reader = CONST_GET(mDO, "Reader");
558
+
559
+ // Initialize the DataObjects::Sqlite3 module, and define its classes
560
+ mSqlite3 = rb_define_module_under(mDO, "Sqlite3");
561
+
562
+ eSqlite3Error = rb_define_class("Sqlite3Error", rb_eStandardError);
563
+
564
+ cConnection = SQLITE3_CLASS("Connection", cDO_Connection);
565
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
566
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
567
+
568
+ cCommand = SQLITE3_CLASS("Command", cDO_Command);
569
+ rb_include_module(cCommand, cDO_Quoting);
570
+ rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
571
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
572
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
573
+ rb_define_method(cCommand, "quote_boolean", cCommand_quote_boolean, 1);
574
+ rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
575
+
576
+ cResult = SQLITE3_CLASS("Result", cDO_Result);
577
+
578
+ cReader = SQLITE3_CLASS("Reader", cDO_Reader);
579
+ rb_define_method(cReader, "close", cReader_close, 0);
580
+ rb_define_method(cReader, "next!", cReader_next, 0);
581
+ rb_define_method(cReader, "values", cReader_values, 0);
582
+ rb_define_method(cReader, "fields", cReader_fields, 0);
583
+
584
+ }