do_postgres 0.10.0-java → 0.10.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }