pg 0.12.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data/BSDL +22 -0
  4. data/ChangeLog +1504 -11
  5. data/Contributors.rdoc +7 -0
  6. data/History.rdoc +181 -3
  7. data/LICENSE +12 -14
  8. data/Manifest.txt +29 -15
  9. data/{BSD → POSTGRES} +0 -0
  10. data/{README.OS_X.rdoc → README-OS_X.rdoc} +0 -0
  11. data/{README.windows.rdoc → README-Windows.rdoc} +0 -0
  12. data/README.ja.rdoc +10 -3
  13. data/README.rdoc +54 -28
  14. data/Rakefile +53 -26
  15. data/Rakefile.cross +235 -196
  16. data/ext/errorcodes.def +931 -0
  17. data/ext/errorcodes.rb +45 -0
  18. data/ext/errorcodes.txt +463 -0
  19. data/ext/extconf.rb +37 -7
  20. data/ext/gvl_wrappers.c +19 -0
  21. data/ext/gvl_wrappers.h +211 -0
  22. data/ext/pg.c +317 -4277
  23. data/ext/pg.h +124 -21
  24. data/ext/pg_connection.c +3642 -0
  25. data/ext/pg_errors.c +89 -0
  26. data/ext/pg_result.c +920 -0
  27. data/lib/pg/connection.rb +86 -0
  28. data/lib/pg/constants.rb +11 -0
  29. data/lib/pg/exceptions.rb +11 -0
  30. data/lib/pg/result.rb +16 -0
  31. data/lib/pg.rb +26 -43
  32. data/sample/array_insert.rb +20 -0
  33. data/sample/async_api.rb +21 -24
  34. data/sample/async_copyto.rb +2 -2
  35. data/sample/async_mixed.rb +56 -0
  36. data/sample/check_conn.rb +21 -0
  37. data/sample/copyfrom.rb +1 -1
  38. data/sample/copyto.rb +1 -1
  39. data/sample/cursor.rb +2 -2
  40. data/sample/disk_usage_report.rb +186 -0
  41. data/sample/issue-119.rb +94 -0
  42. data/sample/losample.rb +6 -6
  43. data/sample/minimal-testcase.rb +17 -0
  44. data/sample/notify_wait.rb +51 -22
  45. data/sample/pg_statistics.rb +294 -0
  46. data/sample/replication_monitor.rb +231 -0
  47. data/sample/test_binary_values.rb +4 -6
  48. data/sample/wal_shipper.rb +434 -0
  49. data/sample/warehouse_partitions.rb +320 -0
  50. data/spec/lib/helpers.rb +70 -23
  51. data/spec/pg/connection_spec.rb +1128 -0
  52. data/spec/{pgresult_spec.rb → pg/result_spec.rb} +142 -47
  53. data/spec/pg_spec.rb +44 -0
  54. data.tar.gz.sig +0 -0
  55. metadata +145 -100
  56. metadata.gz.sig +0 -0
  57. data/GPL +0 -340
  58. data/ext/compat.c +0 -541
  59. data/ext/compat.h +0 -184
  60. data/misc/openssl-pg-segfault.rb +0 -31
  61. data/sample/psql.rb +0 -1181
  62. data/sample/psqlHelp.rb +0 -158
  63. data/sample/test1.rb +0 -60
  64. data/sample/test2.rb +0 -44
  65. data/sample/test4.rb +0 -71
  66. data/spec/m17n_spec.rb +0 -151
  67. data/spec/pgconn_spec.rb +0 -643
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ #
4
+ # Script to automatically move partitioned tables and their indexes
5
+ # to a separate area on disk.
6
+ #
7
+ # Mahlon E. Smith <mahlon@martini.nu>
8
+ #
9
+ # Example use case:
10
+ #
11
+ # - You've got a heavy insert table, such as syslog data.
12
+ # - This table has a partitioning trigger (or is manually partitioned)
13
+ # by date, to separate incoming stuff from archival/report stuff.
14
+ # - You have a tablespace on cheap or slower disk (maybe even
15
+ # ZFS compressed, or some such!)
16
+ #
17
+ # The only assumption this script makes is that your tables are dated, and
18
+ # the tablespace they're moving into already exists.
19
+ #
20
+ # A full example, using the syslog idea from above, where each child
21
+ # table is date partitioned by a convention of "syslog_YEAR-WEEKOFYEAR":
22
+ #
23
+ # syslog # <--- parent
24
+ # syslog_2012_06 # <--- inherited
25
+ # syslog_2012_07 # <--- inherited
26
+ # syslog_2012_08 # <--- inherited
27
+ # ...
28
+ #
29
+ # You'd run this script like so:
30
+ #
31
+ # ./warehouse_partitions.rb -F syslog_%Y_%U
32
+ #
33
+ # Assuming this was week 12 of the year, tables syslog_2012_06 through
34
+ # syslog_2012_11 would start sequentially migrating into the tablespace
35
+ # called 'warehouse'.
36
+ #
37
+
38
+
39
+ begin
40
+ require 'date'
41
+ require 'ostruct'
42
+ require 'optparse'
43
+ require 'pathname'
44
+ require 'etc'
45
+ require 'pg'
46
+
47
+ rescue LoadError # 1.8 support
48
+ unless Object.const_defined?( :Gem )
49
+ require 'rubygems'
50
+ retry
51
+ end
52
+ raise
53
+ end
54
+
55
+
56
+ ### A tablespace migration class.
57
+ ###
58
+ class PGWarehouse
59
+
60
+ def initialize( opts )
61
+ @opts = opts
62
+ @db = PG.connect(
63
+ :dbname => opts.database,
64
+ :host => opts.host,
65
+ :port => opts.port,
66
+ :user => opts.user,
67
+ :password => opts.pass,
68
+ :sslmode => 'prefer'
69
+ )
70
+ @db.exec "SET search_path TO %s" % [ opts.schema ] if opts.schema
71
+
72
+ @relations = self.relations
73
+ end
74
+
75
+ attr_reader :db
76
+
77
+ ######
78
+ public
79
+ ######
80
+
81
+ ### Perform the tablespace moves.
82
+ ###
83
+ def migrate
84
+ if @relations.empty?
85
+ $stderr.puts 'No tables were found for warehousing.'
86
+ return
87
+ end
88
+
89
+ $stderr.puts "Found %d relation%s to move." % [ relations.length, relations.length == 1 ? '' : 's' ]
90
+ @relations.sort_by{|_,v| v[:name] }.each do |_, val|
91
+ $stderr.print " - Moving table '%s' to '%s'... " % [
92
+ val[:name], @opts.tablespace
93
+ ]
94
+
95
+ if @opts.dryrun
96
+ $stderr.puts '(not really)'
97
+
98
+ else
99
+ age = self.timer do
100
+ db.exec "ALTER TABLE %s SET TABLESPACE %s;" % [
101
+ val[:name], @opts.tablespace
102
+ ]
103
+ end
104
+ puts age
105
+ end
106
+
107
+ val[ :indexes ].each do |idx|
108
+ $stderr.print " - Moving index '%s' to '%s'... " % [
109
+ idx, @opts.tablespace
110
+ ]
111
+ if @opts.dryrun
112
+ $stderr.puts '(not really)'
113
+
114
+ else
115
+ age = self.timer do
116
+ db.exec "ALTER INDEX %s SET TABLESPACE %s;" % [
117
+ idx, @opts.tablespace
118
+ ]
119
+ end
120
+ puts age
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+
127
+ #########
128
+ protected
129
+ #########
130
+
131
+ ### Get OIDs and current tablespaces for everything under the
132
+ ### specified schema.
133
+ ###
134
+ def relations
135
+ return @relations if @relations
136
+ relations = {}
137
+
138
+ query = %q{
139
+ SELECT c.oid AS oid,
140
+ c.relname AS name,
141
+ c.relkind AS kind,
142
+ t.spcname AS tspace
143
+ FROM pg_class AS c
144
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
145
+ LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
146
+ WHERE c.relkind = 'r' }
147
+ query << "AND n.nspname='#{@opts.schema}'" if @opts.schema
148
+
149
+ # Get the relations list, along with each element's current tablespace.
150
+ #
151
+ self.db.exec( query ) do |res|
152
+ res.each do |row|
153
+ relations[ row['oid'] ] = {
154
+ :name => row['name'],
155
+ :tablespace => row['tspace'],
156
+ :indexes => [],
157
+ :parent => nil
158
+ }
159
+ end
160
+ end
161
+
162
+ # Add table inheritence information.
163
+ #
164
+ db.exec 'SELECT inhrelid AS oid, inhparent AS parent FROM pg_inherits' do |res|
165
+ res.each do |row|
166
+ relations[ row['oid'] ][ :parent ] = row['parent']
167
+ end
168
+ end
169
+
170
+ # Remove tables that don't qualify for warehousing.
171
+ #
172
+ # - Tables that are not children of a parent
173
+ # - Tables that are already in the warehouse tablespace
174
+ # - The currently active child (it's likely being written to!)
175
+ # - Any table that can't be parsed into the specified format
176
+ #
177
+ relations.reject! do |oid, val|
178
+ begin
179
+ val[:parent].nil? ||
180
+ val[:tablespace] == @opts.tablespace ||
181
+ val[:name] == Time.now.strftime( @opts.format ) ||
182
+ ! DateTime.strptime( val[:name], @opts.format )
183
+ rescue ArgumentError
184
+ true
185
+ end
186
+ end
187
+
188
+ query = %q{
189
+ SELECT c.oid AS oid,
190
+ i.indexname AS name
191
+ FROM pg_class AS c
192
+ INNER JOIN pg_indexes AS i
193
+ ON i.tablename = c.relname }
194
+ query << "AND i.schemaname='#{@opts.schema}'" if @opts.schema
195
+
196
+ # Attach index names to tables.
197
+ #
198
+ db.exec( query ) do |res|
199
+ res.each do |row|
200
+ relations[ row['oid'] ][ :indexes ] << row['name'] if relations[ row['oid'] ]
201
+ end
202
+ end
203
+
204
+ return relations
205
+ end
206
+
207
+
208
+ ### Wrap arbitrary commands in a human readable timer.
209
+ ###
210
+ def timer
211
+ start = Time.now
212
+ yield
213
+ age = Time.now - start
214
+
215
+ diff = age
216
+ secs = diff % 60
217
+ diff = ( diff - secs ) / 60
218
+ mins = diff % 60
219
+ diff = ( diff - mins ) / 60
220
+ hour = diff % 24
221
+
222
+ return "%02d:%02d:%02d" % [ hour, mins, secs ]
223
+ end
224
+ end
225
+
226
+
227
+ ### Parse command line arguments. Return a struct of global options.
228
+ ###
229
+ def parse_args( args )
230
+ options = OpenStruct.new
231
+ options.database = Etc.getpwuid( Process.uid ).name
232
+ options.host = '127.0.0.1'
233
+ options.port = 5432
234
+ options.user = Etc.getpwuid( Process.uid ).name
235
+ options.sslmode = 'prefer'
236
+ options.tablespace = 'warehouse'
237
+
238
+ opts = OptionParser.new do |opts|
239
+ opts.banner = "Usage: #{$0} [options]"
240
+
241
+ opts.separator ''
242
+ opts.separator 'Connection options:'
243
+
244
+ opts.on( '-d', '--database DBNAME',
245
+ "specify the database to connect to (default: \"#{options.database}\")" ) do |db|
246
+ options.database = db
247
+ end
248
+
249
+ opts.on( '-h', '--host HOSTNAME', 'database server host' ) do |host|
250
+ options.host = host
251
+ end
252
+
253
+ opts.on( '-p', '--port PORT', Integer,
254
+ "database server port (default: \"#{options.port}\")" ) do |port|
255
+ options.port = port
256
+ end
257
+
258
+ opts.on( '-n', '--schema SCHEMA', String,
259
+ "operate on the named schema only (default: none)" ) do |schema|
260
+ options.schema = schema
261
+ end
262
+
263
+ opts.on( '-T', '--tablespace SPACE', String,
264
+ "move old tables to this tablespace (default: \"#{options.tablespace}\")" ) do |tb|
265
+ options.tablespace = tb
266
+ end
267
+
268
+ opts.on( '-F', '--tableformat FORMAT', String,
269
+ "The naming format (strftime) for the inherited tables (default: none)" ) do |format|
270
+ options.format = format
271
+ end
272
+
273
+ opts.on( '-U', '--user NAME',
274
+ "database user name (default: \"#{options.user}\")" ) do |user|
275
+ options.user = user
276
+ end
277
+
278
+ opts.on( '-W', 'force password prompt' ) do |pw|
279
+ print 'Password: '
280
+ begin
281
+ system 'stty -echo'
282
+ options.pass = gets.chomp
283
+ ensure
284
+ system 'stty echo'
285
+ puts
286
+ end
287
+ end
288
+
289
+ opts.separator ''
290
+ opts.separator 'Other options:'
291
+
292
+ opts.on_tail( '--dry-run', "don't actually do anything" ) do
293
+ options.dryrun = true
294
+ end
295
+
296
+ opts.on_tail( '--help', 'show this help, then exit' ) do
297
+ $stderr.puts opts
298
+ exit
299
+ end
300
+
301
+ opts.on_tail( '--version', 'output version information, then exit' ) do
302
+ puts Stats::VERSION
303
+ exit
304
+ end
305
+ end
306
+
307
+ opts.parse!( args )
308
+ return options
309
+ end
310
+
311
+
312
+ if __FILE__ == $0
313
+ opts = parse_args( ARGV )
314
+ raise ArgumentError, "A naming format (-F) is required." unless opts.format
315
+
316
+ $stdout.sync = true
317
+ PGWarehouse.new( opts ).migrate
318
+ end
319
+
320
+
data/spec/lib/helpers.rb CHANGED
@@ -3,17 +3,11 @@
3
3
  require 'pathname'
4
4
  require 'rspec'
5
5
  require 'shellwords'
6
+ require 'pg'
6
7
 
7
8
  TEST_DIRECTORY = Pathname.getwd + "tmp_test_specs"
8
9
 
9
- RSpec.configure do |config|
10
- ruby_version_vec = RUBY_VERSION.split('.').map {|c| c.to_i }.pack( "N*" )
11
-
12
- config.mock_with :rspec
13
- config.filter_run_excluding :ruby_19 => true if ruby_version_vec <= [1,9,1].pack( "N*" )
14
- end
15
-
16
- module PgTestingHelpers
10
+ module PG::TestingHelpers
17
11
 
18
12
 
19
13
  # Set some ANSI escape code constants (Shamelessly stolen from Perl's
@@ -48,13 +42,12 @@ module PgTestingHelpers
48
42
  def ansi_code( *attributes )
49
43
  attributes.flatten!
50
44
  attributes.collect! {|at| at.to_s }
51
- # $stderr.puts "Returning ansicode for TERM = %p: %p" %
52
- # [ ENV['TERM'], attributes ]
45
+
53
46
  return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
54
47
  attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
55
48
 
56
49
  # $stderr.puts " attr is: %p" % [attributes]
57
- if attributes.empty?
50
+ if attributes.empty?
58
51
  return ''
59
52
  else
60
53
  return "\e[%sm" % attributes
@@ -62,7 +55,7 @@ module PgTestingHelpers
62
55
  end
63
56
 
64
57
 
65
- ### Colorize the given +string+ with the specified +attributes+ and return it, handling
58
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling
66
59
  ### line-endings, color reset, etc.
67
60
  def colorize( *args )
68
61
  string = ''
@@ -116,9 +109,7 @@ module PgTestingHelpers
116
109
  end
117
110
 
118
111
 
119
- NOFORK_PLATFORMS = %w{java}
120
-
121
- ### Run the specified command +cmd+ after redirecting stdout and stderr to the specified
112
+ ### Run the specified command +cmd+ after redirecting stdout and stderr to the specified
122
113
  ### +logpath+, failing if the execution fails.
123
114
  def log_and_run( logpath, *cmd )
124
115
  cmd.flatten!
@@ -132,11 +123,14 @@ module PgTestingHelpers
132
123
  # Eliminate the noise of creating/tearing down the database by
133
124
  # redirecting STDERR/STDOUT to a logfile if the Ruby interpreter
134
125
  # supports fork()
135
- if NOFORK_PLATFORMS.include?( RUBY_PLATFORM )
126
+ logfh = File.open( logpath, File::WRONLY|File::CREAT|File::APPEND )
127
+ begin
128
+ pid = fork
129
+ rescue NotImplementedError
130
+ logfh.close
136
131
  system( *cmd )
137
132
  else
138
- logfh = File.open( logpath, File::WRONLY|File::CREAT|File::APPEND )
139
- if pid = fork
133
+ if pid
140
134
  logfh.close
141
135
  else
142
136
  $stdout.reopen( logfh )
@@ -186,7 +180,7 @@ module PgTestingHelpers
186
180
  require 'pg'
187
181
  stop_existing_postmasters()
188
182
 
189
- puts "Setting up test database for #{description} tests"
183
+ puts "Setting up test database for #{description}"
190
184
  @test_pgdata = TEST_DIRECTORY + 'data'
191
185
  @test_pgdata.mkpath
192
186
 
@@ -202,7 +196,7 @@ module PgTestingHelpers
202
196
  unless (@test_pgdata+"postgresql.conf").exist?
203
197
  FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
204
198
  $stderr.puts "Running initdb"
205
- log_and_run @logfile, 'initdb', '--no-locale', '-D', @test_pgdata.to_s
199
+ log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
206
200
  end
207
201
 
208
202
  trace "Starting postgres"
@@ -221,9 +215,9 @@ module PgTestingHelpers
221
215
  fail
222
216
  end
223
217
 
224
- conn = PGconn.connect( @conninfo )
218
+ conn = PG.connect( @conninfo )
225
219
  conn.set_notice_processor do |message|
226
- $stderr.puts( message ) if $DEBUG
220
+ $stderr.puts( description + ':' + message ) if $DEBUG
227
221
  end
228
222
 
229
223
  return conn
@@ -232,9 +226,62 @@ module PgTestingHelpers
232
226
 
233
227
  def teardown_testing_db( conn )
234
228
  puts "Tearing down test database"
235
- conn.finish if conn
229
+
230
+ if conn
231
+ check_for_lingering_connections( conn )
232
+ conn.finish
233
+ end
234
+
236
235
  log_and_run @logfile, 'pg_ctl', '-D', @test_pgdata.to_s, 'stop'
237
236
  end
237
+
238
+
239
+ def check_for_lingering_connections( conn )
240
+ conn.exec( "SELECT * FROM pg_stat_activity" ) do |res|
241
+ conns = res.find_all {|row| row['pid'].to_i != conn.backend_pid }
242
+ unless conns.empty?
243
+ puts "Lingering connections remain:"
244
+ conns.each do |row|
245
+ puts " [%d] {%s} %s -- %s" % row.values_at( 'pid', 'state', 'application_name', 'query' )
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ def connection_string_should_contain_application_name(conn_args, app_name)
252
+ conn_name = conn_args.match(/application_name='(.*)'/)[1]
253
+ conn_name.should include(app_name[0..10])
254
+ conn_name.should include(app_name[-10..-1])
255
+ conn_name.length.should <= 64
256
+ end
238
257
  end
239
258
 
240
259
 
260
+ RSpec.configure do |config|
261
+ ruby_version_vec = RUBY_VERSION.split('.').map {|c| c.to_i }.pack( "N*" )
262
+
263
+ config.include( PG::TestingHelpers )
264
+ config.treat_symbols_as_metadata_keys_with_true_values = true
265
+
266
+ config.mock_with :rspec
267
+ config.filter_run_excluding :ruby_19 if ruby_version_vec <= [1,9,1].pack( "N*" )
268
+ if RUBY_PLATFORM =~ /mingw|mswin/
269
+ config.filter_run_excluding :unix
270
+ else
271
+ config.filter_run_excluding :windows
272
+ end
273
+ config.filter_run_excluding :socket_io unless
274
+ PG::Connection.instance_methods.map( &:to_sym ).include?( :socket_io )
275
+
276
+ config.filter_run_excluding :postgresql_90 unless
277
+ PG::Connection.instance_methods.map( &:to_sym ).include?( :escape_literal )
278
+
279
+ if !PG.respond_to?( :library_version )
280
+ config.filter_run_excluding( :postgresql_91, :postgresql_92, :postgresql_93 )
281
+ elsif PG.library_version < 90200
282
+ config.filter_run_excluding( :postgresql_92, :postgresql_93 )
283
+ elsif PG.library_version < 90300
284
+ config.filter_run_excluding( :postgresql_93 )
285
+ end
286
+ end
287
+