do_postgres 0.2.4 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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