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.
- data/.gitignore +0 -1
- data/Manifest.txt +5 -3
- data/Rakefile +38 -15
- data/buildfile +26 -0
- data/ext-java/src/main/java/DoPostgresExtService.java +23 -0
- data/ext-java/src/main/java/do_postgres/PostgresDriverDefinition.java +12 -0
- data/ext/do_postgres_ext/do_postgres_ext.c +788 -0
- data/ext/{extconf.rb → do_postgres_ext/extconf.rb} +6 -3
- data/lib/do_postgres.rb +28 -3
- data/lib/do_postgres/version.rb +1 -1
- data/spec/integration/do_postgres_spec.rb +51 -7
- data/spec/integration/logging_spec.rb +3 -3
- data/spec/integration/quoting_spec.rb +3 -3
- data/spec/spec_helper.rb +23 -4
- metadata +9 -7
- data/ext/do_postgres_ext.c +0 -713
- data/ext/type-oids.h +0 -76
@@ -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}
|
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
|
-
|
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('
|
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
|
-
|
5
|
-
require '
|
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
|
data/lib/do_postgres/version.rb
CHANGED
@@ -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(
|
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
|
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(
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
19
|
-
@command.quote_string("The\\Backslasher\\Rises\\Again").should == "'The
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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:
|
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.
|
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
|
-
-
|
57
|
-
- ext/
|
58
|
-
- ext/
|
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
|
data/ext/do_postgres_ext.c
DELETED
@@ -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
|
-
|