do_postgres 0.10.0-java → 0.10.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/ChangeLog.markdown +22 -0
  2. data/LICENSE +1 -1
  3. data/README.markdown +106 -3
  4. data/Rakefile +56 -9
  5. data/ext/do_postgres/compat.h +55 -0
  6. data/ext/do_postgres/do_postgres.c +1060 -0
  7. data/ext/do_postgres/error.h +483 -0
  8. data/ext/do_postgres/extconf.rb +43 -0
  9. data/ext/do_postgres/pg_config.h +689 -0
  10. data/lib/do_postgres.rb +12 -16
  11. data/lib/do_postgres/do_postgres.jar +0 -0
  12. data/lib/do_postgres/version.rb +1 -1
  13. data/spec/command_spec.rb +2 -2
  14. data/spec/connection_spec.rb +22 -7
  15. data/spec/encoding_spec.rb +2 -1
  16. data/spec/reader_spec.rb +1 -1
  17. data/spec/result_spec.rb +12 -13
  18. data/spec/spec_helper.rb +27 -36
  19. data/spec/typecast/array_spec.rb +1 -1
  20. data/spec/typecast/bigdecimal_spec.rb +2 -2
  21. data/spec/typecast/boolean_spec.rb +2 -2
  22. data/spec/typecast/byte_array_spec.rb +1 -1
  23. data/spec/typecast/class_spec.rb +1 -1
  24. data/spec/typecast/date_spec.rb +2 -2
  25. data/spec/typecast/datetime_spec.rb +2 -2
  26. data/spec/typecast/float_spec.rb +2 -2
  27. data/spec/typecast/integer_spec.rb +1 -1
  28. data/spec/typecast/nil_spec.rb +3 -3
  29. data/spec/typecast/other_spec.rb +8 -0
  30. data/spec/typecast/range_spec.rb +1 -1
  31. data/spec/typecast/string_spec.rb +1 -1
  32. data/spec/typecast/time_spec.rb +1 -1
  33. data/tasks/compile.rake +81 -0
  34. data/tasks/release.rake +12 -71
  35. data/tasks/retrieve.rake +5 -9
  36. data/tasks/spec.rake +19 -15
  37. metadata +120 -107
  38. data/HISTORY.markdown +0 -12
  39. data/Manifest.txt +0 -34
  40. data/lib/do_postgres_ext.jar +0 -0
  41. data/spec/lib/rspec_immediate_feedback_formatter.rb +0 -3
  42. data/tasks/gem.rake +0 -8
  43. data/tasks/install.rake +0 -15
  44. data/tasks/native.rake +0 -35
@@ -0,0 +1,22 @@
1
+ ## 0.10.1 (unreleased, in git)
2
+
3
+ * Support for Ruby 1.8 and 1.9 on Windows.
4
+ * Switch to Jeweler for Gem building tasks (this change may be temporary).
5
+ * Switch to using Bacon for running specs: This should make specs friendlier to
6
+ new Ruby implementations that are not yet 100% MRI-compatible, and in turn,
7
+ prepared the road for our own IronRuby and MacRuby support.
8
+ * Switch to the newly added rake-compiler `JavaExtensionTask` for compiling
9
+ JRuby extensions, instead of our (broken) home-grown solution.
10
+
11
+ ## 0.9.12 2009-05-17
12
+ * Improvements
13
+ * Windows support
14
+
15
+ ## 0.9.11 2009-01-19
16
+ * Improvements
17
+ * Ruby 1.9 support
18
+ * Fixes
19
+ * Fix build issue on certain platforms introduces with 0.9.10
20
+
21
+ ## 0.9.9 2008-11-27
22
+ * No changes since 0.9.8
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007, 2008, 2009 Yehuda Katz, Dirkjan Bussink
1
+ Copyright (c) 2007 - 2010 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
data/README.markdown CHANGED
@@ -1,4 +1,107 @@
1
- do_postgres
2
- ===========
1
+ # do_postgres
3
2
 
4
- A PostgreSQL driver for DataObjects
3
+ * <http://dataobjects.info>
4
+
5
+ ## Description
6
+
7
+ A PostgreSQL driver for DataObjects.
8
+
9
+ ## Features/Problems
10
+
11
+ This driver implements the DataObjects API for the PostgreSQL relational database.
12
+
13
+ ## Synopsis
14
+
15
+ An example of usage:
16
+
17
+ # default user (postgres, postgres), default port (5432)
18
+ DataObjects::Connection.new("postgres://host/database")
19
+ # specified user, specified port
20
+ DataObjects::Connection.new("postgres://user:pass@host:8888/database")
21
+
22
+ @connection = DataObjects::Connection.new("postgres://localhost/employees")
23
+ @reader = @connection.create_command('SELECT * FROM users').execute_reader
24
+ @reader.next!
25
+
26
+ In the future, the `Connection` constructor will be able to be passed either a
27
+ DataObjects-style URL or JDBC style URL, when using do\_postgres on JRuby.
28
+ However, this feature is not currently working reliably and is a known issue.
29
+
30
+ ## Requirements
31
+
32
+ This driver is provided for the following platforms:
33
+ * Ruby MRI (1.8.6/7), 1.9: tested on Linux, Mac OS X and Windows platforms.
34
+ * JRuby 1.3.1 + (1.4+ recommended).
35
+ * Rubinius (experimental).
36
+
37
+ Additionally you should have the following prerequisites:
38
+ * `data_objects` gem
39
+ * `do_jdbc` gem (shared library), if running on JRuby.
40
+
41
+ ## Install
42
+
43
+ To install the gem:
44
+
45
+ gem install do_postgres
46
+
47
+ To compile and install from source:
48
+
49
+ * Install rake-compiler: `gem install rake-compiler`.
50
+
51
+ * For MRI/Rubinius extensions:
52
+ * Install the `gcc` compiler. On OS X, you should install XCode tools. On
53
+ Ubuntu, run `apt-get install build-essential`.
54
+ * Install Ruby and PostgreSQL client.
55
+ * Install the Ruby and PostgreSQL development headers.
56
+ * On Debian-Linux distributions, you can install the following packages
57
+ with `apt`: `ruby-dev` `libpostgresql-dev`.
58
+ * If you want to cross-compile for Windows:
59
+ * Install MinGW:
60
+ * On Debian-Linux distributions, you can install the following package
61
+ with `apt`: `mingw32`.
62
+ * On OS X, this can install the following package with MacPorts: `i386-mingw32-gcc`.
63
+ * Run `rake-compiler cross-ruby`.
64
+ * Run `rake-compiler update-config`.
65
+
66
+ * For JRuby extensions:
67
+ * Install the Java Development Kit (provided if you are
68
+ on a recent version of Mac OS X) from <http://java.sun.com>.
69
+ * Install a recent version of JRuby. Ensure `jruby` is in your `PATH` and/or
70
+ you have configured the `JRUBY_HOME` environment variable to point to your
71
+ JRuby installation.
72
+ * Install `data_objects` and `do_jdbc` with `jruby -S rake install`.
73
+
74
+ * Then, install this driver with `(jruby -S) rake install`.
75
+
76
+ For more information, see the PostgreSQL driver wiki page:
77
+ <http://wiki.github.com/datamapper/do/postgresql>.
78
+
79
+ ## Developers
80
+
81
+ Follow the above installation instructions. Additionally, you'll need:
82
+ * `bacon` gem for running specs.
83
+ * `YARD` gem for generating documentation.
84
+
85
+ See the DataObjects wiki for more comprehensive information on installing and
86
+ contributing to the JRuby-variant of this driver:
87
+ <http://wiki.github.com/datamapper/do/jruby>.
88
+
89
+ To run specs:
90
+
91
+ rake spec
92
+
93
+ To run specs without compiling extensions first:
94
+
95
+ rake spec_no_compile
96
+
97
+ To run individual specs:
98
+
99
+ rake spec TEST=spec/connection_spec.rb
100
+
101
+ (Note that the `rake` task uses a `TEST` parameter, not `SPEC`. This is because
102
+ the `Rake::TestTask` is used for executing the Bacon specs).
103
+
104
+ ## License
105
+
106
+ This code is licensed under an **MIT (X11) License**. Please see the
107
+ accompanying `LICENSE` file.
data/Rakefile CHANGED
@@ -1,16 +1,63 @@
1
+ require 'pathname'
1
2
  require 'rubygems'
2
3
  require 'rake'
3
4
  require 'rake/clean'
4
5
 
5
- require 'pathname'
6
- require 'lib/do_postgres/version'
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+
8
+ require ROOT + 'lib/do_postgres/version'
9
+
10
+ JRUBY = RUBY_PLATFORM =~ /java/
11
+ IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
12
+ WINDOWS = Gem.win_platform? || (JRUBY && ENV_JAVA['os.name'] =~ /windows/i)
13
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
14
+ BINARY_VERSION = '8.3.9'
15
+
16
+ CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_postgres/Makefile ext-java/target ])
17
+
18
+ begin
19
+ gem 'jeweler', '~> 1.4'
20
+ require 'jeweler'
21
+
22
+ Jeweler::Tasks.new do |gem|
23
+ gem.name = 'do_postgres'
24
+ gem.version = DataObjects::Postgres::VERSION
25
+ gem.summary = 'DataObjects PostgreSQL Driver'
26
+ gem.description = 'Implements the DataObjects API for PostgreSQL'
27
+ gem.platform = Gem::Platform::RUBY
28
+ gem.files = Dir['lib/**/*.rb', 'spec/**/*.rb', 'tasks/**/*.rake',
29
+ 'ext/**/*.{rb,c,h}', 'LICENSE', 'Rakefile',
30
+ '*.{markdown,rdoc,txt,yml}']
31
+ gem.extra_rdoc_files = FileList['README*', 'ChangeLog*', 'LICENSE']
32
+ gem.test_files = FileList['spec/**/*.rb']
33
+
34
+ # rake-compiler should generate gemspecs for other platforms (e.g. 'java')
35
+ # and modify dependencies and extensions appropriately
36
+ gem.extensions << 'ext/do_postgres/extconf.rb'
37
+
38
+ gem.add_dependency 'data_objects', DataObjects::Postgres::VERSION
39
+
40
+ gem.add_development_dependency 'bacon', '~>1.1'
41
+ gem.add_development_dependency 'rake-compiler', '~>0.7'
42
+
43
+ gem.has_rdoc = false
44
+ gem.rubyforge_project = 'dorb'
45
+ gem.authors = [ 'Dirkjan Bussink' ]
46
+ gem.email = 'd.bussink@gmail.com'
47
+ end
7
48
 
8
- ROOT = Pathname(__FILE__).dirname.expand_path
9
- JRUBY = RUBY_PLATFORM =~ /java/
10
- WINDOWS = Gem.win_platform?
11
- SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
12
- BINARY_VERSION = '5.0.77'
49
+ if JRUBY
50
+ Rake::Task['build'].clear_actions if Rake::Task.task_defined?('build')
51
+ Rake::Task['install'].clear_actions if Rake::Task.task_defined?('install')
52
+ task :build => [ :java, :gem ]
53
+ task :install do
54
+ sh "#{Config::CONFIG['RUBY_INSTALL_NAME']} -S gem install pkg/do_postgres-#{DataObjects::Postgres::VERSION}-java.gem"
55
+ end
56
+ end
13
57
 
14
- Dir['tasks/*.rake'].sort.each { |f| import f }
58
+ Jeweler::GemcutterTasks.new
15
59
 
16
- CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_postgres_ext/Makefile ext-java/target ])
60
+ FileList['tasks/**/*.rake'].each { |task| import task }
61
+ rescue LoadError
62
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
63
+ end
@@ -0,0 +1,55 @@
1
+ #ifndef RUBY_COMPAT_H
2
+ #define RUBY_COMPAT_H
3
+
4
+ /*
5
+ * Rules for better ruby C extensions:
6
+ *
7
+ * Never use the R<TYPE> macros directly, always use R<TYPE>_<FIELD>
8
+ *
9
+ * Never compare with RBASIC(obj)->klass, always use
10
+ * rb_obj_is_instance_of()
11
+ *
12
+ * Never use RHASH(obj)->tbl or RHASH_TBL().
13
+ *
14
+ */
15
+
16
+
17
+ // Array
18
+ #ifndef RARRAY_PTR
19
+ #define RARRAY_PTR(obj) RARRAY(obj)->ptr
20
+ #endif
21
+
22
+ #ifndef RARRAY_LEN
23
+ #define RARRAY_LEN(obj) RARRAY(obj)->len
24
+ #endif
25
+
26
+ // String
27
+ #ifndef RSTRING_PTR
28
+ #define RSTRING_PTR(obj) RSTRING(obj)->ptr
29
+ #endif
30
+
31
+ #ifndef RSTRING_LEN
32
+ #define RSTRING_LEN(obj) RSTRING(obj)->len
33
+ #endif
34
+
35
+ #ifndef rb_str_ptr
36
+ #define rb_str_ptr(str) RSTRING_PTR(str)
37
+ #endif
38
+
39
+ #ifndef rb_str_ptr_readonly
40
+ #define rb_str_ptr_readonly(str) RSTRING_PTR(str)
41
+ #endif
42
+
43
+ #ifndef rb_str_flush
44
+ #define rb_str_flush(str)
45
+ #endif
46
+
47
+ #ifndef rb_str_update
48
+ #define rb_str_update(str)
49
+ #endif
50
+
51
+ #ifndef rb_str_len
52
+ #define rb_str_len(str) RSTRING_LEN(str)
53
+ #endif
54
+
55
+ #endif
@@ -0,0 +1,1060 @@
1
+ #include <libpq-fe.h>
2
+ #include <postgres.h>
3
+ #include <mb/pg_wchar.h>
4
+ #include <catalog/pg_type.h>
5
+ #include <utils/errcodes.h>
6
+
7
+ /* Undefine constants Postgres also defines */
8
+ #undef PACKAGE_BUGREPORT
9
+ #undef PACKAGE_NAME
10
+ #undef PACKAGE_STRING
11
+ #undef PACKAGE_TARNAME
12
+ #undef PACKAGE_VERSION
13
+
14
+ #ifdef _WIN32
15
+ /* On Windows this stuff is also defined by Postgres, but we don't
16
+ want to use Postgres' version actually */
17
+ #undef fsync
18
+ #undef ftruncate
19
+ #undef fseeko
20
+ #undef ftello
21
+ #undef stat
22
+ #undef vsnprintf
23
+ #undef snprintf
24
+ #undef sprintf
25
+ #undef printf
26
+ #define cCommand_execute cCommand_execute_sync
27
+ #define do_int64 signed __int64
28
+ #else
29
+ #define cCommand_execute cCommand_execute_async
30
+ #define do_int64 signed long long int
31
+ #endif
32
+
33
+ #include <ruby.h>
34
+ #include <string.h>
35
+ #include <math.h>
36
+ #include <ctype.h>
37
+ #include <time.h>
38
+ #include "error.h"
39
+ #include "compat.h"
40
+
41
+ #define ID_CONST_GET rb_intern("const_get")
42
+ #define ID_PATH rb_intern("path")
43
+ #define ID_NEW rb_intern("new")
44
+ #define ID_ESCAPE rb_intern("escape_sql")
45
+
46
+ #define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
47
+ #define POSTGRES_CLASS(klass, parent) (rb_define_class_under(mPostgres, klass, parent))
48
+
49
+ #ifdef HAVE_RUBY_ENCODING_H
50
+ #include <ruby/encoding.h>
51
+
52
+ #define DO_STR_NEW2(str, encoding) \
53
+ ({ \
54
+ VALUE _string = rb_str_new2((const char *)str); \
55
+ if(encoding != -1) { \
56
+ rb_enc_associate_index(_string, encoding); \
57
+ } \
58
+ _string; \
59
+ })
60
+
61
+ #define DO_STR_NEW(str, len, encoding) \
62
+ ({ \
63
+ VALUE _string = rb_str_new((const char *)str, (long)len); \
64
+ if(encoding != -1) { \
65
+ rb_enc_associate_index(_string, encoding); \
66
+ } \
67
+ _string; \
68
+ })
69
+
70
+ #else
71
+
72
+ #define DO_STR_NEW2(str, doc) \
73
+ rb_str_new2((const char *)str)
74
+
75
+ #define DO_STR_NEW(str, len, doc) \
76
+ rb_str_new((const char *)str, (long)len)
77
+ #endif
78
+
79
+
80
+ // To store rb_intern values
81
+ static ID ID_NEW_DATE;
82
+ static ID ID_LOGGER;
83
+ static ID ID_DEBUG;
84
+ static ID ID_LEVEL;
85
+ static ID ID_TO_S;
86
+ static ID ID_RATIONAL;
87
+
88
+ static VALUE mExtlib;
89
+ static VALUE mDO;
90
+ static VALUE mEncoding;
91
+ static VALUE cDO_Quoting;
92
+ static VALUE cDO_Connection;
93
+ static VALUE cDO_Command;
94
+ static VALUE cDO_Result;
95
+ static VALUE cDO_Reader;
96
+
97
+ static VALUE rb_cDate;
98
+ static VALUE rb_cDateTime;
99
+ static VALUE rb_cBigDecimal;
100
+ static VALUE rb_cByteArray;
101
+
102
+ static VALUE mPostgres;
103
+ static VALUE cConnection;
104
+ static VALUE cCommand;
105
+ static VALUE cResult;
106
+ static VALUE cReader;
107
+
108
+ static VALUE eArgumentError;
109
+ static VALUE eConnectionError;
110
+ static VALUE eDataError;
111
+
112
+ static void data_objects_debug(VALUE string, struct timeval* start) {
113
+ struct timeval stop;
114
+ char *message;
115
+
116
+ const char *query = rb_str_ptr_readonly(string);
117
+ size_t length = rb_str_len(string);
118
+ char total_time[32];
119
+ do_int64 duration = 0;
120
+
121
+ VALUE logger = rb_funcall(mPostgres, ID_LOGGER, 0);
122
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
123
+
124
+ if (0 == log_level) {
125
+ gettimeofday(&stop, NULL);
126
+
127
+ duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
128
+
129
+ snprintf(total_time, 32, "%.6f", duration / 1000000.0);
130
+ message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
131
+ snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
132
+ rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
133
+ }
134
+ }
135
+
136
+ static const char * get_uri_option(VALUE query_hash, const char * key) {
137
+ VALUE query_value;
138
+ const char * value = NULL;
139
+
140
+ if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
141
+
142
+ query_value = rb_hash_aref(query_hash, rb_str_new2(key));
143
+
144
+ if (Qnil != query_value) {
145
+ value = StringValuePtr(query_value);
146
+ }
147
+
148
+ return value;
149
+ }
150
+
151
+ /* ====== Time/Date Parsing Helper Functions ====== */
152
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
153
+ do_int64 a, b, c;
154
+ a = *numerator;
155
+ b = *denominator;
156
+ while ( a != 0 ) {
157
+ c = a; a = b % a; b = c;
158
+ }
159
+ *numerator = *numerator / b;
160
+ *denominator = *denominator / b;
161
+ }
162
+
163
+ // Generate the date integer which Date.civil_to_jd returns
164
+ static int jd_from_date(int year, int month, int day) {
165
+ int a, b;
166
+ if ( month <= 2 ) {
167
+ year -= 1;
168
+ month += 12;
169
+ }
170
+ a = year / 100;
171
+ b = 2 - a + (a / 4);
172
+ return (int) (floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524);
173
+ }
174
+
175
+ static VALUE parse_date(const char *date) {
176
+ int year, month, day;
177
+ int jd, ajd;
178
+ VALUE rational;
179
+
180
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
181
+
182
+ jd = jd_from_date(year, month, day);
183
+
184
+ // Math from Date.jd_to_ajd
185
+ ajd = jd * 2 - 1;
186
+ rational = rb_funcall(rb_mKernel, ID_RATIONAL, 2, INT2NUM(ajd), INT2NUM(2));
187
+
188
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
189
+ }
190
+
191
+ // Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
192
+ static VALUE seconds_to_offset(do_int64 num) {
193
+ do_int64 den = 86400;
194
+ reduce(&num, &den);
195
+ return rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ll2inum(num), rb_ll2inum(den));
196
+ }
197
+
198
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
199
+ do_int64 seconds = 0;
200
+
201
+ seconds += hour_offset * 3600;
202
+ seconds += minute_offset * 60;
203
+
204
+ return seconds_to_offset(seconds);
205
+ }
206
+
207
+ static VALUE parse_date_time(const char *date) {
208
+ VALUE ajd, offset;
209
+
210
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
211
+ int jd;
212
+ do_int64 num, den;
213
+
214
+ long int gmt_offset;
215
+ int is_dst;
216
+
217
+ time_t rawtime;
218
+ struct tm * timeinfo;
219
+
220
+ int tokens_read, max_tokens;
221
+
222
+ if (0 != strchr(date, '.')) {
223
+ // This is a datetime with sub-second precision
224
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
225
+ max_tokens = 9;
226
+ } else {
227
+ // This is a datetime second precision
228
+ tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
229
+ max_tokens = 8;
230
+ }
231
+
232
+ if (max_tokens == tokens_read) {
233
+ // We read the Date, Time, and Timezone info
234
+ minute_offset *= hour_offset < 0 ? -1 : 1;
235
+ } else if ((max_tokens - 1) == tokens_read) {
236
+ // We read the Date and Time, but no Minute Offset
237
+ minute_offset = 0;
238
+ } else if (tokens_read == 3 || tokens_read >= (max_tokens - 3)) {
239
+ if (tokens_read == 3) {
240
+ hour = 0;
241
+ min = 0;
242
+ hour_offset = 0;
243
+ minute_offset = 0;
244
+ sec = 0;
245
+ }
246
+ // We read the Date and Time, default to the current locale's offset
247
+
248
+ // Get localtime
249
+ time(&rawtime);
250
+ timeinfo = localtime(&rawtime);
251
+
252
+ is_dst = timeinfo->tm_isdst * 3600;
253
+
254
+ // Reset to GM Time
255
+ timeinfo = gmtime(&rawtime);
256
+
257
+ gmt_offset = mktime(timeinfo) - rawtime;
258
+
259
+ if ( is_dst > 0 )
260
+ gmt_offset -= is_dst;
261
+
262
+ hour_offset = -((int)gmt_offset / 3600);
263
+ minute_offset = -((int)gmt_offset % 3600 / 60);
264
+
265
+ } else {
266
+ // Something went terribly wrong
267
+ rb_raise(eDataError, "Couldn't parse date: %s", date);
268
+ }
269
+
270
+ jd = jd_from_date(year, month, day);
271
+
272
+ // Generate ajd with fractional days for the time
273
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
274
+ num = (hour * 1440) + (min * 24);
275
+
276
+ // Modify the numerator so when we apply the timezone everything works out
277
+ num -= (hour_offset * 1440) + (minute_offset * 24);
278
+
279
+ den = (24 * 1440);
280
+ reduce(&num, &den);
281
+
282
+ num = (num * 86400) + (sec * den);
283
+ den = den * 86400;
284
+ reduce(&num, &den);
285
+
286
+ num = (jd * den) + num;
287
+
288
+ num = num * 2;
289
+ num = num - den;
290
+ den = den * 2;
291
+
292
+ reduce(&num, &den);
293
+
294
+ ajd = rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ull2inum(num), rb_ull2inum(den));
295
+ offset = timezone_to_offset(hour_offset, minute_offset);
296
+
297
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
298
+ }
299
+
300
+ static VALUE parse_time(const char *date) {
301
+
302
+ int year, month, day, hour, min, sec, usec, tokens;
303
+ char subsec[7];
304
+
305
+ if (0 != strchr(date, '.')) {
306
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
307
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
308
+ usec = atoi(subsec);
309
+ usec *= (int) pow(10, (6 - strlen(subsec)));
310
+ } else {
311
+ tokens = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
312
+ if (tokens == 3) {
313
+ hour = 0;
314
+ min = 0;
315
+ sec = 0;
316
+ }
317
+ usec = 0;
318
+ }
319
+
320
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
321
+ }
322
+
323
+ /* ===== Typecasting Functions ===== */
324
+
325
+ static VALUE infer_ruby_type(Oid type) {
326
+ switch(type) {
327
+ case BITOID:
328
+ case VARBITOID:
329
+ case INT2OID:
330
+ case INT4OID:
331
+ case INT8OID:
332
+ return rb_cInteger;
333
+ case FLOAT4OID:
334
+ case FLOAT8OID:
335
+ return rb_cFloat;
336
+ case NUMERICOID:
337
+ case CASHOID:
338
+ return rb_cBigDecimal;
339
+ case BOOLOID:
340
+ return rb_cTrueClass;
341
+ case TIMESTAMPTZOID:
342
+ case TIMESTAMPOID:
343
+ return rb_cDateTime;
344
+ case DATEOID:
345
+ return rb_cDate;
346
+ case BYTEAOID:
347
+ return rb_cByteArray;
348
+ default:
349
+ return rb_cString;
350
+ }
351
+ }
352
+
353
+ static VALUE typecast(const char *value, long length, const VALUE type, int encoding) {
354
+
355
+ if (type == rb_cInteger) {
356
+ return rb_cstr2inum(value, 10);
357
+ } else if (type == rb_cString) {
358
+ return DO_STR_NEW(value, length, encoding);
359
+ } else if (type == rb_cFloat) {
360
+ return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
361
+ } else if (type == rb_cBigDecimal) {
362
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, rb_str_new(value, length));
363
+ } else if (type == rb_cDate) {
364
+ return parse_date(value);
365
+ } else if (type == rb_cDateTime) {
366
+ return parse_date_time(value);
367
+ } else if (type == rb_cTime) {
368
+ return parse_time(value);
369
+ } else if (type == rb_cTrueClass) {
370
+ return *value == 't' ? Qtrue : Qfalse;
371
+ } else if (type == rb_cByteArray) {
372
+ size_t new_length = 0;
373
+ char* unescaped = (char *)PQunescapeBytea((unsigned char*)value, &new_length);
374
+ VALUE byte_array = rb_funcall(rb_cByteArray, ID_NEW, 1, rb_str_new(unescaped, new_length));
375
+ PQfreemem(unescaped);
376
+ return byte_array;
377
+ } else if (type == rb_cClass) {
378
+ return rb_funcall(mDO, rb_intern("full_const_get"), 1, rb_str_new(value, length));
379
+ } else if (type == rb_cObject) {
380
+ return rb_marshal_load(rb_str_new(value, length));
381
+ } else if (type == rb_cNilClass) {
382
+ return Qnil;
383
+ } else {
384
+ return DO_STR_NEW(value, length, encoding);
385
+ }
386
+
387
+ }
388
+
389
+ static void raise_error(VALUE self, PGresult *result, VALUE query) {
390
+ VALUE exception;
391
+ char *message;
392
+ char *sqlstate;
393
+ int postgres_errno;
394
+
395
+ message = PQresultErrorMessage(result);
396
+ sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE);
397
+ postgres_errno = MAKE_SQLSTATE(sqlstate[0], sqlstate[1], sqlstate[2], sqlstate[3], sqlstate[4]);
398
+ PQclear(result);
399
+
400
+ const char *exception_type = "SQLError";
401
+
402
+ struct errcodes *errs;
403
+
404
+ for (errs = errors; errs->error_name; errs++) {
405
+ if(errs->error_no == postgres_errno) {
406
+ exception_type = errs->exception;
407
+ break;
408
+ }
409
+ }
410
+
411
+ VALUE uri = rb_funcall(rb_iv_get(self, "@connection"), rb_intern("to_s"), 0);
412
+
413
+ exception = rb_funcall(CONST_GET(mDO, exception_type), ID_NEW, 5,
414
+ rb_str_new2(message),
415
+ INT2NUM(postgres_errno),
416
+ rb_str_new2(sqlstate),
417
+ query,
418
+ uri);
419
+ rb_exc_raise(exception);
420
+ }
421
+
422
+
423
+ /* ====== Public API ======= */
424
+ static VALUE cConnection_dispose(VALUE self) {
425
+ VALUE connection_container = rb_iv_get(self, "@connection");
426
+
427
+ PGconn *db;
428
+
429
+ if (Qnil == connection_container)
430
+ return Qfalse;
431
+
432
+ db = DATA_PTR(connection_container);
433
+
434
+ if (NULL == db)
435
+ return Qfalse;
436
+
437
+ PQfinish(db);
438
+ rb_iv_set(self, "@connection", Qnil);
439
+
440
+ return Qtrue;
441
+ }
442
+
443
+ static VALUE cCommand_set_types(int argc, VALUE *argv, VALUE self) {
444
+ VALUE type_strings = rb_ary_new();
445
+ VALUE array = rb_ary_new();
446
+
447
+ int i, j;
448
+
449
+ for ( i = 0; i < argc; i++) {
450
+ rb_ary_push(array, argv[i]);
451
+ }
452
+
453
+ for (i = 0; i < RARRAY_LEN(array); i++) {
454
+ VALUE entry = rb_ary_entry(array, i);
455
+ if(TYPE(entry) == T_CLASS) {
456
+ rb_ary_push(type_strings, entry);
457
+ } else if (TYPE(entry) == T_ARRAY) {
458
+ for (j = 0; j < RARRAY_LEN(entry); j++) {
459
+ VALUE sub_entry = rb_ary_entry(entry, j);
460
+ if(TYPE(sub_entry) == T_CLASS) {
461
+ rb_ary_push(type_strings, sub_entry);
462
+ } else {
463
+ rb_raise(eArgumentError, "Invalid type given");
464
+ }
465
+ }
466
+ } else {
467
+ rb_raise(eArgumentError, "Invalid type given");
468
+ }
469
+ }
470
+
471
+ rb_iv_set(self, "@field_types", type_strings);
472
+
473
+ return array;
474
+ }
475
+
476
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args[]) {
477
+ VALUE query = rb_iv_get(klass, "@text");
478
+
479
+ int i;
480
+ VALUE array = rb_ary_new();
481
+ for ( i = 0; i < count; i++) {
482
+ rb_ary_push(array, (VALUE)args[i]);
483
+ }
484
+ query = rb_funcall(klass, ID_ESCAPE, 1, array);
485
+
486
+ return query;
487
+ }
488
+
489
+ static VALUE cConnection_quote_string(VALUE self, VALUE string) {
490
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
491
+
492
+ const char *source = rb_str_ptr_readonly(string);
493
+ size_t source_len = rb_str_len(string);
494
+
495
+ char *escaped;
496
+ size_t quoted_length = 0;
497
+ VALUE result;
498
+
499
+ // Allocate space for the escaped version of 'string'
500
+ // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
501
+ escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
502
+
503
+ // Escape 'source' using the current charset in use on the conection 'db'
504
+ quoted_length = PQescapeStringConn(db, escaped + 1, source, source_len, NULL);
505
+
506
+ // Wrap the escaped string in single-quotes, this is DO's convention
507
+ escaped[quoted_length + 1] = escaped[0] = '\'';
508
+
509
+ result = DO_STR_NEW(escaped, quoted_length + 2, FIX2INT(rb_iv_get(self, "@encoding_id")));
510
+
511
+ free(escaped);
512
+ return result;
513
+ }
514
+
515
+ static VALUE cConnection_quote_byte_array(VALUE self, VALUE string) {
516
+ PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
517
+
518
+ const unsigned char *source = (unsigned char*) rb_str_ptr_readonly(string);
519
+ size_t source_len = rb_str_len(string);
520
+
521
+ unsigned char *escaped;
522
+ unsigned char *escaped_quotes;
523
+ size_t quoted_length = 0;
524
+ VALUE result;
525
+
526
+ // Allocate space for the escaped version of 'string'
527
+ // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
528
+ escaped = PQescapeByteaConn(db, source, source_len, &quoted_length);
529
+ escaped_quotes = (unsigned char *)calloc(quoted_length + 1, sizeof(unsigned char));
530
+ memcpy(escaped_quotes + 1, escaped, quoted_length);
531
+
532
+ // Wrap the escaped string in single-quotes, this is DO's convention (replace trailing \0)
533
+ escaped_quotes[quoted_length] = escaped_quotes[0] = '\'';
534
+
535
+ result = rb_str_new((const char *)escaped_quotes, quoted_length + 1);
536
+ PQfreemem(escaped);
537
+ free(escaped_quotes);
538
+ return result;
539
+ }
540
+
541
+ static void full_connect(VALUE self, PGconn *db);
542
+
543
+ #ifdef _WIN32
544
+ static PGresult* cCommand_execute_sync(VALUE self, PGconn *db, VALUE query) {
545
+ PGresult *response;
546
+ struct timeval start;
547
+ char* str = StringValuePtr(query);
548
+
549
+ while ((response = PQgetResult(db)) != NULL) {
550
+ PQclear(response);
551
+ }
552
+
553
+ gettimeofday(&start, NULL);
554
+
555
+ response = PQexec(db, str);
556
+
557
+ data_objects_debug(query, &start);
558
+
559
+ if (response == NULL) {
560
+ if(PQstatus(db) != CONNECTION_OK) {
561
+ PQreset(db);
562
+ if (PQstatus(db) == CONNECTION_OK) {
563
+ response = PQexec(db, str);
564
+ } else {
565
+ VALUE connection = rb_iv_get(self, "@connection");
566
+ full_connect(connection, db);
567
+ response = PQexec(db, str);
568
+ }
569
+ }
570
+
571
+ if(response == NULL) {
572
+ rb_raise(eConnectionError, PQerrorMessage(db));
573
+ }
574
+ }
575
+
576
+ return response;
577
+ }
578
+ #else
579
+ static PGresult* cCommand_execute_async(VALUE self, PGconn *db, VALUE query) {
580
+ int socket_fd;
581
+ int retval;
582
+ fd_set rset;
583
+ PGresult *response;
584
+ struct timeval start;
585
+ char* str = StringValuePtr(query);
586
+
587
+ while ((response = PQgetResult(db)) != NULL) {
588
+ PQclear(response);
589
+ }
590
+
591
+ retval = PQsendQuery(db, str);
592
+
593
+ if (!retval) {
594
+ if(PQstatus(db) != CONNECTION_OK) {
595
+ PQreset(db);
596
+ if (PQstatus(db) == CONNECTION_OK) {
597
+ retval = PQsendQuery(db, str);
598
+ } else {
599
+ VALUE connection = rb_iv_get(self, "@connection");
600
+ full_connect(connection, db);
601
+ retval = PQsendQuery(db, str);
602
+ }
603
+ }
604
+
605
+ if(!retval) {
606
+ rb_raise(eConnectionError, "%s", PQerrorMessage(db));
607
+ }
608
+ }
609
+
610
+ gettimeofday(&start, NULL);
611
+ socket_fd = PQsocket(db);
612
+
613
+ data_objects_debug(query, &start);
614
+
615
+ for(;;) {
616
+ FD_ZERO(&rset);
617
+ FD_SET(socket_fd, &rset);
618
+ retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
619
+ if (retval < 0) {
620
+ rb_sys_fail(0);
621
+ }
622
+
623
+ if (retval == 0) {
624
+ continue;
625
+ }
626
+
627
+ if (PQconsumeInput(db) == 0) {
628
+ rb_raise(eConnectionError, "%s", PQerrorMessage(db));
629
+ }
630
+
631
+ if (PQisBusy(db) == 0) {
632
+ break;
633
+ }
634
+ }
635
+
636
+ return PQgetResult(db);
637
+ }
638
+ #endif
639
+
640
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
641
+ VALUE r_host, r_user, r_password, r_path, r_query, r_port;
642
+
643
+ PGconn *db = NULL;
644
+
645
+ rb_iv_set(self, "@using_socket", Qfalse);
646
+
647
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
648
+ if (Qnil != r_host) {
649
+ rb_iv_set(self, "@host", r_host);
650
+ }
651
+
652
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
653
+ if (Qnil != r_user) {
654
+ rb_iv_set(self, "@user", r_user);
655
+ }
656
+
657
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
658
+ if (Qnil != r_password) {
659
+ rb_iv_set(self, "@password", r_password);
660
+ }
661
+
662
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
663
+ if (Qnil != r_path) {
664
+ rb_iv_set(self, "@path", r_path);
665
+ }
666
+
667
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
668
+ if (Qnil != r_port) {
669
+ r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
670
+ rb_iv_set(self, "@port", r_port);
671
+ }
672
+
673
+ // Pull the querystring off the URI
674
+ r_query = rb_funcall(uri, rb_intern("query"), 0);
675
+ rb_iv_set(self, "@query", r_query);
676
+
677
+ const char* encoding = get_uri_option(r_query, "encoding");
678
+ if (!encoding) { encoding = get_uri_option(r_query, "charset"); }
679
+ if (!encoding) { encoding = "UTF-8"; }
680
+
681
+ rb_iv_set(self, "@encoding", rb_str_new2(encoding));
682
+
683
+ full_connect(self, db);
684
+
685
+ rb_iv_set(self, "@uri", uri);
686
+
687
+ return Qtrue;
688
+ }
689
+
690
+ static void full_connect(VALUE self, PGconn *db) {
691
+
692
+ PGresult *result = NULL;
693
+ VALUE r_host, r_user, r_password, r_path, r_port, r_query, r_options;
694
+ char *host = NULL, *user = NULL, *password = NULL, *path = NULL, *database = NULL;
695
+ const char *port = "5432";
696
+ VALUE encoding = Qnil;
697
+ const char *search_path = NULL;
698
+ char *search_path_query = NULL;
699
+ const char *backslash_off = "SET backslash_quote = off";
700
+ const char *standard_strings_on = "SET standard_conforming_strings = on";
701
+ const char *warning_messages = "SET client_min_messages = warning";
702
+
703
+ if((r_host = rb_iv_get(self, "@host")) != Qnil) {
704
+ host = StringValuePtr(r_host);
705
+ }
706
+
707
+ if((r_user = rb_iv_get(self, "@user")) != Qnil) {
708
+ user = StringValuePtr(r_user);
709
+ }
710
+
711
+ if((r_password = rb_iv_get(self, "@password")) != Qnil) {
712
+ password = StringValuePtr(r_password);
713
+ }
714
+
715
+ if((r_port = rb_iv_get(self, "@port")) != Qnil) {
716
+ port = StringValuePtr(r_port);
717
+ }
718
+
719
+ if((r_path = rb_iv_get(self, "@path")) != Qnil) {
720
+ path = StringValuePtr(r_path);
721
+ database = strtok(path, "/");
722
+ }
723
+
724
+ if (NULL == database || 0 == strlen(database)) {
725
+ rb_raise(eConnectionError, "Database must be specified");
726
+ }
727
+
728
+ r_query = rb_iv_get(self, "@query");
729
+
730
+ search_path = get_uri_option(r_query, "search_path");
731
+
732
+ db = PQsetdbLogin(
733
+ host,
734
+ port,
735
+ NULL,
736
+ NULL,
737
+ database,
738
+ user,
739
+ password
740
+ );
741
+
742
+ if ( PQstatus(db) == CONNECTION_BAD ) {
743
+ rb_raise(eConnectionError, "%s", PQerrorMessage(db));
744
+ }
745
+
746
+ if (search_path != NULL) {
747
+ search_path_query = (char *)calloc(256, sizeof(char));
748
+ snprintf(search_path_query, 256, "set search_path to %s;", search_path);
749
+ r_query = rb_str_new2(search_path_query);
750
+ result = cCommand_execute(self, db, r_query);
751
+
752
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
753
+ free((void *)search_path_query);
754
+ raise_error(self, result, r_query);
755
+ }
756
+
757
+ free((void *)search_path_query);
758
+ }
759
+
760
+ r_options = rb_str_new2(backslash_off);
761
+ result = cCommand_execute(self, db, r_options);
762
+
763
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
764
+ rb_warn("%s", PQresultErrorMessage(result));
765
+ }
766
+
767
+ r_options = rb_str_new2(standard_strings_on);
768
+ result = cCommand_execute(self, db, r_options);
769
+
770
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
771
+ rb_warn("%s", PQresultErrorMessage(result));
772
+ }
773
+
774
+ r_options = rb_str_new2(warning_messages);
775
+ result = cCommand_execute(self, db, r_options);
776
+
777
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
778
+ rb_warn("%s", PQresultErrorMessage(result));
779
+ }
780
+
781
+ encoding = rb_iv_get(self, "@encoding");
782
+
783
+ #ifdef HAVE_PQSETCLIENTENCODING
784
+ VALUE pg_encoding = rb_hash_aref(CONST_GET(mEncoding, "MAP"), encoding);
785
+ if(pg_encoding != Qnil) {
786
+ if(PQsetClientEncoding(db, rb_str_ptr_readonly(pg_encoding))) {
787
+ rb_raise(eConnectionError, "Couldn't set encoding: %s", rb_str_ptr_readonly(encoding));
788
+ } else {
789
+ #ifdef HAVE_RUBY_ENCODING_H
790
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index(rb_str_ptr_readonly(encoding))));
791
+ #endif
792
+ rb_iv_set(self, "@pg_encoding", pg_encoding);
793
+ }
794
+ } else {
795
+ rb_warn("Encoding %s is not a known Ruby encoding for PostgreSQL\n", rb_str_ptr_readonly(encoding));
796
+ rb_iv_set(self, "@encoding", rb_str_new2("UTF-8"));
797
+ #ifdef HAVE_RUBY_ENCODING_H
798
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index("UTF-8")));
799
+ #endif
800
+ rb_iv_set(self, "@pg_encoding", rb_str_new2("UTF8"));
801
+ }
802
+ #endif
803
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
804
+ }
805
+
806
+ static VALUE cConnection_character_set(VALUE self) {
807
+ return rb_iv_get(self, "@encoding");
808
+ }
809
+
810
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
811
+ VALUE connection = rb_iv_get(self, "@connection");
812
+ VALUE postgres_connection = rb_iv_get(connection, "@connection");
813
+ if (Qnil == postgres_connection) {
814
+ rb_raise(eConnectionError, "This connection has already been closed.");
815
+ }
816
+
817
+ PGconn *db = DATA_PTR(postgres_connection);
818
+ PGresult *response;
819
+ int status;
820
+
821
+ VALUE affected_rows = Qnil;
822
+ VALUE insert_id = Qnil;
823
+
824
+ VALUE query = build_query_from_args(self, argc, argv);
825
+
826
+ response = cCommand_execute(self, db, query);
827
+
828
+ status = PQresultStatus(response);
829
+
830
+ if ( status == PGRES_TUPLES_OK ) {
831
+ insert_id = INT2NUM(atoi(PQgetvalue(response, 0, 0)));
832
+ affected_rows = INT2NUM(atoi(PQcmdTuples(response)));
833
+ }
834
+ else if ( status == PGRES_COMMAND_OK ) {
835
+ insert_id = Qnil;
836
+ affected_rows = INT2NUM(atoi(PQcmdTuples(response)));
837
+ }
838
+ else {
839
+ raise_error(self, response, query);
840
+ }
841
+
842
+ PQclear(response);
843
+
844
+ return rb_funcall(cResult, ID_NEW, 3, self, affected_rows, insert_id);
845
+ }
846
+
847
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
848
+ VALUE reader, query;
849
+ VALUE field_names, field_types;
850
+
851
+ int i;
852
+ int field_count;
853
+ int infer_types = 0;
854
+
855
+ VALUE connection = rb_iv_get(self, "@connection");
856
+ VALUE postgres_connection = rb_iv_get(connection, "@connection");
857
+ if (Qnil == postgres_connection) {
858
+ rb_raise(eConnectionError, "This connection has already been closed.");
859
+ }
860
+
861
+ PGconn *db = DATA_PTR(postgres_connection);
862
+ PGresult *response;
863
+
864
+ query = build_query_from_args(self, argc, argv);
865
+
866
+ response = cCommand_execute(self, db, query);
867
+
868
+ if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
869
+ raise_error(self, response, query);
870
+ }
871
+
872
+ field_count = PQnfields(response);
873
+
874
+ reader = rb_funcall(cReader, ID_NEW, 0);
875
+ rb_iv_set(reader, "@connection", connection);
876
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
877
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
878
+ rb_iv_set(reader, "@row_count", INT2NUM(PQntuples(response)));
879
+
880
+ field_names = rb_ary_new();
881
+ field_types = rb_iv_get(self, "@field_types");
882
+
883
+ if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
884
+ field_types = rb_ary_new();
885
+ infer_types = 1;
886
+ } else if (RARRAY_LEN(field_types) != field_count) {
887
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
888
+ // and error
889
+ rb_funcall(reader, rb_intern("close"), 0);
890
+ rb_raise(eArgumentError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
891
+ }
892
+
893
+ for ( i = 0; i < field_count; i++ ) {
894
+ rb_ary_push(field_names, rb_str_new2(PQfname(response, i)));
895
+ if ( infer_types == 1 ) {
896
+ rb_ary_push(field_types, infer_ruby_type(PQftype(response, i)));
897
+ }
898
+ }
899
+
900
+ rb_iv_set(reader, "@position", INT2NUM(0));
901
+ rb_iv_set(reader, "@fields", field_names);
902
+ rb_iv_set(reader, "@field_types", field_types);
903
+
904
+ return reader;
905
+ }
906
+
907
+ static VALUE cReader_close(VALUE self) {
908
+ VALUE reader_container = rb_iv_get(self, "@reader");
909
+
910
+ PGresult *reader;
911
+
912
+ if (Qnil == reader_container)
913
+ return Qfalse;
914
+
915
+ reader = DATA_PTR(reader_container);
916
+
917
+ if (NULL == reader)
918
+ return Qfalse;
919
+
920
+ PQclear(reader);
921
+ rb_iv_set(self, "@reader", Qnil);
922
+ return Qtrue;
923
+ }
924
+
925
+ static VALUE cReader_next(VALUE self) {
926
+ PGresult *reader = DATA_PTR(rb_iv_get(self, "@reader"));
927
+
928
+ int field_count;
929
+ int row_count;
930
+ int i;
931
+ int position;
932
+
933
+ VALUE array = rb_ary_new();
934
+ VALUE field_types, field_type;
935
+ VALUE value;
936
+
937
+ row_count = NUM2INT(rb_iv_get(self, "@row_count"));
938
+ field_count = NUM2INT(rb_iv_get(self, "@field_count"));
939
+ field_types = rb_iv_get(self, "@field_types");
940
+ position = NUM2INT(rb_iv_get(self, "@position"));
941
+
942
+ if ( position > (row_count - 1) ) {
943
+ rb_iv_set(self, "@values", Qnil);
944
+ return Qfalse;
945
+ }
946
+
947
+ int enc = -1;
948
+ #ifdef HAVE_RUBY_ENCODING_H
949
+ VALUE encoding_id = rb_iv_get(rb_iv_get(self, "@connection"), "@encoding_id");
950
+ if (encoding_id != Qnil) {
951
+ enc = FIX2INT(encoding_id);
952
+ }
953
+ #endif
954
+
955
+ for ( i = 0; i < field_count; i++ ) {
956
+ field_type = rb_ary_entry(field_types, i);
957
+
958
+ // Always return nil if the value returned from Postgres is null
959
+ if (!PQgetisnull(reader, position, i)) {
960
+ value = typecast(PQgetvalue(reader, position, i), PQgetlength(reader, position, i), field_type, enc);
961
+ } else {
962
+ value = Qnil;
963
+ }
964
+
965
+ rb_ary_push(array, value);
966
+ }
967
+
968
+ rb_iv_set(self, "@values", array);
969
+ rb_iv_set(self, "@position", INT2NUM(position+1));
970
+
971
+ return Qtrue;
972
+ }
973
+
974
+ static VALUE cReader_values(VALUE self) {
975
+
976
+ VALUE values = rb_iv_get(self, "@values");
977
+ if(values == Qnil) {
978
+ rb_raise(eDataError, "Reader not initialized");
979
+ return Qnil;
980
+ } else {
981
+ return values;
982
+ }
983
+ }
984
+
985
+ static VALUE cReader_fields(VALUE self) {
986
+ return rb_iv_get(self, "@fields");
987
+ }
988
+
989
+ static VALUE cReader_field_count(VALUE self) {
990
+ return rb_iv_get(self, "@field_count");
991
+ }
992
+
993
+ void Init_do_postgres() {
994
+ rb_require("date");
995
+ rb_require("bigdecimal");
996
+
997
+ // Get references classes needed for Date/Time parsing
998
+ rb_cDate = CONST_GET(rb_mKernel, "Date");
999
+ rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
1000
+ rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
1001
+
1002
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
1003
+
1004
+ #ifdef RUBY_LESS_THAN_186
1005
+ ID_NEW_DATE = rb_intern("new0");
1006
+ #else
1007
+ ID_NEW_DATE = rb_intern("new!");
1008
+ #endif
1009
+ ID_LOGGER = rb_intern("logger");
1010
+ ID_DEBUG = rb_intern("debug");
1011
+ ID_LEVEL = rb_intern("level");
1012
+ ID_TO_S = rb_intern("to_s");
1013
+ ID_RATIONAL = rb_intern("Rational");
1014
+
1015
+ // Get references to the Extlib module
1016
+ mExtlib = CONST_GET(rb_mKernel, "Extlib");
1017
+ rb_cByteArray = CONST_GET(mExtlib, "ByteArray");
1018
+
1019
+ // Get references to the DataObjects module and its classes
1020
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
1021
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
1022
+ cDO_Connection = CONST_GET(mDO, "Connection");
1023
+ cDO_Command = CONST_GET(mDO, "Command");
1024
+ cDO_Result = CONST_GET(mDO, "Result");
1025
+ cDO_Reader = CONST_GET(mDO, "Reader");
1026
+
1027
+ eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
1028
+ mPostgres = rb_define_module_under(mDO, "Postgres");
1029
+ eConnectionError = CONST_GET(mDO, "ConnectionError");
1030
+ eDataError = CONST_GET(mDO, "DataError");
1031
+ mEncoding = rb_define_module_under(mPostgres, "Encoding");
1032
+
1033
+ cConnection = POSTGRES_CLASS("Connection", cDO_Connection);
1034
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
1035
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
1036
+ rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
1037
+ rb_define_method(cConnection, "quote_string", cConnection_quote_string, 1);
1038
+ rb_define_method(cConnection, "quote_byte_array", cConnection_quote_byte_array, 1);
1039
+
1040
+ cCommand = POSTGRES_CLASS("Command", cDO_Command);
1041
+ rb_define_method(cCommand, "set_types", cCommand_set_types, -1);
1042
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
1043
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
1044
+
1045
+ cResult = POSTGRES_CLASS("Result", cDO_Result);
1046
+
1047
+ cReader = POSTGRES_CLASS("Reader", cDO_Reader);
1048
+ rb_define_method(cReader, "close", cReader_close, 0);
1049
+ rb_define_method(cReader, "next!", cReader_next, 0);
1050
+ rb_define_method(cReader, "values", cReader_values, 0);
1051
+ rb_define_method(cReader, "fields", cReader_fields, 0);
1052
+ rb_define_method(cReader, "field_count", cReader_field_count, 0);
1053
+
1054
+ struct errcodes *errs;
1055
+
1056
+ for (errs = errors; errs->error_name; errs++) {
1057
+ rb_const_set(mPostgres, rb_intern(errs->error_name), INT2NUM(errs->error_no));
1058
+ }
1059
+
1060
+ }