do_postgres 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 +0,0 @@
1
- pkg
data/Manifest.txt CHANGED
@@ -6,9 +6,11 @@ README.txt
6
6
  Rakefile
7
7
  TODO
8
8
  autobuild.rb
9
- ext/do_postgres_ext.c
10
- ext/extconf.rb
11
- ext/type-oids.h
9
+ buildfile
10
+ ext-java/src/main/java/DoPostgresExtService.java
11
+ ext-java/src/main/java/do_postgres/PostgresDriverDefinition.java
12
+ ext/do_postgres_ext/do_postgres_ext.c
13
+ ext/do_postgres_ext/extconf.rb
12
14
  lib/do_postgres.rb
13
15
  lib/do_postgres/transaction.rb
14
16
  lib/do_postgres/version.rb
data/Rakefile CHANGED
@@ -1,44 +1,67 @@
1
+ require 'pathname'
1
2
  require 'rubygems'
2
3
  require 'spec/rake/spectask'
3
- require 'pathname'
4
-
5
- ROOT = Pathname(__FILE__).dirname.expand_path
4
+ require 'lib/do_postgres/version'
6
5
 
7
- require "lib/do_postgres/version"
8
6
 
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.
7
+ ROOT = Pathname(__FILE__).dirname.expand_path
8
+ JRUBY = RUBY_PLATFORM =~ /java/
9
+ WINDOWS = Gem.win_platform?
13
10
  SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
14
11
 
15
12
  AUTHOR = "Bernerd Schaefer"
16
13
  EMAIL = "bj.schaefer@gmail.com"
17
14
  GEM_NAME = "do_postgres"
18
15
  GEM_VERSION = DataObjects::Postgres::VERSION
19
- GEM_DEPENDENCIES = [["data_objects", GEM_VERSION]]
16
+ GEM_DEPENDENCIES = if JRUBY
17
+ [["data_objects", GEM_VERSION], ["do_jdbc", GEM_VERSION], ["jdbc-postgres", ">=8.2"]]
18
+ else
19
+ [["data_objects", GEM_VERSION]]
20
+ end
20
21
  GEM_CLEAN = ['**/*.{o,so,bundle,log,a,gem,dSYM,obj,pdb,lib,def,exp,DS_Store}', 'ext/Makefile']
21
- GEM_EXTRAS = { :extensions => %w[ ext/extconf.rb ], :has_rdoc => false }
22
+ GEM_EXTRAS = { :has_rdoc => false, :extensions => 'ext/do_postgres_ext/extconf.rb' }
22
23
 
23
24
  PROJECT_NAME = "dorb"
24
25
  PROJECT_URL = "http://rubyforge.org/projects/dorb"
25
26
  PROJECT_DESCRIPTION = PROJECT_SUMMARY = "A DataObject.rb driver for MySQL"
26
27
 
27
- DRIVER = true
28
+ JAVA_DRIVER = true
29
+
30
+ # RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
31
+ RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
28
32
 
29
33
  if (tasks_dir = ROOT.parent + 'tasks').directory?
30
34
  require tasks_dir + 'hoe'
31
35
  end
32
36
 
37
+ # compile the extension
38
+ begin
39
+ gem('rake-compiler')
40
+ require 'rake/extensiontask'
41
+ Rake::ExtensionTask.new('do_postgres_ext', HOE.spec)
42
+ rescue LoadError
43
+ warn "To cross-compile, install rake-compiler (gem install rake-compiler)"
44
+ if tasks_dir.directory?
45
+ require tasks_dir + 'ext_helper'
46
+ setup_extension('do_postgres_ext', HOE.spec)
47
+ end
48
+ end
49
+
50
+
51
+ def sudo_gem(cmd)
52
+ sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
53
+ end
54
+
33
55
  # Installation
34
56
 
57
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
35
58
  task :install => [ :package ] do
36
- sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
59
+ sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
37
60
  end
38
61
 
39
- desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
62
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
40
63
  task :uninstall => [ :clobber ] do
41
- sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
64
+ sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
42
65
  end
43
66
 
44
67
  desc 'Run specifications'
@@ -48,7 +71,7 @@ Spec::Rake::SpecTask.new(:spec) do |t|
48
71
  t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
49
72
 
50
73
  begin
51
- t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
74
+ t.rcov = RUN_RCOV
52
75
  t.rcov_opts << '--exclude' << 'spec'
53
76
  t.rcov_opts << '--text-summary'
54
77
  t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
data/buildfile ADDED
@@ -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_postgres' 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_postgres.PostgresDriverDefinition;
3
+
4
+ public class DoPostgresExtService extends AbstractDataObjectsExtService {
5
+
6
+ private final static DriverDefinition driver = new PostgresDriverDefinition();
7
+ public final static String RUBY_MODULE_NAME = "Postgres";
8
+ public final static String RUBY_ERROR_NAME = "PostgresError";
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,12 @@
1
+ package do_postgres;
2
+
3
+ import data_objects.drivers.AbstractDriverDefinition;
4
+
5
+ public class PostgresDriverDefinition extends AbstractDriverDefinition {
6
+
7
+ public boolean supportsJdbcGeneratedKeys()
8
+ {
9
+ return false;
10
+ }
11
+
12
+ }
@@ -0,0 +1,788 @@
1
+ #include <libpq-fe.h>
2
+ #include <postgres.h>
3
+ #include <mb/pg_wchar.h>
4
+ #include <catalog/pg_type.h>
5
+
6
+ /* Undefine constants Postgres also defines */
7
+ #undef PACKAGE_BUGREPORT
8
+ #undef PACKAGE_NAME
9
+ #undef PACKAGE_STRING
10
+ #undef PACKAGE_TARNAME
11
+ #undef PACKAGE_VERSION
12
+ #include <ruby.h>
13
+ #include <version.h>
14
+ #include <string.h>
15
+ #include <math.h>
16
+
17
+ #define ID_CONST_GET rb_intern("const_get")
18
+ #define ID_PATH rb_intern("path")
19
+ #define ID_NEW rb_intern("new")
20
+ #define ID_ESCAPE rb_intern("escape_sql")
21
+
22
+ #define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
23
+ #define TAINTED_STRING(name, length) rb_tainted_str_new(name, length)
24
+ #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
25
+ #define POSTGRES_CLASS(klass, parent) (rb_define_class_under(mPostgres, klass, parent))
26
+ #define DEBUG(value) data_objects_debug(value)
27
+ #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
28
+
29
+ #ifndef RSTRING_PTR
30
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
31
+ #endif
32
+
33
+ #ifndef RSTRING_LEN
34
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
35
+ #endif
36
+
37
+ #ifndef RARRAY_LEN
38
+ #define RARRAY_LEN(a) RARRAY(a)->len
39
+ #endif
40
+
41
+ #ifdef _WIN32
42
+ #define do_int64 signed __int64
43
+ #else
44
+ #define do_int64 signed long long int
45
+ #endif
46
+
47
+ // To store rb_intern values
48
+ static ID ID_NEW_DATE;
49
+ static ID ID_LOGGER;
50
+ static ID ID_DEBUG;
51
+ static ID ID_LEVEL;
52
+ static ID ID_TO_S;
53
+
54
+ static VALUE mDO;
55
+ static VALUE cDO_Quoting;
56
+ static VALUE cDO_Connection;
57
+ static VALUE cDO_Command;
58
+ static VALUE cDO_Result;
59
+ static VALUE cDO_Reader;
60
+
61
+ static VALUE rb_cDate;
62
+ static VALUE rb_cDateTime;
63
+ static VALUE rb_cRational;
64
+ static VALUE rb_cBigDecimal;
65
+
66
+ static VALUE mPostgres;
67
+ static VALUE cConnection;
68
+ static VALUE cCommand;
69
+ static VALUE cResult;
70
+ static VALUE cReader;
71
+
72
+ static VALUE ePostgresError;
73
+
74
+ static void data_objects_debug(VALUE string, struct timeval* start) {
75
+ struct timeval stop;
76
+ char *message;
77
+
78
+ char *query = RSTRING_PTR(string);
79
+ int length = RSTRING_LEN(string);
80
+ char total_time[32];
81
+ do_int64 duration = 0;
82
+
83
+ VALUE logger = rb_funcall(mPostgres, ID_LOGGER, 0);
84
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
85
+
86
+ if (0 == log_level) {
87
+ gettimeofday(&stop, NULL);
88
+
89
+ duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
90
+ if(stop.tv_usec < start->tv_usec) {
91
+ duration += 1000000;
92
+ }
93
+
94
+ snprintf(total_time, 32, "%.6f", duration / 1000000.0);
95
+ message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
96
+ snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
97
+ rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
98
+ }
99
+ }
100
+
101
+ static char * get_uri_option(VALUE query_hash, char * key) {
102
+ VALUE query_value;
103
+ char * value = NULL;
104
+
105
+ if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
106
+
107
+ query_value = rb_hash_aref(query_hash, RUBY_STRING(key));
108
+
109
+ if (Qnil != query_value) {
110
+ value = StringValuePtr(query_value);
111
+ }
112
+
113
+ return value;
114
+ }
115
+
116
+ /* ====== Time/Date Parsing Helper Functions ====== */
117
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
118
+ do_int64 a, b, c;
119
+ a = *numerator;
120
+ b = *denominator;
121
+ while ( a != 0 ) {
122
+ c = a; a = b % a; b = c;
123
+ }
124
+ *numerator = *numerator / b;
125
+ *denominator = *denominator / b;
126
+ }
127
+
128
+ // Generate the date integer which Date.civil_to_jd returns
129
+ static int jd_from_date(int year, int month, int day) {
130
+ int a, b;
131
+ if ( month <= 2 ) {
132
+ year -= 1;
133
+ month += 12;
134
+ }
135
+ a = year / 100;
136
+ b = 2 - a + (a / 4);
137
+ return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
138
+ }
139
+
140
+ static VALUE parse_date(const char *date) {
141
+ int year, month, day;
142
+ int jd, ajd;
143
+ VALUE rational;
144
+
145
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
146
+
147
+ jd = jd_from_date(year, month, day);
148
+
149
+ // Math from Date.jd_to_ajd
150
+ ajd = jd * 2 - 1;
151
+ rational = rb_funcall(rb_cRational, rb_intern("new!"), 2, INT2NUM(ajd), INT2NUM(2));
152
+
153
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
154
+ }
155
+
156
+ // Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
157
+ static VALUE seconds_to_offset(do_int64 num) {
158
+ do_int64 den = 86400;
159
+ reduce(&num, &den);
160
+ return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
161
+ }
162
+
163
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
164
+ do_int64 seconds = 0;
165
+
166
+ seconds += hour_offset * 3600;
167
+ seconds += minute_offset * 60;
168
+
169
+ return seconds_to_offset(seconds);
170
+ }
171
+
172
+ static VALUE parse_date_time(const char *date) {
173
+ VALUE ajd, offset;
174
+
175
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
176
+ int jd;
177
+ do_int64 num, den;
178
+
179
+ long int gmt_offset;
180
+ int is_dst;
181
+
182
+ time_t rawtime;
183
+ struct tm * timeinfo;
184
+
185
+ int tokens_read, max_tokens;
186
+
187
+ if (0 != strchr(date, '.')) {
188
+ // This is a datetime with sub-second precision
189
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
190
+ max_tokens = 9;
191
+ } else {
192
+ // This is a datetime second precision
193
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
194
+ max_tokens = 8;
195
+ }
196
+
197
+ if (max_tokens == tokens_read) {
198
+ // We read the Date, Time, and Timezone info
199
+ minute_offset *= hour_offset < 0 ? -1 : 1;
200
+ } else if ((max_tokens - 1) == tokens_read) {
201
+ // We read the Date and Time, but no Minute Offset
202
+ minute_offset = 0;
203
+ } else if (tokens_read == 3) {
204
+ return parse_date(date);
205
+ } else if (tokens_read >= (max_tokens - 3)) {
206
+ // We read the Date and Time, default to the current locale's offset
207
+
208
+ // Get localtime
209
+ time(&rawtime);
210
+ timeinfo = localtime(&rawtime);
211
+
212
+ is_dst = timeinfo->tm_isdst * 3600;
213
+
214
+ // Reset to GM Time
215
+ timeinfo = gmtime(&rawtime);
216
+
217
+ gmt_offset = mktime(timeinfo) - rawtime;
218
+
219
+ if ( is_dst > 0 )
220
+ gmt_offset -= is_dst;
221
+
222
+ hour_offset = -(gmt_offset / 3600);
223
+ minute_offset = -(gmt_offset % 3600 / 60);
224
+
225
+ } else {
226
+ // Something went terribly wrong
227
+ rb_raise(ePostgresError, "Couldn't parse date: %s", date);
228
+ }
229
+
230
+ jd = jd_from_date(year, month, day);
231
+
232
+ // Generate ajd with fractional days for the time
233
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
234
+ num = (hour * 1440) + (min * 24);
235
+
236
+ // Modify the numerator so when we apply the timezone everything works out
237
+ num -= (hour_offset * 1440) + (minute_offset * 24);
238
+
239
+ den = (24 * 1440);
240
+ reduce(&num, &den);
241
+
242
+ num = (num * 86400) + (sec * den);
243
+ den = den * 86400;
244
+ reduce(&num, &den);
245
+
246
+ num = (jd * den) + num;
247
+
248
+ num = num * 2;
249
+ num = num - den;
250
+ den = den * 2;
251
+
252
+ reduce(&num, &den);
253
+
254
+ ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
255
+ offset = timezone_to_offset(hour_offset, minute_offset);
256
+
257
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
258
+ }
259
+
260
+ static VALUE parse_time(char *date) {
261
+
262
+ int year, month, day, hour, min, sec, usec;
263
+ char subsec[7];
264
+
265
+ if (0 != strchr(date, '.')) {
266
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
267
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
268
+ usec = atoi(subsec);
269
+ usec *= pow(10, (6 - strlen(subsec)));
270
+ } else {
271
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
272
+ usec = 0;
273
+ }
274
+
275
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
276
+ }
277
+
278
+ /* ===== Typecasting Functions ===== */
279
+
280
+ static VALUE infer_ruby_type(Oid type) {
281
+ char *ruby_type = "String";
282
+ switch(type) {
283
+ case BITOID:
284
+ case VARBITOID:
285
+ case INT2OID:
286
+ case INT4OID:
287
+ case INT8OID: {
288
+ ruby_type = "Integer";
289
+ break;
290
+ }
291
+ case FLOAT4OID:
292
+ case FLOAT8OID: {
293
+ ruby_type = "Float";
294
+ break;
295
+ }
296
+ case NUMERICOID:
297
+ case CASHOID: {
298
+ ruby_type = "BigDecimal";
299
+ break;
300
+ }
301
+ case BOOLOID: {
302
+ ruby_type = "TrueClass";
303
+ break;
304
+ }
305
+ case TIMESTAMPTZOID:
306
+ case TIMESTAMPOID: {
307
+ ruby_type = "DateTime";
308
+ break;
309
+ }
310
+ case DATEOID: {
311
+ ruby_type = "Date";
312
+ break;
313
+ }
314
+ }
315
+ return rb_str_new2(ruby_type);
316
+ }
317
+
318
+ static VALUE typecast(char *value, long length, char *type) {
319
+
320
+ if ( strcmp(type, "Class") == 0) {
321
+ return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value, length));
322
+ } else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
323
+ return rb_cstr2inum(value, 10);
324
+ } else if ( strcmp(type, "Float") == 0 ) {
325
+ return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
326
+ } else if (0 == strcmp("BigDecimal", type) ) {
327
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value, length));
328
+ } else if ( strcmp(type, "TrueClass") == 0 ) {
329
+ return *value == 't' ? Qtrue : Qfalse;
330
+ } else if ( strcmp(type, "Date") == 0 ) {
331
+ return parse_date(value);
332
+ } else if ( strcmp(type, "DateTime") == 0 ) {
333
+ return parse_date_time(value);
334
+ } else if ( strcmp(type, "Time") == 0 ) {
335
+ return parse_time(value);
336
+ } else {
337
+ return TAINTED_STRING(value, length);
338
+ }
339
+
340
+ }
341
+
342
+ /* ====== Public API ======= */
343
+ static VALUE cConnection_dispose(VALUE self) {
344
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
345
+ PQfinish(db);
346
+ return Qtrue;
347
+ }
348
+
349
+ static VALUE cCommand_set_types(VALUE self, VALUE array) {
350
+ rb_iv_set(self, "@field_types", array);
351
+ return array;
352
+ }
353
+
354
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args[]) {
355
+ VALUE query = rb_iv_get(klass, "@text");
356
+ if ( count > 0 ) {
357
+ int i;
358
+ VALUE array = rb_ary_new();
359
+ for ( i = 0; i < count; i++) {
360
+ rb_ary_push(array, (VALUE)args[i]);
361
+ }
362
+ query = rb_funcall(klass, ID_ESCAPE, 1, array);
363
+ }
364
+ return query;
365
+ }
366
+
367
+ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
368
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
369
+
370
+ const char *source = RSTRING_PTR(string);
371
+ int source_len = RSTRING_LEN(string);
372
+
373
+ char *escaped;
374
+ int quoted_length = 0;
375
+ VALUE result;
376
+
377
+ // Allocate space for the escaped version of 'string'
378
+ // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
379
+ escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
380
+
381
+ // Escape 'source' using the current charset in use on the conection 'db'
382
+ quoted_length = PQescapeStringConn(db, escaped + 1, source, source_len, NULL);
383
+
384
+ // Wrap the escaped string in single-quotes, this is DO's convention
385
+ escaped[quoted_length + 1] = escaped[0] = '\'';
386
+
387
+ result = rb_str_new(escaped, quoted_length + 2);
388
+ free(escaped);
389
+ return result;
390
+ }
391
+
392
+ static PGresult* cCommand_execute_async(PGconn *db, VALUE query) {
393
+ int socket_fd;
394
+ int retval;
395
+ fd_set rset;
396
+ PGresult *response;
397
+ struct timeval start;
398
+ char* str = StringValuePtr(query);
399
+
400
+ while ((response = PQgetResult(db)) != NULL) {
401
+ PQclear(response);
402
+ }
403
+
404
+ retval = PQsendQuery(db, str);
405
+
406
+ if (!retval) {
407
+ if(PQstatus(db) != CONNECTION_OK) {
408
+ PQreset(db);
409
+ if (PQstatus(db) == CONNECTION_OK) {
410
+ retval = PQsendQuery(db, str);
411
+ }
412
+ }
413
+
414
+ if(!retval) {
415
+ rb_raise(ePostgresError, PQerrorMessage(db));
416
+ }
417
+ }
418
+
419
+ gettimeofday(&start, NULL);
420
+ socket_fd = PQsocket(db);
421
+
422
+ for(;;) {
423
+ FD_ZERO(&rset);
424
+ FD_SET(socket_fd, &rset);
425
+ retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
426
+ if (retval < 0) {
427
+ rb_sys_fail(0);
428
+ }
429
+
430
+ if (retval == 0) {
431
+ continue;
432
+ }
433
+
434
+ if (PQconsumeInput(db) == 0) {
435
+ rb_raise(ePostgresError, PQerrorMessage(db));
436
+ }
437
+
438
+ if (PQisBusy(db) == 0) {
439
+ break;
440
+ }
441
+ }
442
+
443
+ data_objects_debug(query, &start);
444
+ return PQgetResult(db);
445
+ }
446
+
447
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
448
+ PGresult *result = NULL;
449
+ VALUE r_host, r_user, r_password, r_path, r_port, r_query, r_options;
450
+ char *host = NULL, *user = NULL, *password = NULL, *path;
451
+ char *database = "", *port = "5432";
452
+ char *encoding = NULL;
453
+ char *search_path = NULL;
454
+ char *search_path_query = NULL;
455
+ char *backslash_off = "SET backslash_quote = off";
456
+ char *standard_strings_on = "SET standard_conforming_strings = on";
457
+
458
+ PGconn *db;
459
+
460
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
461
+ if ( Qnil != r_host && "localhost" != StringValuePtr(r_host) ) {
462
+ host = StringValuePtr(r_host);
463
+ }
464
+
465
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
466
+ if (Qnil != r_user) {
467
+ user = StringValuePtr(r_user);
468
+ }
469
+
470
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
471
+ if (Qnil != r_password) {
472
+ password = StringValuePtr(r_password);
473
+ }
474
+
475
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
476
+ path = StringValuePtr(r_path);
477
+ if (Qnil != r_path) {
478
+ database = strtok(path, "/");
479
+ }
480
+
481
+ if (NULL == database || 0 == strlen(database)) {
482
+ rb_raise(ePostgresError, "Database must be specified");
483
+ }
484
+
485
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
486
+ if (Qnil != r_port) {
487
+ r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
488
+ port = StringValuePtr(r_port);
489
+ }
490
+
491
+ // Pull the querystring off the URI
492
+ r_query = rb_funcall(uri, rb_intern("query"), 0);
493
+
494
+ search_path = get_uri_option(r_query, "search_path");
495
+
496
+ db = PQsetdbLogin(
497
+ host,
498
+ port,
499
+ NULL,
500
+ NULL,
501
+ database,
502
+ user,
503
+ password
504
+ );
505
+
506
+ if ( PQstatus(db) == CONNECTION_BAD ) {
507
+ rb_raise(ePostgresError, PQerrorMessage(db));
508
+ }
509
+
510
+ if (search_path != NULL) {
511
+ search_path_query = (char *)calloc(256, sizeof(char));
512
+ snprintf(search_path_query, 256, "set search_path to %s;", search_path);
513
+ r_query = rb_str_new2(search_path_query);
514
+ result = cCommand_execute_async(db, r_query);
515
+
516
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
517
+ free(search_path_query);
518
+ rb_raise(ePostgresError, PQresultErrorMessage(result));
519
+ }
520
+
521
+ free(search_path_query);
522
+ }
523
+
524
+ r_options = rb_str_new2(backslash_off);
525
+ result = cCommand_execute_async(db, r_options);
526
+
527
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
528
+ rb_warn(PQresultErrorMessage(result));
529
+ }
530
+
531
+ r_options = rb_str_new2(standard_strings_on);
532
+ result = cCommand_execute_async(db, r_options);
533
+
534
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
535
+ rb_warn(PQresultErrorMessage(result));
536
+ }
537
+
538
+ encoding = get_uri_option(r_query, "encoding");
539
+ if (!encoding) { encoding = get_uri_option(r_query, "charset"); }
540
+ if (!encoding) { encoding = "utf8"; }
541
+
542
+ #ifdef HAVE_PQSETCLIENTENCODING
543
+ if(PQsetClientEncoding(db, encoding)) {
544
+ rb_raise(ePostgresError, "Couldn't set encoding: %s", encoding);
545
+ }
546
+ #endif
547
+
548
+ rb_iv_set(self, "@uri", uri);
549
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
550
+
551
+ return Qtrue;
552
+ }
553
+
554
+ static VALUE cConnection_character_set(VALUE self) {
555
+ VALUE connection_container = rb_iv_get(self, "@connection");
556
+ PGconn *db;
557
+
558
+ const char *encoding;
559
+
560
+ if (Qnil == connection_container)
561
+ return Qfalse;
562
+
563
+ db = DATA_PTR(connection_container);
564
+
565
+ encoding = pg_encoding_to_char(PQclientEncoding(db));
566
+
567
+ return rb_funcall(RUBY_STRING(encoding), rb_intern("downcase"), 0);
568
+ }
569
+
570
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
571
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
572
+ PGresult *response;
573
+ int status;
574
+
575
+ int affected_rows;
576
+ int insert_id;
577
+
578
+ VALUE query = build_query_from_args(self, argc, argv);
579
+
580
+ response = cCommand_execute_async(db, query);
581
+
582
+ status = PQresultStatus(response);
583
+
584
+ if ( status == PGRES_TUPLES_OK ) {
585
+ insert_id = atoi(PQgetvalue(response, 0, 0));
586
+ affected_rows = 1;
587
+ }
588
+ else if ( status == PGRES_COMMAND_OK ) {
589
+ insert_id = 0;
590
+ affected_rows = atoi(PQcmdTuples(response));
591
+ }
592
+ else {
593
+ char *message = PQresultErrorMessage(response);
594
+ char *sqlstate = PQresultErrorField(response, PG_DIAG_SQLSTATE);
595
+ PQclear(response);
596
+ rb_raise(ePostgresError, "(sql_state=%s) %sQuery: %s\n", sqlstate, message, StringValuePtr(query));
597
+ }
598
+
599
+ PQclear(response);
600
+
601
+ return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(insert_id));
602
+ }
603
+
604
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
605
+ VALUE reader, query;
606
+ VALUE field_names, field_types;
607
+
608
+ int i;
609
+ int field_count;
610
+ int infer_types = 0;
611
+
612
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
613
+ PGresult *response;
614
+
615
+ query = build_query_from_args(self, argc, argv);
616
+
617
+ response = cCommand_execute_async(db, query);
618
+
619
+ if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
620
+ char *message = PQresultErrorMessage(response);
621
+ PQclear(response);
622
+ rb_raise(ePostgresError, "%sQuery: %s\n", message, StringValuePtr(query));
623
+ }
624
+
625
+ field_count = PQnfields(response);
626
+
627
+ reader = rb_funcall(cReader, ID_NEW, 0);
628
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
629
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
630
+ rb_iv_set(reader, "@row_count", INT2NUM(PQntuples(response)));
631
+
632
+ field_names = rb_ary_new();
633
+ field_types = rb_iv_get(self, "@field_types");
634
+
635
+ if ( field_types == Qnil || RARRAY(field_types)->len == 0 ) {
636
+ field_types = rb_ary_new();
637
+ infer_types = 1;
638
+ }
639
+
640
+ for ( i = 0; i < field_count; i++ ) {
641
+ rb_ary_push(field_names, rb_str_new2(PQfname(response, i)));
642
+ if ( infer_types == 1 ) {
643
+ rb_ary_push(field_types, infer_ruby_type(PQftype(response, i)));
644
+ }
645
+ }
646
+
647
+ rb_iv_set(reader, "@position", INT2NUM(0));
648
+ rb_iv_set(reader, "@fields", field_names);
649
+ rb_iv_set(reader, "@field_types", field_types);
650
+
651
+ return reader;
652
+ }
653
+
654
+ static VALUE cReader_close(VALUE self) {
655
+ VALUE reader_container = rb_iv_get(self, "@reader");
656
+
657
+ PGresult *reader;
658
+
659
+ if (Qnil == reader_container)
660
+ return Qfalse;
661
+
662
+ reader = DATA_PTR(reader_container);
663
+
664
+ if (NULL == reader)
665
+ return Qfalse;
666
+
667
+ PQclear(reader);
668
+ rb_iv_set(self, "@reader", Qnil);
669
+ return Qtrue;
670
+ }
671
+
672
+ static VALUE cReader_next(VALUE self) {
673
+ PGresult *reader = DATA_PTR(rb_iv_get(self, "@reader"));
674
+
675
+ int field_count;
676
+ int row_count;
677
+ int i;
678
+ int position;
679
+
680
+ char *type = "";
681
+
682
+ VALUE array = rb_ary_new();
683
+ VALUE field_types, ruby_type;
684
+ VALUE value;
685
+
686
+ row_count = NUM2INT(rb_iv_get(self, "@row_count"));
687
+ field_count = NUM2INT(rb_iv_get(self, "@field_count"));
688
+ field_types = rb_iv_get(self, "@field_types");
689
+ position = NUM2INT(rb_iv_get(self, "@position"));
690
+
691
+ if ( position > (row_count-1) ) {
692
+ return Qnil;
693
+ }
694
+
695
+ for ( i = 0; i < field_count; i++ ) {
696
+ ruby_type = RARRAY(field_types)->ptr[i];
697
+
698
+ if ( TYPE(ruby_type) == T_STRING ) {
699
+ type = StringValuePtr(ruby_type);
700
+ }
701
+ else {
702
+ type = rb_class2name(ruby_type);
703
+ }
704
+
705
+ // Always return nil if the value returned from Postgres is null
706
+ if (!PQgetisnull(reader, position, i)) {
707
+ value = typecast(PQgetvalue(reader, position, i), PQgetlength(reader, position, i), type);
708
+ } else {
709
+ value = Qnil;
710
+ }
711
+
712
+ rb_ary_push(array, value);
713
+ }
714
+
715
+ rb_iv_set(self, "@values", array);
716
+ rb_iv_set(self, "@position", INT2NUM(position+1));
717
+
718
+ return Qtrue;
719
+ }
720
+
721
+ static VALUE cReader_values(VALUE self) {
722
+
723
+ int position = rb_iv_get(self, "@position");
724
+ int row_count = NUM2INT(rb_iv_get(self, "@row_count"));
725
+
726
+ if ( position == Qnil || NUM2INT(position) > row_count ) {
727
+ rb_raise(ePostgresError, "Reader not initialized");
728
+ }
729
+ else {
730
+ return rb_iv_get(self, "@values");
731
+ }
732
+ }
733
+
734
+ static VALUE cReader_fields(VALUE self) {
735
+ return rb_iv_get(self, "@fields");
736
+ }
737
+
738
+ void Init_do_postgres_ext() {
739
+ rb_require("date");
740
+ rb_require("bigdecimal");
741
+
742
+ // Get references classes needed for Date/Time parsing
743
+ rb_cDate = CONST_GET(rb_mKernel, "Date");
744
+ rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
745
+ rb_cTime = CONST_GET(rb_mKernel, "Time");
746
+ rb_cRational = CONST_GET(rb_mKernel, "Rational");
747
+ rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
748
+
749
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
750
+
751
+ ID_NEW_DATE = RUBY_VERSION_CODE < 186 ? rb_intern("new0") : rb_intern("new!");
752
+ ID_LOGGER = rb_intern("logger");
753
+ ID_DEBUG = rb_intern("debug");
754
+ ID_LEVEL = rb_intern("level");
755
+ ID_TO_S = rb_intern("to_s");
756
+
757
+ // Get references to the DataObjects module and its classes
758
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
759
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
760
+ cDO_Connection = CONST_GET(mDO, "Connection");
761
+ cDO_Command = CONST_GET(mDO, "Command");
762
+ cDO_Result = CONST_GET(mDO, "Result");
763
+ cDO_Reader = CONST_GET(mDO, "Reader");
764
+
765
+ mPostgres = rb_define_module_under(mDO, "Postgres");
766
+ ePostgresError = rb_define_class("PostgresError", rb_eStandardError);
767
+
768
+ cConnection = POSTGRES_CLASS("Connection", cDO_Connection);
769
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
770
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
771
+ rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
772
+
773
+ cCommand = POSTGRES_CLASS("Command", cDO_Command);
774
+ rb_include_module(cCommand, cDO_Quoting);
775
+ rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
776
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
777
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
778
+ rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
779
+
780
+ cResult = POSTGRES_CLASS("Result", cDO_Result);
781
+
782
+ cReader = POSTGRES_CLASS("Reader", cDO_Reader);
783
+ rb_define_method(cReader, "close", cReader_close, 0);
784
+ rb_define_method(cReader, "next!", cReader_next, 0);
785
+ rb_define_method(cReader, "values", cReader_values, 0);
786
+ rb_define_method(cReader, "fields", cReader_fields, 0);
787
+
788
+ }