do_sqlite3 0.9.9-x86-mswin32-60 → 0.9.11-x86-mswin32-60

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