do_postgres 0.9.9 → 0.9.10

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.
@@ -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
-