do_postgres 0.2.4 → 0.9.2

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/ext/extconf.rb CHANGED
@@ -1,7 +1,18 @@
1
- ENV["RC_ARCHS"] = `uname -m`.chomp if `uname -sr` =~ /^Darwin/
1
+ if RUBY_PLATFORM =~ /darwin/
2
+ ENV["RC_ARCHS"] = `uname -m`.chomp if `uname -sr` =~ /^Darwin/
3
+
4
+ # On PowerPC the defaults are fine
5
+ ENV["RC_ARCHS"] = '' if `uname -m` =~ /^Power Macintosh/
6
+ end
2
7
 
3
8
  require 'mkmf'
4
9
 
10
+ # be polite: you can't force existance of uname functionality on all
11
+ # platforms.
12
+ if RUBY_PLATFORM =~ /darwin/
13
+ ENV["RC_ARCHS"] = `uname -m`.chomp if `uname -sr` =~ /^Darwin/
14
+ end
15
+
5
16
  def config_value(type)
6
17
  ENV["POSTGRES_#{type.upcase}"] || pg_config(type)
7
18
  end
@@ -11,7 +22,8 @@ def pg_config(type)
11
22
  end
12
23
 
13
24
  def have_build_env
14
- have_library('pq') && have_header('libpq-fe.h') && have_header('libpq/libpq-fs.h')
25
+ (have_library('pq') || have_library('libpq')) &&
26
+ have_header('libpq-fe.h') && have_header('libpq/libpq-fs.h')
15
27
  end
16
28
 
17
29
  dir_config('pgsql', config_value('include'), config_value('lib'))
@@ -23,9 +35,9 @@ compat_functions = %w(PQescapeString PQexecParams)
23
35
  if have_build_env
24
36
  required_libraries.each(&method(:have_library))
25
37
  desired_functions.each(&method(:have_func))
26
- $CFLAGS << ' -Wall '
27
- dir_config("postgres_c")
28
- create_makefile("postgres_c")
38
+ $CFLAGS << ' -Wall ' unless RUBY_PLATFORM =~ /mswin/
39
+ create_makefile("do_postgres_ext")
29
40
  else
30
41
  puts 'Could not find PostgreSQL build environment (libraries & headers): Makefile not created'
31
- end
42
+ exit(1)
43
+ end
data/ext/type-oids.h ADDED
@@ -0,0 +1,76 @@
1
+ #define BOOLOID 16
2
+ #define BYTEAOID 17
3
+ #define CHAROID 18
4
+ #define NAMEOID 19
5
+ #define INT8OID 20
6
+ #define INT2OID 21
7
+ #define INT2VECTOROID 22
8
+ #define INT4OID 23
9
+ #define REGPROCOID 24
10
+ #define TEXTOID 25
11
+ #define OIDOID 26
12
+ #define TIDOID 27
13
+ #define XIDOID 28
14
+ #define CIDOID 29
15
+ #define OIDVECTOROID 30
16
+ #define PG_TYPE_RELTYPE_OID 71
17
+ #define PG_ATTRIBUTE_RELTYPE_OID 75
18
+ #define PG_PROC_RELTYPE_OID 81
19
+ #define PG_CLASS_RELTYPE_OID 83
20
+ #define XMLOID 142
21
+ #define POINTOID 600
22
+ #define LSEGOID 601
23
+ #define PATHOID 602
24
+ #define BOXOID 603
25
+ #define POLYGONOID 604
26
+ #define LINEOID 628
27
+ #define FLOAT4OID 700
28
+ #define FLOAT8OID 701
29
+ #define ABSTIMEOID 702
30
+ #define RELTIMEOID 703
31
+ #define TINTERVALOID 704
32
+ #define UNKNOWNOID 705
33
+ #define CIRCLEOID 718
34
+ #define CASHOID 790
35
+ #define MACADDROID 829
36
+ #define INETOID 869
37
+ #define CIDROID 650
38
+ #define INT4ARRAYOID 1007
39
+ #define FLOAT4ARRAYOID 1021
40
+ #define ACLITEMOID 1033
41
+ #define CSTRINGARRAYOID 1263
42
+ #define BPCHAROID 1042
43
+ #define VARCHAROID 1043
44
+ #define DATEOID 1082
45
+ #define TIMEOID 1083
46
+ #define TIMESTAMPOID 1114
47
+ #define TIMESTAMPTZOID 1184
48
+ #define INTERVALOID 1186
49
+ #define TIMETZOID 1266
50
+ #define BITOID 1560
51
+ #define VARBITOID 1562
52
+ #define NUMERICOID 1700
53
+ #define REFCURSOROID 1790
54
+ #define REGPROCEDUREOID 2202
55
+ #define REGOPEROID 2203
56
+ #define REGOPERATOROID 2204
57
+ #define REGCLASSOID 2205
58
+ #define REGTYPEOID 2206
59
+ #define REGTYPEARRAYOID 2211
60
+ #define TSVECTOROID 3614
61
+ #define GTSVECTOROID 3642
62
+ #define TSQUERYOID 3615
63
+ #define REGCONFIGOID 3734
64
+ #define REGDICTIONARYOID 3769
65
+ #define RECORDOID 2249
66
+ #define CSTRINGOID 2275
67
+ #define ANYOID 2276
68
+ #define ANYARRAYOID 2277
69
+ #define VOIDOID 2278
70
+ #define TRIGGEROID 2279
71
+ #define LANGUAGE_HANDLEROID 2280
72
+ #define INTERNALOID 2281
73
+ #define OPAQUEOID 2282
74
+ #define ANYELEMENTOID 2283
75
+ #define ANYNONARRAYOID 2776
76
+ #define ANYENUMOID 3500
data/lib/do_postgres.rb CHANGED
@@ -1,246 +1,5 @@
1
- require 'postgres_c'
2
- require 'data_objects'
3
-
4
- module DataObject
5
- module Postgres
6
- TYPES = Hash[*Postgres_c.constants.select {|x| x.include?("OID")}.map {|x| [Postgres_c.const_get(x), x.gsub(/_?OID$/, "")]}.flatten]
7
- QUOTE_STRING = "'"
8
- QUOTE_COLUMN = "\""
9
-
10
- class Connection < DataObject::Connection
11
- attr_reader :db
12
-
13
- def initialize(connection_string)
14
- @state = STATE_CLOSED
15
- @connection_string = connection_string
16
- end
17
-
18
- def open
19
- @db = Postgres_c.PQconnectdb(@connection_string)
20
- if Postgres_c.PQstatus(@db) != Postgres_c::CONNECTION_OK
21
- raise ConnectionFailed, "Unable to connect to database with provided connection string. \n#{Postgres_c.PQerrorMessage(@db)}"
22
- end
23
- @state = STATE_OPEN
24
- true
25
- end
26
-
27
- def close
28
- Postgres_c.PQfinish(@db)
29
- @state = STATE_CLOSED
30
- true
31
- end
32
-
33
- def create_command(text)
34
- Command.new(self, text)
35
- end
36
-
37
- def begin_transaction
38
- Transaction.new(self)
39
- end
40
-
41
- end
42
-
43
- class Transaction < Connection
44
-
45
- attr_reader :connection
46
-
47
- def initialize(conn)
48
- @connection = conn
49
- exec_sql("BEGIN")
50
- end
51
-
52
- # Commits the transaction
53
- def commit
54
- exec_sql("COMMIT")
55
- end
56
1
 
57
- # Rolls back the transaction
58
- def rollback(savepoint = nil)
59
- exec_sql("ROLLBACK#{savepoint ? " TO " + savepoint : ""}")
60
- end
61
-
62
- # Creates a savepoint for rolling back later (not commonly supported)
63
- def save(name)
64
- exec_sql("SAVEPOINT #{name}")
65
- end
66
-
67
- def create_command(*args)
68
- @connection.create_command(*args)
69
- end
70
-
71
- protected
72
-
73
- def exec_sql(sql)
74
- @connection.logger.debug(sql)
75
- Postgres_c.PQexec(@connection.db, "COMMIT")
76
- end
77
-
78
- end
79
-
80
- class Reader < DataObject::Reader
81
-
82
- def initialize(db, reader)
83
- @reader = reader
84
- case Postgres_c.PQresultStatus(reader)
85
- when Postgres_c::PGRES_COMMAND_OK
86
- @records_affected = Postgres_c.PQcmdTuples(reader).to_i
87
- close
88
- when Postgres_c::PGRES_TUPLES_OK
89
- @fields, @field_types = [], []
90
- @field_count = Postgres_c.PQnfields(@reader)
91
- i = 0
92
- while(i < @field_count)
93
- @field_types.push(Postgres_c.PQftype(@reader, i))
94
- @fields.push(Postgres_c.PQfname(@reader, i))
95
- i += 1
96
- end
97
- @rows = Postgres_c.PQntuples(@reader)
98
- @has_rows = @rows > 0
99
- @cursor = 0
100
- @state = STATE_OPEN
101
- end
102
- end
103
-
104
- def real_close
105
- Postgres_c.PQclear(@reader)
106
- end
107
-
108
- def data_type_name(col)
109
-
110
- end
111
-
112
- def name(col)
113
- super
114
- Postgres_c.PQfname(@reader, col)
115
- end
116
-
117
- def get_index(name)
118
- super
119
- @fields.index(name)
120
- end
121
-
122
- def null?(idx)
123
- super
124
- Postgres_c.PQgetisnull(@reader, @cursor, idx) != 0
125
- end
126
-
127
- def item(idx)
128
- super
129
- val = Postgres_c.PQgetvalue(@reader, @cursor, idx)
130
- typecast(val, @field_types[idx])
131
- end
132
-
133
- def each
134
- return unless has_rows?
135
-
136
- while(true) do
137
- yield
138
- break unless self.next
139
- end
140
- end
141
-
142
- def next
143
- super
144
- if @cursor >= @rows - 1
145
- @cursor = nil
146
- close
147
- return nil
148
- end
149
- @cursor += 1
150
- true
151
- end
152
-
153
- protected
154
- def native_type(col)
155
- TYPES[Postgres_c.PQftype(@reader, col)]
156
- end
157
-
158
- def typecast(val, field_type)
159
- return nil if val.nil? || val == "NULL"
160
- case TYPES[field_type]
161
- when "BOOL"
162
- val == "t"
163
- when "INT2", "INT4", "OID", "TID", "XID", "CID", "INT8"
164
- val == '' ? nil : val.to_i
165
- when "FLOAT4", "FLOAT8", "NUMERIC", "CASH"
166
- val.to_f
167
- when "TIMESTAMP", "TIMETZ", "TIMESTAMPTZ"
168
- DateTime.parse(val) rescue nil
169
- when "TIME"
170
- DateTime.parse(val).to_time rescue nil
171
- when "DATE"
172
- Date.parse(val) rescue nil
173
- else
174
- val
175
- end
176
- end
177
-
178
- end
179
-
180
- class ResultData < DataObject::ResultData
181
-
182
- def last_insert_row
183
- @last_insert_row ||= begin
184
- reader = @conn.create_command("select lastval()").execute_reader
185
- reader.item(0).to_i
186
- rescue QueryError
187
- raise NoInsertError, "You tried to get the last inserted row without doing an insert\n#{Postgres_c.PQerrorMessage(@conn.db)}"
188
- ensure
189
- reader and reader.close
190
- end
191
- end
192
-
193
- end
194
-
195
- class Command < DataObject::Command
196
-
197
- def execute_reader(*args)
198
- super
199
- sql = escape_sql(args)
200
- @connection.logger.debug { sql }
201
- ptr = Postgres_c.PQexec(@connection.db, sql)
202
- unless [Postgres_c::PGRES_COMMAND_OK, Postgres_c::PGRES_TUPLES_OK].include?(Postgres_c.PQresultStatus(ptr))
203
- raise QueryError, "Your query failed.\n#{Postgres_c.PQerrorMessage(@connection.db)}QUERY: \"#{sql}\""
204
- else
205
- reader = Reader.new(@connection.db, ptr)
206
- if block_given?
207
- return_value = yield(reader)
208
- reader.close
209
- return_value
210
- else
211
- reader
212
- end
213
- end
214
- end
215
-
216
- def execute_non_query(*args)
217
- super
218
- sql = escape_sql(args)
219
- @connection.logger.debug { sql }
220
- results = Postgres_c.PQexec(@connection.db, sql)
221
- status = Postgres_c.PQresultStatus(results)
222
- if status == Postgres_c::PGRES_TUPLES_OK
223
- Postgres_c.PQclear(results)
224
- raise QueryError, "Your query failed or you tried to execute a SELECT query through execute_non_reader\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{sql}\""
225
- elsif status != Postgres_c::PGRES_COMMAND_OK
226
- Postgres_c.PQclear(results)
227
- raise QueryError, "Your query failed.\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{sql}\""
228
- end
229
- rows_affected = Postgres_c.PQcmdTuples(results).to_i
230
- Postgres_c.PQclear(results)
231
- ResultData.new(@connection, rows_affected)
232
- end
233
-
234
- def quote_string(value)
235
- if value =~ /[\x00-\x80]/
236
- raise "String cannot contain $Text$ in the body" if value.include?("$Text$")
237
- "$Text$#{value}$Text$"
238
- else
239
- super
240
- end
241
- end
242
-
243
- end
244
-
245
- end
246
- end
2
+ require 'rubygems'
3
+ require 'data_objects'
4
+ require 'do_postgres_ext'
5
+ require 'do_postgres/transaction'
@@ -0,0 +1,37 @@
1
+
2
+ module DataObjects
3
+
4
+ module Postgres
5
+
6
+ class Transaction < DataObjects::Transaction
7
+
8
+ def begin
9
+ cmd = "BEGIN"
10
+ connection.create_command(cmd).execute_non_query
11
+ end
12
+
13
+ def commit
14
+ cmd = "COMMIT PREPARED '#{id}'"
15
+ connection.create_command(cmd).execute_non_query
16
+ end
17
+
18
+ def rollback
19
+ cmd = "ROLLBACK"
20
+ connection.create_command(cmd).execute_non_query
21
+ end
22
+
23
+ def rollback_prepared
24
+ cmd = "ROLLBACK PREPARED '#{id}'"
25
+ connection.create_command(cmd).execute_non_query
26
+ end
27
+
28
+ def prepare
29
+ cmd = "PREPARE TRANSACTION '#{id}'"
30
+ connection.create_command(cmd).execute_non_query
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,202 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ #
5
+ #
6
+ # Create a postgres db named do_test that accepts connections
7
+ # from localhost from your current user (without password) to enable this spec.
8
+ #
9
+ # You also need to allow passwordless access from localhost-
10
+ # locate the following line in your pg_hba.conf file:
11
+ #
12
+ # # IPv4 local connections:
13
+ # host all all 127.0.0.1/32 md5
14
+ #
15
+ # and replace 'md5' with 'trust' for these specs to work
16
+ #
17
+ #
18
+
19
+ describe "DataObjects::Postgres::Connection" do
20
+ include PostgresSpecHelpers
21
+
22
+ it "should connect to the db" do
23
+ connection = DataObjects::Connection.new("postgres://localhost/do_test")
24
+ end
25
+ end
26
+
27
+ describe "DataObjects::Postgres::Command" do
28
+ include PostgresSpecHelpers
29
+
30
+ before :all do
31
+ @connection = ensure_users_table_and_return_connection
32
+ end
33
+
34
+ it "should create a command" do
35
+ @connection.create_command("CREATE TABLE users").should be_a_kind_of(DataObjects::Postgres::Command)
36
+ end
37
+
38
+ it "should set types" do
39
+ command = @connection.create_command("SELECT id, name FROM users")
40
+ command.set_types [Integer, String]
41
+ command.instance_variable_get("@field_types").should == [Integer, String]
42
+ end
43
+
44
+ it "should execute a non query" do
45
+ command = @connection.create_command("INSERT INTO users (name) VALUES ('Test')")
46
+ result = command.execute_non_query
47
+ result.should be_a_kind_of(DataObjects::Postgres::Result)
48
+ end
49
+
50
+ it "should execute a reader" do
51
+ command = @connection.create_command("SELECT * FROM users")
52
+ reader = command.execute_reader
53
+ reader.should be_a_kind_of(DataObjects::Postgres::Reader)
54
+ reader.close.should == true
55
+ end
56
+ end
57
+
58
+ describe "DataObjects::Postgres::Result" do
59
+ include PostgresSpecHelpers
60
+
61
+ before :all do
62
+ @connection = ensure_users_table_and_return_connection
63
+ end
64
+
65
+ it "should raise errors on bad queries" do
66
+ command = @connection.create_command("INSER INTO users (name) VALUES ('Test')")
67
+ lambda { command.execute_non_query }.should raise_error
68
+ command = @connection.create_command("INSERT INTO users (non_existant_field) VALUES ('Test')")
69
+ lambda { command.execute_non_query }.should raise_error
70
+ end
71
+
72
+ it "should not have an insert_id without RETURNING" do
73
+ command = @connection.create_command("INSERT INTO users (name) VALUES ('Test')")
74
+ result = command.execute_non_query
75
+ result.insert_id.should == 0;
76
+ result.to_i.should == 1;
77
+ end
78
+
79
+ it "should have an insert_id when RETURNING" do
80
+ command = @connection.create_command("INSERT INTO users (name) VALUES ('Test') RETURNING id")
81
+ result = command.execute_non_query
82
+ result.insert_id.should_not == 0;
83
+ result.to_i.should == 1;
84
+ end
85
+ end
86
+
87
+ describe "DataObjects::Postgres::Reader" do
88
+ include PostgresSpecHelpers
89
+
90
+ before :all do
91
+ @connection = ensure_users_table_and_return_connection
92
+ @connection.create_command("INSERT INTO users (name) VALUES ('Test')").execute_non_query
93
+ @connection.create_command("INSERT INTO users (name) VALUES ('Test')").execute_non_query
94
+ @connection.create_command("INSERT INTO users (name) VALUES ('Test')").execute_non_query
95
+ end
96
+
97
+ it "should raise errors on bad queries" do
98
+ command = @connection.create_command("SELT * FROM users")
99
+ lambda { command.execute_reader }.should raise_error
100
+ command = @connection.create_command("SELECT * FROM non_existant_table")
101
+ lambda { command.execute_reader }.should raise_error
102
+ end
103
+
104
+ it "should open and close a reader" do
105
+ command = @connection.create_command("SELECT * FROM users LIMIT 3")
106
+ command.set_types [Integer, String]
107
+ reader = command.execute_reader
108
+ reader.close
109
+ end
110
+
111
+ it "should typecast a value from the postgres type" do
112
+ command = @connection.create_command("SELECT id, name, registered, money FROM users ORDER BY id DESC LIMIT 3")
113
+ reader = command.execute_reader
114
+ reader.send(:instance_variable_get, "@field_count").should == 4
115
+ reader.send(:instance_variable_get, "@row_count").should == 3
116
+ while ( reader.next!)
117
+ reader.values[0].should be_a_kind_of(Integer)
118
+ reader.values[1].should be_a_kind_of(String)
119
+ reader.values[2].should == false
120
+ reader.values[3].should == 1908.56
121
+ end
122
+ reader.close
123
+ end
124
+
125
+ it "should typecast from set_types" do
126
+ command = @connection.create_command("SELECT id, name FROM users ORDER BY id LIMIT 1")
127
+ command.set_types [Integer, String]
128
+ reader = command.execute_reader
129
+ reader.next!
130
+ reader.values[0].should be_a_kind_of(Integer)
131
+ reader.values[1].should be_a_kind_of(String)
132
+ reader.close
133
+ end
134
+
135
+ it "should handle a null value" do
136
+ id = insert("INSERT INTO users (name) VALUES (NULL)")
137
+ select("SELECT name from users WHERE name is null") do |reader|
138
+ reader.values[0].should == nil
139
+ end
140
+ end
141
+
142
+ it "should not convert empty strings to null" do
143
+ id = insert("INSERT INTO users (name) VALUES ('')")
144
+ select("SELECT name FROM users WHERE id = ?", [String], id) do |reader|
145
+ reader.values.first.should == ''
146
+ end
147
+ end
148
+
149
+ it "should typecast a date field" do
150
+ command = @connection.create_command("SELECT created_on FROM users WHERE created_on is not null LIMIT 1")
151
+ reader = command.execute_reader
152
+ reader.next!
153
+ reader.values[0].should be_a_kind_of(Date)
154
+ end
155
+
156
+ it "should typecast a BigDecimal field" do
157
+ money_in_the_bank = BigDecimal.new('1.29')
158
+
159
+ id = insert("INSERT INTO users (name, money) VALUES (?, ?)", "MC Hammer", money_in_the_bank)
160
+ select("SELECT money FROM users WHERE id = ?", [BigDecimal], id) do |reader|
161
+ reader.values.first.should == money_in_the_bank
162
+ end
163
+ end
164
+
165
+ it "should typecast a timestamp field" do
166
+ command = @connection.create_command("SELECT created_at FROM users WHERE created_at is not null LIMIT 1")
167
+ reader = command.execute_reader
168
+ reader.next!
169
+ dt = reader.values[0]
170
+ reader.values[0].should be_a_kind_of(DateTime)
171
+
172
+ command = @connection.create_command("SELECT created_at::date as date FROM users WHERE created_at is not null LIMIT 1")
173
+ reader = command.execute_reader
174
+ reader.next!
175
+ reader.values[0].should be_a_kind_of(Date)
176
+
177
+ end
178
+
179
+ it "should work on a join" do
180
+
181
+ user = @connection.create_command("SELECT * FROM users WHERE id = 1").execute_reader
182
+ user.next!
183
+
184
+ @connection.create_command("INSERT INTO companies (name) VALUES ('ELEC')").execute_non_query
185
+ reader = @connection.create_command(<<-EOF).execute_reader
186
+ SELECT u.* FROM users AS u
187
+ LEFT JOIN companies AS c
188
+ ON u.company_id=c.id
189
+ WHERE c.name='ELEC'
190
+ EOF
191
+ reader.next!
192
+ reader.values.should == user.values
193
+ end
194
+
195
+ it "should typecast a time field" do
196
+ pending "Time fields have no date information, and don't work with Time"
197
+ command = @connection.create_command("SELECT born_at FROM users LIMIT 1")
198
+ reader = command.execute_reader
199
+ reader.next!
200
+ reader.values[0].should be_a_kind_of(Time)
201
+ end
202
+ end