do_postgres 0.2.4 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,4 +1,4 @@
1
- do_sqlite3
2
- =================
1
+ do_postgres
2
+ ===========
3
3
 
4
- A plugin for the Merb framework that provides ....
4
+ A PostgreSQL driver for DataObjects
data/Rakefile CHANGED
@@ -1,36 +1,57 @@
1
1
  require 'rubygems'
2
+ require 'rake/clean'
2
3
  require 'rake/gempackagetask'
4
+ require 'spec/rake/spectask'
5
+ require 'pathname'
6
+ require Pathname(__FILE__).dirname.expand_path.parent + 'tasks/ext_helper'
3
7
 
4
- PLUGIN = "do_postgres"
5
- NAME = "do_postgres"
6
- VERSION = "0.2.4"
7
- AUTHOR = "Yehuda Katz"
8
- EMAIL = "wycats@gmail.com"
9
- HOMEPAGE = "http://dataobjects.devjavu.com"
10
- SUMMARY = "A DataObject.rb driver for postgres"
8
+ # House-keeping
9
+ CLEAN.include '**/*.o', '**/*.so', '**/*.bundle', '**/*.a',
10
+ '**/*.log', '{ext,lib}/*.{bundle,so,obj,pdb,lib,def,exp}',
11
+ 'ext/Makefile'
12
+
13
+ JRUBY = (RUBY_PLATFORM =~ /java/) rescue nil
14
+ WINDOWS = (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) rescue nil
15
+ # don't use SUDO with JRuby, for the moment, although this behaviour
16
+ # is not entirely correct.
17
+ SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
11
18
 
12
19
  spec = Gem::Specification.new do |s|
13
- s.name = NAME
14
- s.version = VERSION
15
- s.platform = Gem::Platform::RUBY
16
- s.has_rdoc = true
17
- s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
18
- s.summary = SUMMARY
19
- s.description = s.summary
20
- s.author = AUTHOR
21
- s.email = EMAIL
22
- s.homepage = HOMEPAGE
23
- s.add_dependency('data_objects', ["<=0.2.0"])
24
- s.require_path = 'lib'
25
- s.autorequire = PLUGIN
26
- s.extensions = ["ext/extconf.rb"]
27
- s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs,ext}/**/*").reject {|x| x =~ /\.(o|bundle)$/ }
20
+ s.name = 'do_postgres'
21
+ s.version = '0.9.2'
22
+ s.platform = Gem::Platform::RUBY
23
+ s.has_rdoc = true
24
+ s.extra_rdoc_files = %w[ README LICENSE TODO ]
25
+ s.summary = 'A DataObject.rb driver for PostgreSQL'
26
+ s.description = s.summary
27
+ s.author = 'Yehuda Katz'
28
+ s.email = 'wycats@gmail.com'
29
+ s.homepage = 'http://rubyforge.org/projects/dorb'
30
+ s.rubyforge_project = 'dorb'
31
+ s.require_path = 'lib'
32
+ s.extensions = %w[ ext/extconf.rb ]
33
+ s.files = FileList[ '{ext,lib,spec}/**/*.{c,h,rb}', 'Rakefile', *s.extra_rdoc_files ]
34
+ s.add_dependency('data_objects', "=#{s.version}")
28
35
  end
29
36
 
30
37
  Rake::GemPackageTask.new(spec) do |pkg|
31
38
  pkg.gem_spec = spec
32
39
  end
33
40
 
34
- task :install => [:package] do
35
- sh %{sudo gem install pkg/#{NAME}-#{VERSION}}, :verbose => false
36
- end
41
+ # Use of ext_helper to properly setup compile tasks and native gem generation
42
+ setup_extension "#{spec.name}_ext", spec
43
+
44
+ task :install => [ :package ] do
45
+ sh %{#{SUDO} gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources}, :verbose => false
46
+ end
47
+
48
+ desc "Uninstall #{spec.name} #{spec.version} (default ruby)"
49
+ task :uninstall => [ :clobber ] do
50
+ sh "#{SUDO} gem uninstall #{spec.name} -v#{spec.version} -I -x", :verbose => false
51
+ end
52
+
53
+ desc 'Run specifications'
54
+ Spec::Rake::SpecTask.new(:spec => [ :compile ]) do |t|
55
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
56
+ t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
57
+ end
data/TODO CHANGED
@@ -0,0 +1,4 @@
1
+ TODO
2
+ ====
3
+
4
+ * Add JDBC-based version of this driver.
@@ -0,0 +1,614 @@
1
+ #include <ruby.h>
2
+ #include <version.h>
3
+ #include <string.h>
4
+ #include <math.h>
5
+ #include <time.h>
6
+ #include <locale.h>
7
+ #include <libpq-fe.h>
8
+ #include "type-oids.h"
9
+
10
+ #define ID_CONST_GET rb_intern("const_get")
11
+ #define ID_PATH rb_intern("path")
12
+ #define ID_NEW rb_intern("new")
13
+ #define ID_ESCAPE rb_intern("escape_sql")
14
+
15
+ #define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
16
+ #define TAINTED_STRING(name) rb_tainted_str_new2(name)
17
+ #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
18
+ #define POSTGRES_CLASS(klass, parent) (rb_define_class_under(mPostgres, klass, parent))
19
+ #define DEBUG(value) data_objects_debug(value)
20
+
21
+ #ifdef _WIN32
22
+ #define do_int64 signed __int64
23
+ #else
24
+ #define do_int64 signed long long int
25
+ #endif
26
+
27
+ // To store rb_intern values
28
+ static ID ID_NEW_DATE;
29
+ static ID ID_LOGGER;
30
+ static ID ID_DEBUG;
31
+ static ID ID_LEVEL;
32
+
33
+ static VALUE mDO;
34
+ static VALUE cDO_Quoting;
35
+ static VALUE cDO_Connection;
36
+ static VALUE cDO_Command;
37
+ static VALUE cDO_Result;
38
+ static VALUE cDO_Reader;
39
+
40
+ static VALUE rb_cDate;
41
+ static VALUE rb_cDateTime;
42
+ static VALUE rb_cRational;
43
+ static VALUE rb_cBigDecimal;
44
+
45
+ static VALUE mPostgres;
46
+ static VALUE cConnection;
47
+ static VALUE cCommand;
48
+ static VALUE cResult;
49
+ static VALUE cReader;
50
+
51
+ static VALUE ePostgresError;
52
+
53
+ static void data_objects_debug(VALUE string) {
54
+ VALUE logger = rb_funcall(mPostgres, ID_LOGGER, 0);
55
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
56
+
57
+ if (0 == log_level) {
58
+ rb_funcall(logger, ID_DEBUG, 1, string);
59
+ }
60
+ }
61
+
62
+ /* ====== Time/Date Parsing Helper Functions ====== */
63
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
64
+ do_int64 a, b, c;
65
+ a = *numerator;
66
+ b = *denominator;
67
+ while ( a != 0 ) {
68
+ c = a; a = b % a; b = c;
69
+ }
70
+ *numerator = *numerator / b;
71
+ *denominator = *denominator / b;
72
+ }
73
+
74
+ // Generate the date integer which Date.civil_to_jd returns
75
+ static int jd_from_date(int year, int month, int day) {
76
+ int a, b;
77
+ if ( month <= 2 ) {
78
+ year -= 1;
79
+ month += 12;
80
+ }
81
+ a = year / 100;
82
+ b = 2 - a + (a / 4);
83
+ return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
84
+ }
85
+
86
+ static VALUE parse_date(const char *date) {
87
+ int year, month, day;
88
+ int jd, ajd;
89
+ VALUE rational;
90
+
91
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
92
+
93
+ jd = jd_from_date(year, month, day);
94
+
95
+ // Math from Date.jd_to_ajd
96
+ ajd = jd * 2 - 1;
97
+ rational = rb_funcall(rb_cRational, rb_intern("new!"), 2, INT2NUM(ajd), INT2NUM(2));
98
+
99
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
100
+ }
101
+
102
+ // Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
103
+ static VALUE seconds_to_offset(do_int64 num) {
104
+ do_int64 den = 86400;
105
+ reduce(&num, &den);
106
+ return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
107
+ }
108
+
109
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
110
+ do_int64 seconds = 0;
111
+
112
+ seconds += hour_offset * 3600;
113
+ seconds += minute_offset * 60;
114
+
115
+ return seconds_to_offset(seconds);
116
+ }
117
+
118
+ static VALUE parse_date_time(const char *date) {
119
+ VALUE ajd, offset;
120
+
121
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
122
+ int jd;
123
+ do_int64 num, den;
124
+
125
+ long int gmt_offset;
126
+ int is_dst;
127
+
128
+ time_t rawtime;
129
+ struct tm * timeinfo;
130
+
131
+ int tokens_read, max_tokens;
132
+
133
+ if (0 != strchr(date, '.')) {
134
+ // This is a datetime with sub-second precision
135
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
136
+ max_tokens = 9;
137
+ } else {
138
+ // This is a datetime second precision
139
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
140
+ max_tokens = 8;
141
+ }
142
+
143
+ if (max_tokens == tokens_read) {
144
+ // We read the Date, Time, and Timezone info
145
+ minute_offset *= hour_offset < 0 ? -1 : 1;
146
+ } else if ((max_tokens - 1) == tokens_read) {
147
+ // We read the Date and Time, but no Minute Offset
148
+ minute_offset = 0;
149
+ } else if (tokens_read == 3) {
150
+ return parse_date(date);
151
+ } else if (tokens_read >= (max_tokens - 3)) {
152
+ // We read the Date and Time, default to the current locale's offset
153
+
154
+ // Get localtime
155
+ time(&rawtime);
156
+ timeinfo = localtime(&rawtime);
157
+
158
+ is_dst = timeinfo->tm_isdst * 3600;
159
+
160
+ // Reset to GM Time
161
+ timeinfo = gmtime(&rawtime);
162
+
163
+ gmt_offset = mktime(timeinfo) - rawtime;
164
+
165
+ if ( is_dst > 0 )
166
+ gmt_offset -= is_dst;
167
+
168
+ hour_offset = -(gmt_offset / 3600);
169
+ minute_offset = -(gmt_offset % 3600 / 60);
170
+
171
+ } else {
172
+ // Something went terribly wrong
173
+ rb_raise(ePostgresError, "Couldn't parse date: %s", date);
174
+ }
175
+
176
+ jd = jd_from_date(year, month, day);
177
+
178
+ // Generate ajd with fractional days for the time
179
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
180
+ num = (hour * 1440) + (min * 24);
181
+
182
+ // Modify the numerator so when we apply the timezone everything works out
183
+ num -= (hour_offset * 1440) + (minute_offset * 24);
184
+
185
+ den = (24 * 1440);
186
+ reduce(&num, &den);
187
+
188
+ num = (num * 86400) + (sec * den);
189
+ den = den * 86400;
190
+ reduce(&num, &den);
191
+
192
+ num = (jd * den) + num;
193
+
194
+ num = num * 2;
195
+ num = num - den;
196
+ den = den * 2;
197
+
198
+ reduce(&num, &den);
199
+
200
+ ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
201
+ offset = timezone_to_offset(hour_offset, minute_offset);
202
+
203
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
204
+ }
205
+
206
+ static VALUE parse_time(char *date) {
207
+
208
+ int year, month, day, hour, min, sec, usec;
209
+ char subsec[7];
210
+
211
+ if (0 != strchr(date, '.')) {
212
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
213
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
214
+ usec = atoi(subsec);
215
+ usec *= pow(10, (6 - strlen(subsec)));
216
+ } else {
217
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
218
+ usec = 0;
219
+ }
220
+
221
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
222
+ }
223
+
224
+ /* ===== Typecasting Functions ===== */
225
+
226
+ static VALUE infer_ruby_type(Oid type) {
227
+ char *ruby_type = "String";
228
+ switch(type) {
229
+ case INT2OID:
230
+ case INT4OID:
231
+ case INT8OID: {
232
+ ruby_type = "Fixnum";
233
+ break;
234
+ }
235
+ case FLOAT4OID:
236
+ case FLOAT8OID: {
237
+ ruby_type = "Float";
238
+ break;
239
+ }
240
+ case BOOLOID: {
241
+ ruby_type = "TrueClass";
242
+ break;
243
+ }
244
+ case TIMESTAMPOID: {
245
+ ruby_type = "DateTime";
246
+ break;
247
+ }
248
+ // case TIMESTAMPTZOID
249
+ // case TIMETZOID
250
+ case TIMEOID: {
251
+ ruby_type = "Time";
252
+ break;
253
+ }
254
+ case DATEOID: {
255
+ ruby_type = "Date";
256
+ break;
257
+ }
258
+ }
259
+ return rb_str_new2(ruby_type);
260
+ }
261
+
262
+ static VALUE typecast(char *value, char *type) {
263
+
264
+ if ( strcmp(type, "Class") == 0) {
265
+ return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
266
+ } else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
267
+ return rb_cstr2inum(value, 10);
268
+ } else if ( strcmp(type, "Float") == 0 ) {
269
+ return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
270
+ } else if (0 == strcmp("BigDecimal", type) ) {
271
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value));
272
+ } else if ( strcmp(type, "TrueClass") == 0 ) {
273
+ return *value == 't' ? Qtrue : Qfalse;
274
+ } else if ( strcmp(type, "Date") == 0 ) {
275
+ return parse_date(value);
276
+ } else if ( strcmp(type, "DateTime") == 0 ) {
277
+ return parse_date_time(value);
278
+ } else if ( strcmp(type, "Time") == 0 ) {
279
+ return parse_time(value);
280
+ } else {
281
+ return TAINTED_STRING(value);
282
+ }
283
+
284
+ }
285
+
286
+ /* ====== Public API ======= */
287
+
288
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
289
+ VALUE r_host, r_user, r_password, r_path, r_port;
290
+ char *host = NULL, *user = NULL, *password = NULL, *path;
291
+ char *database = "", *port = "5432";
292
+
293
+ PGconn *db;
294
+
295
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
296
+ if ( Qnil != r_host && "localhost" != StringValuePtr(r_host) ) {
297
+ host = StringValuePtr(r_host);
298
+ }
299
+
300
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
301
+ if (Qnil != r_user) {
302
+ user = StringValuePtr(r_user);
303
+ }
304
+
305
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
306
+ if (Qnil != r_password) {
307
+ password = StringValuePtr(r_password);
308
+ }
309
+
310
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
311
+ path = StringValuePtr(r_path);
312
+ if (Qnil != r_path) {
313
+ database = strtok(path, "/");
314
+ }
315
+
316
+ if (NULL == database || 0 == strlen(database)) {
317
+ rb_raise(ePostgresError, "Database must be specified");
318
+ }
319
+
320
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
321
+ if (Qnil != r_port) {
322
+ r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
323
+ port = StringValuePtr(r_port);
324
+ }
325
+
326
+ db = PQsetdbLogin(
327
+ host,
328
+ port,
329
+ NULL,
330
+ NULL,
331
+ database,
332
+ user,
333
+ password
334
+ );
335
+
336
+ if ( PQstatus(db) == CONNECTION_BAD ) {
337
+ rb_raise(ePostgresError, PQerrorMessage(db));
338
+ }
339
+
340
+ rb_iv_set(self, "@uri", uri);
341
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
342
+
343
+ return Qtrue;
344
+ }
345
+
346
+ static VALUE cConnection_dispose(VALUE self) {
347
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
348
+ PQfinish(db);
349
+ return Qtrue;
350
+ }
351
+
352
+ static VALUE cCommand_set_types(VALUE self, VALUE array) {
353
+ rb_iv_set(self, "@field_types", array);
354
+ return array;
355
+ }
356
+
357
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args[]) {
358
+ VALUE query = rb_iv_get(klass, "@text");
359
+ if ( count > 0 ) {
360
+ int i;
361
+ VALUE array = rb_ary_new();
362
+ for ( i = 0; i < count; i++) {
363
+ rb_ary_push(array, (VALUE)args[i]);
364
+ }
365
+ query = rb_funcall(klass, ID_ESCAPE, 1, array);
366
+ }
367
+ return query;
368
+ }
369
+
370
+ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
371
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
372
+
373
+ size_t length;
374
+ const char *source = StringValuePtr(string);
375
+ char *escaped;
376
+ int quoted_length = 0;
377
+ VALUE result;
378
+
379
+ length = strlen(source);
380
+
381
+ // Allocate space for the escaped version of 'string'
382
+ // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
383
+ escaped = (char *)calloc(strlen(source) * 2 + 3, sizeof(char));
384
+
385
+ // Escape 'source' using the current charset in use on the conection 'db'
386
+ quoted_length = PQescapeStringConn(db, escaped + 1, source, length, NULL);
387
+
388
+ // Wrap the escaped string in single-quotes, this is DO's convention
389
+ escaped[quoted_length + 1] = escaped[0] = '\'';
390
+
391
+ result = rb_str_new(escaped, quoted_length + 2);
392
+ free(escaped);
393
+ return result;
394
+ }
395
+
396
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
397
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
398
+ PGresult *response;
399
+ int status;
400
+
401
+ int affected_rows;
402
+ int insert_id;
403
+
404
+ VALUE query = build_query_from_args(self, argc, argv);
405
+ data_objects_debug(query);
406
+
407
+ response = PQexec(db, StringValuePtr(query));
408
+
409
+ status = PQresultStatus(response);
410
+
411
+ if ( status == PGRES_TUPLES_OK ) {
412
+ insert_id = atoi(PQgetvalue(response, 0, 0));
413
+ affected_rows = 1;
414
+ }
415
+ else if ( status == PGRES_COMMAND_OK ) {
416
+ insert_id = 0;
417
+ affected_rows = atoi(PQcmdTuples(response));
418
+ }
419
+ else {
420
+ char *message = PQresultErrorMessage(response);
421
+ PQclear(response);
422
+ rb_raise(ePostgresError, message);
423
+ }
424
+
425
+ PQclear(response);
426
+
427
+ return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(insert_id));
428
+ }
429
+
430
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
431
+ VALUE reader, query;
432
+ VALUE field_names, field_types;
433
+
434
+ int i;
435
+ int field_count;
436
+ int infer_types = 0;
437
+
438
+ PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
439
+ PGresult *response;
440
+
441
+ query = build_query_from_args(self, argc, argv);
442
+ data_objects_debug(query);
443
+
444
+ response = PQexec(db, StringValuePtr(query));
445
+
446
+ if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
447
+ char *message = PQresultErrorMessage(response);
448
+ PQclear(response);
449
+ rb_raise(ePostgresError, message);
450
+ }
451
+
452
+ field_count = PQnfields(response);
453
+
454
+ reader = rb_funcall(cReader, ID_NEW, 0);
455
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
456
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
457
+ rb_iv_set(reader, "@row_count", INT2NUM(PQntuples(response)));
458
+
459
+ field_names = rb_ary_new();
460
+ field_types = rb_iv_get(self, "@field_types");
461
+
462
+ if ( field_types == Qnil || RARRAY(field_types)->len == 0 ) {
463
+ field_types = rb_ary_new();
464
+ infer_types = 1;
465
+ }
466
+
467
+ for ( i = 0; i < field_count; i++ ) {
468
+ rb_ary_push(field_names, rb_str_new2(PQfname(response, i)));
469
+ if ( infer_types == 1 ) {
470
+ rb_ary_push(field_types, infer_ruby_type(PQftype(response, i)));
471
+ }
472
+ }
473
+
474
+ rb_iv_set(reader, "@position", INT2NUM(0));
475
+ rb_iv_set(reader, "@fields", field_names);
476
+ rb_iv_set(reader, "@field_types", field_types);
477
+
478
+ return reader;
479
+ }
480
+
481
+ static VALUE cReader_close(VALUE self) {
482
+ VALUE reader_container = rb_iv_get(self, "@reader");
483
+
484
+ PGresult *reader;
485
+
486
+ if (Qnil == reader_container)
487
+ return Qfalse;
488
+
489
+ reader = DATA_PTR(reader_container);
490
+
491
+ if (NULL == reader)
492
+ return Qfalse;
493
+
494
+ PQclear(reader);
495
+ rb_iv_set(self, "@reader", Qnil);
496
+ return Qtrue;
497
+ }
498
+
499
+ static VALUE cReader_next(VALUE self) {
500
+ PGresult *reader = DATA_PTR(rb_iv_get(self, "@reader"));
501
+
502
+ int field_count;
503
+ int row_count;
504
+ int i;
505
+ int position;
506
+
507
+ char *type = "";
508
+
509
+ VALUE array = rb_ary_new();
510
+ VALUE field_types, ruby_type;
511
+ VALUE value;
512
+
513
+ row_count = NUM2INT(rb_iv_get(self, "@row_count"));
514
+ field_count = NUM2INT(rb_iv_get(self, "@field_count"));
515
+ field_types = rb_iv_get(self, "@field_types");
516
+ position = NUM2INT(rb_iv_get(self, "@position"));
517
+
518
+ if ( position > (row_count-1) ) {
519
+ return Qnil;
520
+ }
521
+
522
+ for ( i = 0; i < field_count; i++ ) {
523
+ ruby_type = RARRAY(field_types)->ptr[i];
524
+
525
+ if ( TYPE(ruby_type) == T_STRING ) {
526
+ type = RSTRING(ruby_type)->ptr;
527
+ }
528
+ else {
529
+ type = rb_class2name(ruby_type);
530
+ }
531
+
532
+ // Always return nil if the value returned from Postgres is null
533
+ if (!PQgetisnull(reader, position, i)) {
534
+ value = typecast(PQgetvalue(reader, position, i), type);
535
+ } else {
536
+ value = Qnil;
537
+ }
538
+
539
+ rb_ary_push(array, value);
540
+ }
541
+
542
+ rb_iv_set(self, "@values", array);
543
+ rb_iv_set(self, "@position", INT2NUM(position+1));
544
+
545
+ return Qtrue;
546
+ }
547
+
548
+ static VALUE cReader_values(VALUE self) {
549
+
550
+ int position = rb_iv_get(self, "@position");
551
+ int row_count = NUM2INT(rb_iv_get(self, "@row_count"));
552
+
553
+ if ( position == Qnil || NUM2INT(position) > row_count ) {
554
+ rb_raise(ePostgresError, "Reader not initialized");
555
+ }
556
+ else {
557
+ return rb_iv_get(self, "@values");
558
+ }
559
+ }
560
+
561
+ static VALUE cReader_fields(VALUE self) {
562
+ return rb_iv_get(self, "@fields");
563
+ }
564
+
565
+ void Init_do_postgres_ext() {
566
+ rb_require("rubygems");
567
+ rb_require("date");
568
+
569
+ // Get references classes needed for Date/Time parsing
570
+ rb_cDate = CONST_GET(rb_mKernel, "Date");
571
+ rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
572
+ rb_cTime = CONST_GET(rb_mKernel, "Time");
573
+ rb_cRational = CONST_GET(rb_mKernel, "Rational");
574
+ rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
575
+
576
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
577
+
578
+ ID_NEW_DATE = RUBY_VERSION_CODE < 186 ? rb_intern("new0") : rb_intern("new!");
579
+ ID_LOGGER = rb_intern("logger");
580
+ ID_DEBUG = rb_intern("debug");
581
+ ID_LEVEL = rb_intern("level");
582
+
583
+ // Get references to the DataObjects module and its classes
584
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
585
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
586
+ cDO_Connection = CONST_GET(mDO, "Connection");
587
+ cDO_Command = CONST_GET(mDO, "Command");
588
+ cDO_Result = CONST_GET(mDO, "Result");
589
+ cDO_Reader = CONST_GET(mDO, "Reader");
590
+
591
+ mPostgres = rb_define_module_under(mDO, "Postgres");
592
+ ePostgresError = rb_define_class("PostgresError", rb_eStandardError);
593
+
594
+ cConnection = POSTGRES_CLASS("Connection", cDO_Connection);
595
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
596
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
597
+
598
+ cCommand = POSTGRES_CLASS("Command", cDO_Command);
599
+ rb_include_module(cCommand, cDO_Quoting);
600
+ rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
601
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
602
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
603
+ rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
604
+
605
+ cResult = POSTGRES_CLASS("Result", cDO_Result);
606
+
607
+ cReader = POSTGRES_CLASS("Reader", cDO_Reader);
608
+ rb_define_method(cReader, "close", cReader_close, 0);
609
+ rb_define_method(cReader, "next!", cReader_next, 0);
610
+ rb_define_method(cReader, "values", cReader_values, 0);
611
+ rb_define_method(cReader, "fields", cReader_fields, 0);
612
+
613
+ }
614
+