do_sqlite3 0.9.9 → 0.9.10

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