do_postgres 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 +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
+ }