do_postgres 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,15 +18,18 @@ def config_value(type)
18
18
  end
19
19
 
20
20
  def pg_config(type)
21
- IO.popen("pg_config --#{type}dir").readline.chomp rescue nil
21
+ IO.popen("pg_config --#{type}").readline.chomp rescue nil
22
22
  end
23
23
 
24
24
  def have_build_env
25
25
  (have_library('pq') || have_library('libpq')) &&
26
- have_header('libpq-fe.h') && have_header('libpq/libpq-fs.h')
26
+ have_header('libpq-fe.h') && have_header('libpq/libpq-fs.h') &&
27
+ have_header('postgres.h') && have_header('mb/pg_wchar.h') &&
28
+ have_header('catalog/pg_type.h')
27
29
  end
28
30
 
29
- dir_config('pgsql', config_value('include'), config_value('lib'))
31
+ dir_config('pgsql', config_value('includedir-server'), config_value('libdir'))
32
+ dir_config('pgsql', config_value('includedir'), config_value('libdir'))
30
33
 
31
34
  required_libraries = []
32
35
  desired_functions = %w(PQsetClientEncoding pg_encoding_to_char PQfreemem)
data/lib/do_postgres.rb CHANGED
@@ -1,5 +1,30 @@
1
-
2
1
  require 'rubygems'
3
2
  require 'data_objects'
4
- require 'do_postgres_ext'
5
- require 'do_postgres/transaction'
3
+ if RUBY_PLATFORM =~ /java/
4
+ require 'do_jdbc'
5
+ require 'java'
6
+ gem 'jdbc-postgres'
7
+ require 'jdbc/postgres' # the JDBC driver, packaged as a gem
8
+ end
9
+
10
+ require File.expand_path(File.join(File.dirname(__FILE__), 'do_postgres_ext'))
11
+ require File.expand_path(File.join(File.dirname(__FILE__), 'do_postgres', 'version'))
12
+ require File.expand_path(File.join(File.dirname(__FILE__), 'do_postgres', 'transaction'))
13
+
14
+ if RUBY_PLATFORM =~ /java/
15
+ # Another way of loading the JDBC Class. This seems to be more reliable
16
+ # than Class.forName() within the data_objects.Connection Java class,
17
+ # which is currently not working as expected.
18
+ import 'org.postgresql.Driver'
19
+
20
+ module DataObjects
21
+ module Postgres
22
+ class Connection
23
+ def self.pool_size
24
+ 20
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -1,5 +1,5 @@
1
1
  module DataObjects
2
2
  module Postgres
3
- VERSION = "0.9.9"
3
+ VERSION = "0.9.10"
4
4
  end
5
5
  end
@@ -20,17 +20,17 @@ describe "DataObjects::Postgres::Connection" do
20
20
  include PostgresSpecHelpers
21
21
 
22
22
  it "should connect to the db" do
23
- connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
23
+ connection = DataObjects::Connection.new(DO_POSTGRES_SPEC_URI)
24
24
  connection.close
25
25
  end
26
26
 
27
- it "should be able to send querues asynchronuously in parallel" do
27
+ it "should be able to send queries asynchronously in parallel" do
28
28
  threads = []
29
29
 
30
30
  start = Time.now
31
31
  4.times do |i|
32
32
  threads << Thread.new do
33
- connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
33
+ connection = DataObjects::Connection.new(DO_POSTGRES_SPEC_URI)
34
34
  command = connection.create_command("SELECT pg_sleep(1)")
35
35
  result = command.execute_non_query
36
36
  end
@@ -61,7 +61,8 @@ describe "DataObjects::Postgres::Command" do
61
61
  it "should set types" do
62
62
  command = @connection.create_command("SELECT id, name FROM users")
63
63
  command.set_types [Integer, String]
64
- command.instance_variable_get("@field_types").should == [Integer, String]
64
+ # NOTE: should we really be testing for the presence of instance variables?
65
+ command.instance_variable_get(JRUBY ? "@types" : "@field_types").should == [Integer, String]
65
66
  end
66
67
 
67
68
  it "should execute a non query" do
@@ -177,6 +178,7 @@ describe "DataObjects::Postgres::Reader" do
177
178
  command = @connection.create_command("SELECT created_on FROM users WHERE created_on is not null LIMIT 1")
178
179
  reader = command.execute_reader
179
180
  reader.next!
181
+ # do_jdbc currently returning Time
180
182
  reader.values[0].should be_a_kind_of(Date)
181
183
  end
182
184
 
@@ -219,11 +221,53 @@ describe "DataObjects::Postgres::Reader" do
219
221
  reader.values.should == user.values
220
222
  end
221
223
 
222
- it "should typecast a time field" do
223
- pending "Time fields have no date information, and don't work with Time"
224
+ it "should not typecast a time field" do
224
225
  command = @connection.create_command("SELECT born_at FROM users LIMIT 1")
225
226
  reader = command.execute_reader
226
227
  reader.next!
227
- reader.values[0].should be_a_kind_of(Time)
228
+ reader.values[0].should be_a_kind_of(String)
228
229
  end
230
+
231
+ it "should typecast a timestamp field without time zone" do
232
+ command = @connection.create_command("SELECT created_at FROM users LIMIT 1")
233
+ reader = command.execute_reader
234
+ reader.next!
235
+ reader.values[0].should be_a_kind_of(DateTime)
236
+ end
237
+
238
+ it "should typecast a timestamp field with time zone" do
239
+ command = @connection.create_command("SELECT fired_at FROM users LIMIT 1")
240
+ reader = command.execute_reader
241
+ reader.next!
242
+ reader.values[0].should be_a_kind_of(DateTime)
243
+ end
244
+
245
+ it "should typecast a numeric field to a big decimal" do
246
+ command = @connection.create_command("SELECT amount FROM users LIMIT 1")
247
+ reader = command.execute_reader
248
+ reader.next!
249
+ reader.values[0].should be_a_kind_of(BigDecimal)
250
+ end
251
+
252
+ it "should return the current character set" do
253
+ connection = DataObjects::Connection.new("postgres://#{POSTGRES.user}:#{POSTGRES.pass}@#{POSTGRES.hostname}:#{POSTGRES.port}/#{POSTGRES.database}")
254
+ connection.character_set.should == "utf8"
255
+ connection.close
256
+ end
257
+
258
+ it "should support changing the character set" do
259
+ pending "JDBC API does not provide an easy way to get the current character set" if JRUBY
260
+ # current character set can be retrieved with the following query:
261
+ # "SHOW VARIABLES LIKE character_set_database"
262
+
263
+ connection = DataObjects::Connection.new("postgres://#{POSTGRES.user}:#{POSTGRES.pass}@#{POSTGRES.hostname}:#{POSTGRES.port}/#{POSTGRES.database}?charset=latin1")
264
+ # N.B. query parameter after forward slash causes problems with JDBC
265
+ connection.character_set.should == "latin1"
266
+ connection.close
267
+
268
+ connection = DataObjects::Connection.new("postgres://#{POSTGRES.user}:#{POSTGRES.pass}@#{POSTGRES.hostname}:#{POSTGRES.port}/#{POSTGRES.database}?charset=utf8")
269
+ connection.character_set.should == "utf8"
270
+ connection.close
271
+ end
272
+
229
273
  end
@@ -4,7 +4,7 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
4
4
  describe DataObjects::Postgres::Command do
5
5
 
6
6
  before(:each) do
7
- @connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
7
+ @connection = DataObjects::Connection.new(DO_POSTGRES_SPEC_URI)
8
8
  end
9
9
 
10
10
  after(:each) do
@@ -17,7 +17,7 @@ describe DataObjects::Postgres::Command do
17
17
  command = @connection.create_command("SELECT * FROM users")
18
18
  @mock_logger = mock('MockLogger', :level => 0)
19
19
  DataObjects::Postgres.should_receive(:logger).and_return(@mock_logger)
20
- @mock_logger.should_receive(:debug).with("SELECT * FROM users")
20
+ @mock_logger.should_receive(:debug).with(/\([\d.]+\) SELECT \* FROM users/)
21
21
  command.execute_reader
22
22
  end
23
23
 
@@ -35,7 +35,7 @@ describe DataObjects::Postgres::Command do
35
35
  command = @connection.create_command("INSERT INTO users (name) VALUES (?)")
36
36
  @mock_logger = mock('MockLogger', :level => 0)
37
37
  DataObjects::Postgres.should_receive(:logger).and_return(@mock_logger)
38
- @mock_logger.should_receive(:debug).with("INSERT INTO users (name) VALUES ('Blah')")
38
+ @mock_logger.should_receive(:debug).with(/\([\d.]+\) INSERT INTO users \(name\) VALUES \('Blah'\)/)
39
39
  command.execute_non_query('Blah')
40
40
  end
41
41
 
@@ -4,7 +4,7 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
4
4
  describe DataObjects::Postgres::Command do
5
5
 
6
6
  before(:each) do
7
- @connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
7
+ @connection = DataObjects::Connection.new(DO_POSTGRES_SPEC_URI)
8
8
  @command = @connection.create_command("INSERT INTO users (name) VALUES (?)")
9
9
  end
10
10
 
@@ -15,8 +15,8 @@ describe DataObjects::Postgres::Command do
15
15
  it "should properly quote a string" do
16
16
  @command.quote_string("O'Hare").should == "'O''Hare'"
17
17
  @command.quote_string("Willy O'Hare & Johnny O'Toole").should == "'Willy O''Hare & Johnny O''Toole'"
18
- @command.quote_string("Billy\\Bob").should == "'Billy\\\\Bob'"
19
- @command.quote_string("The\\Backslasher\\Rises\\Again").should == "'The\\\\Backslasher\\\\Rises\\\\Again'"
18
+ @command.quote_string("Billy\\Bob").should == "'Billy\\Bob'"
19
+ @command.quote_string("The\\Backslasher\\Rises\\Again").should == "'The\\Backslasher\\Rises\\Again'"
20
20
  @command.quote_string("Scott \"The Rage\" Bauer").should == "'Scott \"The Rage\" Bauer'"
21
21
  end
22
22
 
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  $TESTING=true
2
+ JRUBY = RUBY_PLATFORM =~ /java/
2
3
 
3
4
  require 'rubygems'
4
5
 
@@ -6,6 +7,7 @@ gem 'rspec', '>=1.1.3'
6
7
  require 'spec'
7
8
 
8
9
  require 'date'
10
+ require 'ostruct'
9
11
  require 'pathname'
10
12
  require 'fileutils'
11
13
  require 'bigdecimal'
@@ -15,6 +17,11 @@ require 'bigdecimal'
15
17
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data_objects', 'lib'))
16
18
  require 'data_objects'
17
19
 
20
+ if JRUBY
21
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'do_jdbc', 'lib'))
22
+ require 'do_jdbc'
23
+ end
24
+
18
25
  # put the pre-compiled extension in the path to be found
19
26
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
20
27
  require 'do_postgres'
@@ -24,10 +31,21 @@ FileUtils.mkdir_p(File.dirname(log_path))
24
31
 
25
32
  DataObjects::Postgres.logger = DataObjects::Logger.new(log_path, 0)
26
33
 
34
+ POSTGRES = OpenStruct.new
35
+ POSTGRES.user = ENV['DO_PG_USER'] || 'postgres'
36
+ POSTGRES.pass = ENV['DO_PG_PASS'] || ''
37
+ POSTGRES.host = ENV['DO_PG_HOST'] || '127.0.0.1'
38
+ POSTGRES.hostname = ENV['DO_PG_HOSTNAME'] || 'localhost'
39
+ POSTGRES.port = ENV['DO_PG_PORT'] || '5432'
40
+ POSTGRES.database = ENV['DO_PG_DATABASE'] || 'do_test'
41
+
42
+ DO_POSTGRES_SPEC_URI = Addressable::URI::parse(ENV["DO_PG_SPEC_URI"] ||
43
+ "postgres://#{POSTGRES.user}:#{POSTGRES.pass}@#{POSTGRES.hostname}:#{POSTGRES.port}/#{POSTGRES.database}")
44
+
27
45
  module PostgresSpecHelpers
28
46
 
29
47
  def ensure_users_table_and_return_connection
30
- connection = DataObjects::Connection.new("postgres://postgres@localhost/do_test")
48
+ connection = DataObjects::Connection.new(DO_POSTGRES_SPEC_URI)
31
49
  connection.create_command("DROP TABLE users").execute_non_query rescue nil
32
50
  connection.create_command("DROP TABLE companies").execute_non_query rescue nil
33
51
  connection.create_command(<<-EOF).execute_non_query
@@ -38,11 +56,12 @@ module PostgresSpecHelpers
38
56
  money double precision DEFAULT 1908.56,
39
57
  created_on date DEFAULT ('now'::text)::date,
40
58
  created_at timestamp without time zone DEFAULT now(),
41
- -- born_at time without time zone DEFAULT now(),
59
+ born_at time without time zone DEFAULT now(),
42
60
  fired_at timestamp with time zone DEFAULT now(),
61
+ amount numeric(10,2) DEFAULT 11.1,
43
62
  company_id integer DEFAULT 1
44
63
  )
45
- WITH (OIDS=FALSE);
64
+ WITHOUT OIDS;
46
65
  EOF
47
66
 
48
67
  connection.create_command(<<-EOF).execute_non_query
@@ -50,7 +69,7 @@ module PostgresSpecHelpers
50
69
  id serial NOT NULL,
51
70
  "name" character varying
52
71
  )
53
- WITH (OIDS=FALSE);
72
+ WITHOUT OIDS;
54
73
  EOF
55
74
 
56
75
  return connection
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: do_postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9
4
+ version: 0.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernerd Schaefer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-27 00:00:00 -08:00
12
+ date: 2009-01-04 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - "="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.9.9
23
+ version: 0.9.10
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hoe
@@ -38,7 +38,7 @@ email:
38
38
  executables: []
39
39
 
40
40
  extensions:
41
- - ext/extconf.rb
41
+ - ext/do_postgres_ext/extconf.rb
42
42
  extra_rdoc_files:
43
43
  - History.txt
44
44
  - Manifest.txt
@@ -53,9 +53,11 @@ files:
53
53
  - Rakefile
54
54
  - TODO
55
55
  - autobuild.rb
56
- - ext/do_postgres_ext.c
57
- - ext/extconf.rb
58
- - ext/type-oids.h
56
+ - buildfile
57
+ - ext-java/src/main/java/DoPostgresExtService.java
58
+ - ext-java/src/main/java/do_postgres/PostgresDriverDefinition.java
59
+ - ext/do_postgres_ext/do_postgres_ext.c
60
+ - ext/do_postgres_ext/extconf.rb
59
61
  - lib/do_postgres.rb
60
62
  - lib/do_postgres/transaction.rb
61
63
  - lib/do_postgres/version.rb
@@ -1,713 +0,0 @@
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
- #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
21
-
22
- #ifdef _WIN32
23
- #define do_int64 signed __int64
24
- #else
25
- #define do_int64 signed long long int
26
- #endif
27
-
28
- // To store rb_intern values
29
- static ID ID_NEW_DATE;
30
- static ID ID_LOGGER;
31
- static ID ID_DEBUG;
32
- static ID ID_LEVEL;
33
- static ID ID_TO_S;
34
- static ID ID_PARSE;
35
-
36
- static VALUE mDO;
37
- static VALUE cDO_Quoting;
38
- static VALUE cDO_Connection;
39
- static VALUE cDO_Command;
40
- static VALUE cDO_Result;
41
- static VALUE cDO_Reader;
42
-
43
- static VALUE rb_cDate;
44
- static VALUE rb_cDateTime;
45
- static VALUE rb_cRational;
46
- static VALUE rb_cBigDecimal;
47
- static VALUE rb_cCGI;
48
-
49
- static VALUE mPostgres;
50
- static VALUE cConnection;
51
- static VALUE cCommand;
52
- static VALUE cResult;
53
- static VALUE cReader;
54
-
55
- static VALUE ePostgresError;
56
-
57
- static void data_objects_debug(VALUE string) {
58
- VALUE logger = rb_funcall(mPostgres, ID_LOGGER, 0);
59
- int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
60
-
61
- if (0 == log_level) {
62
- rb_funcall(logger, ID_DEBUG, 1, string);
63
- }
64
- }
65
-
66
- /* ====== Time/Date Parsing Helper Functions ====== */
67
- static void reduce( do_int64 *numerator, do_int64 *denominator ) {
68
- do_int64 a, b, c;
69
- a = *numerator;
70
- b = *denominator;
71
- while ( a != 0 ) {
72
- c = a; a = b % a; b = c;
73
- }
74
- *numerator = *numerator / b;
75
- *denominator = *denominator / b;
76
- }
77
-
78
- // Generate the date integer which Date.civil_to_jd returns
79
- static int jd_from_date(int year, int month, int day) {
80
- int a, b;
81
- if ( month <= 2 ) {
82
- year -= 1;
83
- month += 12;
84
- }
85
- a = year / 100;
86
- b = 2 - a + (a / 4);
87
- return floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524;
88
- }
89
-
90
- static VALUE parse_date(const char *date) {
91
- int year, month, day;
92
- int jd, ajd;
93
- VALUE rational;
94
-
95
- sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
96
-
97
- jd = jd_from_date(year, month, day);
98
-
99
- // Math from Date.jd_to_ajd
100
- ajd = jd * 2 - 1;
101
- rational = rb_funcall(rb_cRational, rb_intern("new!"), 2, INT2NUM(ajd), INT2NUM(2));
102
-
103
- return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
104
- }
105
-
106
- // Creates a Rational for use as a Timezone offset to be passed to DateTime.new!
107
- static VALUE seconds_to_offset(do_int64 num) {
108
- do_int64 den = 86400;
109
- reduce(&num, &den);
110
- return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
111
- }
112
-
113
- static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
114
- do_int64 seconds = 0;
115
-
116
- seconds += hour_offset * 3600;
117
- seconds += minute_offset * 60;
118
-
119
- return seconds_to_offset(seconds);
120
- }
121
-
122
- static VALUE parse_date_time(const char *date) {
123
- VALUE ajd, offset;
124
-
125
- int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
126
- int jd;
127
- do_int64 num, den;
128
-
129
- long int gmt_offset;
130
- int is_dst;
131
-
132
- time_t rawtime;
133
- struct tm * timeinfo;
134
-
135
- int tokens_read, max_tokens;
136
-
137
- if (0 != strchr(date, '.')) {
138
- // This is a datetime with sub-second precision
139
- tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
140
- max_tokens = 9;
141
- } else {
142
- // This is a datetime second precision
143
- tokens_read = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
144
- max_tokens = 8;
145
- }
146
-
147
- if (max_tokens == tokens_read) {
148
- // We read the Date, Time, and Timezone info
149
- minute_offset *= hour_offset < 0 ? -1 : 1;
150
- } else if ((max_tokens - 1) == tokens_read) {
151
- // We read the Date and Time, but no Minute Offset
152
- minute_offset = 0;
153
- } else if (tokens_read == 3) {
154
- return parse_date(date);
155
- } else if (tokens_read >= (max_tokens - 3)) {
156
- // We read the Date and Time, default to the current locale's offset
157
-
158
- // Get localtime
159
- time(&rawtime);
160
- timeinfo = localtime(&rawtime);
161
-
162
- is_dst = timeinfo->tm_isdst * 3600;
163
-
164
- // Reset to GM Time
165
- timeinfo = gmtime(&rawtime);
166
-
167
- gmt_offset = mktime(timeinfo) - rawtime;
168
-
169
- if ( is_dst > 0 )
170
- gmt_offset -= is_dst;
171
-
172
- hour_offset = -(gmt_offset / 3600);
173
- minute_offset = -(gmt_offset % 3600 / 60);
174
-
175
- } else {
176
- // Something went terribly wrong
177
- rb_raise(ePostgresError, "Couldn't parse date: %s", date);
178
- }
179
-
180
- jd = jd_from_date(year, month, day);
181
-
182
- // Generate ajd with fractional days for the time
183
- // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
184
- num = (hour * 1440) + (min * 24);
185
-
186
- // Modify the numerator so when we apply the timezone everything works out
187
- num -= (hour_offset * 1440) + (minute_offset * 24);
188
-
189
- den = (24 * 1440);
190
- reduce(&num, &den);
191
-
192
- num = (num * 86400) + (sec * den);
193
- den = den * 86400;
194
- reduce(&num, &den);
195
-
196
- num = (jd * den) + num;
197
-
198
- num = num * 2;
199
- num = num - den;
200
- den = den * 2;
201
-
202
- reduce(&num, &den);
203
-
204
- ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
205
- offset = timezone_to_offset(hour_offset, minute_offset);
206
-
207
- return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
208
- }
209
-
210
- static VALUE parse_time(char *date) {
211
-
212
- int year, month, day, hour, min, sec, usec;
213
- char subsec[7];
214
-
215
- if (0 != strchr(date, '.')) {
216
- // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
217
- sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
218
- usec = atoi(subsec);
219
- usec *= pow(10, (6 - strlen(subsec)));
220
- } else {
221
- sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
222
- usec = 0;
223
- }
224
-
225
- return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
226
- }
227
-
228
- /* ===== Typecasting Functions ===== */
229
-
230
- static VALUE infer_ruby_type(Oid type) {
231
- char *ruby_type = "String";
232
- switch(type) {
233
- case INT2OID:
234
- case INT4OID:
235
- case INT8OID: {
236
- ruby_type = "Fixnum";
237
- break;
238
- }
239
- case FLOAT4OID:
240
- case FLOAT8OID: {
241
- ruby_type = "Float";
242
- break;
243
- }
244
- case BOOLOID: {
245
- ruby_type = "TrueClass";
246
- break;
247
- }
248
- case TIMESTAMPOID: {
249
- ruby_type = "DateTime";
250
- break;
251
- }
252
- // case TIMESTAMPTZOID
253
- // case TIMETZOID
254
- case TIMEOID: {
255
- ruby_type = "Time";
256
- break;
257
- }
258
- case DATEOID: {
259
- ruby_type = "Date";
260
- break;
261
- }
262
- }
263
- return rb_str_new2(ruby_type);
264
- }
265
-
266
- static VALUE typecast(char *value, char *type) {
267
-
268
- if ( strcmp(type, "Class") == 0) {
269
- return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
270
- } else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
271
- return rb_cstr2inum(value, 10);
272
- } else if ( strcmp(type, "Float") == 0 ) {
273
- return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
274
- } else if (0 == strcmp("BigDecimal", type) ) {
275
- return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value));
276
- } else if ( strcmp(type, "TrueClass") == 0 ) {
277
- return *value == 't' ? Qtrue : Qfalse;
278
- } else if ( strcmp(type, "Date") == 0 ) {
279
- return parse_date(value);
280
- } else if ( strcmp(type, "DateTime") == 0 ) {
281
- return parse_date_time(value);
282
- } else if ( strcmp(type, "Time") == 0 ) {
283
- return parse_time(value);
284
- } else {
285
- return TAINTED_STRING(value);
286
- }
287
-
288
- }
289
-
290
- // Pull an option out of a querystring-formmated option list using CGI::parse
291
- static char * get_uri_option(VALUE querystring, char * key) {
292
- VALUE options_hash, option_value;
293
-
294
- char * value = NULL;
295
-
296
- // Ensure that we're dealing with a string
297
- querystring = rb_funcall(querystring, ID_TO_S, 0);
298
-
299
- options_hash = rb_funcall(rb_cCGI, ID_PARSE, 1, querystring);
300
-
301
- // TODO: rb_hash_aref always returns an array?
302
- option_value = rb_ary_entry(rb_hash_aref(options_hash, RUBY_STRING(key)), 0);
303
-
304
- if (Qnil != option_value) {
305
- value = StringValuePtr(option_value);
306
- }
307
-
308
- return value;
309
- }
310
-
311
- /* ====== Public API ======= */
312
-
313
-
314
-
315
- static VALUE cConnection_dispose(VALUE self) {
316
- PGconn *db = DATA_PTR(rb_iv_get(self, "@connection"));
317
- PQfinish(db);
318
- return Qtrue;
319
- }
320
-
321
- static VALUE cCommand_set_types(VALUE self, VALUE array) {
322
- rb_iv_set(self, "@field_types", array);
323
- return array;
324
- }
325
-
326
- static VALUE build_query_from_args(VALUE klass, int count, VALUE *args[]) {
327
- VALUE query = rb_iv_get(klass, "@text");
328
- if ( count > 0 ) {
329
- int i;
330
- VALUE array = rb_ary_new();
331
- for ( i = 0; i < count; i++) {
332
- rb_ary_push(array, (VALUE)args[i]);
333
- }
334
- query = rb_funcall(klass, ID_ESCAPE, 1, array);
335
- }
336
- return query;
337
- }
338
-
339
- static VALUE cCommand_quote_string(VALUE self, VALUE string) {
340
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
341
-
342
- size_t length;
343
- const char *source = StringValuePtr(string);
344
- char *escaped;
345
- int quoted_length = 0;
346
- VALUE result;
347
-
348
- length = strlen(source);
349
-
350
- // Allocate space for the escaped version of 'string'
351
- // http://www.postgresql.org/docs/8.3/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING
352
- escaped = (char *)calloc(strlen(source) * 2 + 3, sizeof(char));
353
-
354
- // Escape 'source' using the current charset in use on the conection 'db'
355
- quoted_length = PQescapeStringConn(db, escaped + 1, source, length, NULL);
356
-
357
- // Wrap the escaped string in single-quotes, this is DO's convention
358
- escaped[quoted_length + 1] = escaped[0] = '\'';
359
-
360
- result = rb_str_new(escaped, quoted_length + 2);
361
- free(escaped);
362
- return result;
363
- }
364
-
365
- static PGresult* cCommand_execute_async(PGconn *db, VALUE query) {
366
- int socket_fd;
367
- int retval;
368
- fd_set rset;
369
- PGresult *response;
370
- char* str = StringValuePtr(query);
371
- while ((response = PQgetResult(db)) != NULL) {
372
- PQclear(response);
373
- }
374
-
375
- retval = PQsendQuery(db, str);
376
- data_objects_debug(query);
377
-
378
- if (!retval) {
379
- rb_raise(ePostgresError, PQerrorMessage(db));
380
- }
381
-
382
- socket_fd = PQsocket(db);
383
-
384
- for(;;) {
385
- FD_ZERO(&rset);
386
- FD_SET(socket_fd, &rset);
387
- retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
388
- if (retval < 0) {
389
- rb_sys_fail(0);
390
- }
391
-
392
- if (retval == 0) {
393
- continue;
394
- }
395
-
396
- if (PQconsumeInput(db) == 0) {
397
- rb_raise(ePostgresError, PQerrorMessage(db));
398
- }
399
-
400
- if (PQisBusy(db) == 0) {
401
- break;
402
- }
403
- }
404
-
405
- return PQgetResult(db);
406
- }
407
-
408
- static VALUE cConnection_initialize(VALUE self, VALUE uri) {
409
- PGresult *result = NULL;
410
- VALUE r_host, r_user, r_password, r_path, r_port, r_options, r_query;
411
- char *host = NULL, *user = NULL, *password = NULL, *path;
412
- char *database = "", *port = "5432";
413
- char *search_path = NULL;
414
- char *search_path_query = NULL;
415
- PGconn *db;
416
-
417
- r_host = rb_funcall(uri, rb_intern("host"), 0);
418
- if ( Qnil != r_host && "localhost" != StringValuePtr(r_host) ) {
419
- host = StringValuePtr(r_host);
420
- }
421
-
422
- r_user = rb_funcall(uri, rb_intern("user"), 0);
423
- if (Qnil != r_user) {
424
- user = StringValuePtr(r_user);
425
- }
426
-
427
- r_password = rb_funcall(uri, rb_intern("password"), 0);
428
- if (Qnil != r_password) {
429
- password = StringValuePtr(r_password);
430
- }
431
-
432
- r_path = rb_funcall(uri, rb_intern("path"), 0);
433
- path = StringValuePtr(r_path);
434
- if (Qnil != r_path) {
435
- database = strtok(path, "/");
436
- }
437
-
438
- if (NULL == database || 0 == strlen(database)) {
439
- rb_raise(ePostgresError, "Database must be specified");
440
- }
441
-
442
- r_port = rb_funcall(uri, rb_intern("port"), 0);
443
- if (Qnil != r_port) {
444
- r_port = rb_funcall(r_port, rb_intern("to_s"), 0);
445
- port = StringValuePtr(r_port);
446
- }
447
-
448
- // Pull the querystring off the URI
449
- r_options = rb_funcall(uri, rb_intern("query"), 0);
450
-
451
- search_path = get_uri_option(r_options, "search_path");
452
-
453
- db = PQsetdbLogin(
454
- host,
455
- port,
456
- NULL,
457
- NULL,
458
- database,
459
- user,
460
- password
461
- );
462
-
463
- if ( PQstatus(db) == CONNECTION_BAD ) {
464
- rb_raise(ePostgresError, PQerrorMessage(db));
465
- }
466
-
467
- if (search_path != NULL) {
468
- search_path_query = (char *) malloc(256 * sizeof(char));
469
- memset(search_path_query, 0, 256);
470
- sprintf(search_path_query, "set search_path to %s;", search_path);
471
- r_query = rb_str_new(search_path_query, strlen(search_path_query) + 1);
472
- result = cCommand_execute_async(db, r_query);
473
- // printf("status = %s\n", PQresStatus(PQresultStatus(result)));
474
- // printf("result msg: %s\n", PQresultErrorMessage(result));
475
-
476
- if (PQresultStatus(result) != PGRES_COMMAND_OK) {
477
- free(search_path_query);
478
- rb_raise(ePostgresError, PQresultErrorMessage(result));
479
- }
480
-
481
- free(search_path_query);
482
- }
483
-
484
- rb_iv_set(self, "@uri", uri);
485
- rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
486
-
487
-
488
- return Qtrue;
489
- }
490
-
491
- static VALUE cCommand_execute_non_query(int argc, VALUE *argv[], VALUE self) {
492
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
493
- PGresult *response;
494
- int status;
495
-
496
- int affected_rows;
497
- int insert_id;
498
-
499
- VALUE query = build_query_from_args(self, argc, argv);
500
-
501
- response = cCommand_execute_async(db, query);
502
-
503
- status = PQresultStatus(response);
504
-
505
- if ( status == PGRES_TUPLES_OK ) {
506
- insert_id = atoi(PQgetvalue(response, 0, 0));
507
- affected_rows = 1;
508
- }
509
- else if ( status == PGRES_COMMAND_OK ) {
510
- insert_id = 0;
511
- affected_rows = atoi(PQcmdTuples(response));
512
- }
513
- else {
514
- char *message = PQresultErrorMessage(response);
515
- PQclear(response);
516
- rb_raise(ePostgresError, message);
517
- }
518
-
519
- PQclear(response);
520
-
521
- return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(insert_id));
522
- }
523
-
524
- static VALUE cCommand_execute_reader(int argc, VALUE *argv[], VALUE self) {
525
- VALUE reader, query;
526
- VALUE field_names, field_types;
527
-
528
- int i;
529
- int field_count;
530
- int infer_types = 0;
531
-
532
- PGconn *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
533
- PGresult *response;
534
-
535
- query = build_query_from_args(self, argc, argv);
536
-
537
- response = cCommand_execute_async(db, query);
538
-
539
- if ( PQresultStatus(response) != PGRES_TUPLES_OK ) {
540
- char *message = PQresultErrorMessage(response);
541
- PQclear(response);
542
- rb_raise(ePostgresError, message);
543
- }
544
-
545
- field_count = PQnfields(response);
546
-
547
- reader = rb_funcall(cReader, ID_NEW, 0);
548
- rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
549
- rb_iv_set(reader, "@field_count", INT2NUM(field_count));
550
- rb_iv_set(reader, "@row_count", INT2NUM(PQntuples(response)));
551
-
552
- field_names = rb_ary_new();
553
- field_types = rb_iv_get(self, "@field_types");
554
-
555
- if ( field_types == Qnil || RARRAY(field_types)->len == 0 ) {
556
- field_types = rb_ary_new();
557
- infer_types = 1;
558
- }
559
-
560
- for ( i = 0; i < field_count; i++ ) {
561
- rb_ary_push(field_names, rb_str_new2(PQfname(response, i)));
562
- if ( infer_types == 1 ) {
563
- rb_ary_push(field_types, infer_ruby_type(PQftype(response, i)));
564
- }
565
- }
566
-
567
- rb_iv_set(reader, "@position", INT2NUM(0));
568
- rb_iv_set(reader, "@fields", field_names);
569
- rb_iv_set(reader, "@field_types", field_types);
570
-
571
- return reader;
572
- }
573
-
574
- static VALUE cReader_close(VALUE self) {
575
- VALUE reader_container = rb_iv_get(self, "@reader");
576
-
577
- PGresult *reader;
578
-
579
- if (Qnil == reader_container)
580
- return Qfalse;
581
-
582
- reader = DATA_PTR(reader_container);
583
-
584
- if (NULL == reader)
585
- return Qfalse;
586
-
587
- PQclear(reader);
588
- rb_iv_set(self, "@reader", Qnil);
589
- return Qtrue;
590
- }
591
-
592
- static VALUE cReader_next(VALUE self) {
593
- PGresult *reader = DATA_PTR(rb_iv_get(self, "@reader"));
594
-
595
- int field_count;
596
- int row_count;
597
- int i;
598
- int position;
599
-
600
- char *type = "";
601
-
602
- VALUE array = rb_ary_new();
603
- VALUE field_types, ruby_type;
604
- VALUE value;
605
-
606
- row_count = NUM2INT(rb_iv_get(self, "@row_count"));
607
- field_count = NUM2INT(rb_iv_get(self, "@field_count"));
608
- field_types = rb_iv_get(self, "@field_types");
609
- position = NUM2INT(rb_iv_get(self, "@position"));
610
-
611
- if ( position > (row_count-1) ) {
612
- return Qnil;
613
- }
614
-
615
- for ( i = 0; i < field_count; i++ ) {
616
- ruby_type = RARRAY(field_types)->ptr[i];
617
-
618
- if ( TYPE(ruby_type) == T_STRING ) {
619
- type = RSTRING(ruby_type)->ptr;
620
- }
621
- else {
622
- type = rb_class2name(ruby_type);
623
- }
624
-
625
- // Always return nil if the value returned from Postgres is null
626
- if (!PQgetisnull(reader, position, i)) {
627
- value = typecast(PQgetvalue(reader, position, i), type);
628
- } else {
629
- value = Qnil;
630
- }
631
-
632
- rb_ary_push(array, value);
633
- }
634
-
635
- rb_iv_set(self, "@values", array);
636
- rb_iv_set(self, "@position", INT2NUM(position+1));
637
-
638
- return Qtrue;
639
- }
640
-
641
- static VALUE cReader_values(VALUE self) {
642
-
643
- int position = rb_iv_get(self, "@position");
644
- int row_count = NUM2INT(rb_iv_get(self, "@row_count"));
645
-
646
- if ( position == Qnil || NUM2INT(position) > row_count ) {
647
- rb_raise(ePostgresError, "Reader not initialized");
648
- }
649
- else {
650
- return rb_iv_get(self, "@values");
651
- }
652
- }
653
-
654
- static VALUE cReader_fields(VALUE self) {
655
- return rb_iv_get(self, "@fields");
656
- }
657
-
658
- void Init_do_postgres_ext() {
659
- rb_require("rubygems");
660
- rb_require("date");
661
- rb_require("bigdecimal");
662
- rb_require("cgi");
663
-
664
- // Get references classes needed for Date/Time parsing
665
- rb_cDate = CONST_GET(rb_mKernel, "Date");
666
- rb_cDateTime = CONST_GET(rb_mKernel, "DateTime");
667
- rb_cTime = CONST_GET(rb_mKernel, "Time");
668
- rb_cRational = CONST_GET(rb_mKernel, "Rational");
669
- rb_cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
670
-
671
- rb_cCGI = RUBY_CLASS("CGI");
672
-
673
- rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
674
-
675
- ID_NEW_DATE = RUBY_VERSION_CODE < 186 ? rb_intern("new0") : rb_intern("new!");
676
- ID_LOGGER = rb_intern("logger");
677
- ID_DEBUG = rb_intern("debug");
678
- ID_LEVEL = rb_intern("level");
679
- ID_TO_S = rb_intern("to_s");
680
- ID_PARSE = rb_intern("parse");
681
-
682
- // Get references to the DataObjects module and its classes
683
- mDO = CONST_GET(rb_mKernel, "DataObjects");
684
- cDO_Quoting = CONST_GET(mDO, "Quoting");
685
- cDO_Connection = CONST_GET(mDO, "Connection");
686
- cDO_Command = CONST_GET(mDO, "Command");
687
- cDO_Result = CONST_GET(mDO, "Result");
688
- cDO_Reader = CONST_GET(mDO, "Reader");
689
-
690
- mPostgres = rb_define_module_under(mDO, "Postgres");
691
- ePostgresError = rb_define_class("PostgresError", rb_eStandardError);
692
-
693
- cConnection = POSTGRES_CLASS("Connection", cDO_Connection);
694
- rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
695
- rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
696
-
697
- cCommand = POSTGRES_CLASS("Command", cDO_Command);
698
- rb_include_module(cCommand, cDO_Quoting);
699
- rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
700
- rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
701
- rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
702
- rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
703
-
704
- cResult = POSTGRES_CLASS("Result", cDO_Result);
705
-
706
- cReader = POSTGRES_CLASS("Reader", cDO_Reader);
707
- rb_define_method(cReader, "close", cReader_close, 0);
708
- rb_define_method(cReader, "next!", cReader_next, 0);
709
- rb_define_method(cReader, "values", cReader_values, 0);
710
- rb_define_method(cReader, "fields", cReader_fields, 0);
711
-
712
- }
713
-