pg 1.1.0.pre20180730144600-x64-mingw32 → 1.1.0.pre20180730171000-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -1
  4. data/.gems +6 -0
  5. data/.hgignore +21 -0
  6. data/.hgsigs +29 -0
  7. data/.hgtags +36 -0
  8. data/.hoerc +2 -0
  9. data/.irbrc +23 -0
  10. data/.pryrc +23 -0
  11. data/.tm_properties +21 -0
  12. data/.travis.yml +41 -0
  13. data/Gemfile +2 -0
  14. data/Manifest.txt +2 -65
  15. data/Rakefile +1 -0
  16. data/appveyor.yml +50 -0
  17. data/certs/ged.pem +26 -0
  18. data/lib/2.0/pg_ext.so +0 -0
  19. data/lib/2.1/pg_ext.so +0 -0
  20. data/lib/2.2/pg_ext.so +0 -0
  21. data/lib/2.3/pg_ext.so +0 -0
  22. data/lib/2.4/pg_ext.so +0 -0
  23. data/lib/2.5/pg_ext.so +0 -0
  24. data/lib/libpq.dll +0 -0
  25. data/lib/pg.rb +1 -1
  26. data/lib/pg/binary_decoder.rb +22 -0
  27. data/lib/pg/tuple.rb +30 -0
  28. data/misc/openssl-pg-segfault.rb +31 -0
  29. data/misc/postgres/History.txt +9 -0
  30. data/misc/postgres/Manifest.txt +5 -0
  31. data/misc/postgres/README.txt +21 -0
  32. data/misc/postgres/Rakefile +21 -0
  33. data/misc/postgres/lib/postgres.rb +16 -0
  34. data/misc/ruby-pg/History.txt +9 -0
  35. data/misc/ruby-pg/Manifest.txt +5 -0
  36. data/misc/ruby-pg/README.txt +21 -0
  37. data/misc/ruby-pg/Rakefile +21 -0
  38. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  39. data/pg.gemspec +61 -0
  40. data/sample/array_insert.rb +20 -0
  41. data/sample/async_api.rb +106 -0
  42. data/sample/async_copyto.rb +39 -0
  43. data/sample/async_mixed.rb +56 -0
  44. data/sample/check_conn.rb +21 -0
  45. data/sample/copydata.rb +71 -0
  46. data/sample/copyfrom.rb +81 -0
  47. data/sample/copyto.rb +19 -0
  48. data/sample/cursor.rb +21 -0
  49. data/sample/disk_usage_report.rb +177 -0
  50. data/sample/issue-119.rb +94 -0
  51. data/sample/losample.rb +69 -0
  52. data/sample/minimal-testcase.rb +17 -0
  53. data/sample/notify_wait.rb +72 -0
  54. data/sample/pg_statistics.rb +285 -0
  55. data/sample/replication_monitor.rb +222 -0
  56. data/sample/test_binary_values.rb +33 -0
  57. data/sample/wal_shipper.rb +434 -0
  58. data/sample/warehouse_partitions.rb +311 -0
  59. data/spec/pg/connection_sync_spec.rb +41 -0
  60. data/spec/pg/tuple_spec.rb +266 -0
  61. metadata +69 -25
  62. metadata.gz.sig +0 -0
  63. data/ChangeLog +0 -0
@@ -0,0 +1,69 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'pg'
4
+
5
+ SAMPLE_WRITE_DATA = 'some sample data'
6
+ SAMPLE_EXPORT_NAME = 'lowrite.txt'
7
+
8
+ conn = PG.connect( :dbname => 'test', :host => 'localhost', :port => 5432 )
9
+ puts "dbname: " + conn.db + "\thost: " + conn.host + "\tuser: " + conn.user
10
+
11
+ # Start a transaction, as all large object functions require one.
12
+ puts "Beginning transaction"
13
+ conn.exec( 'BEGIN' )
14
+
15
+ # Test importing from a file
16
+ puts "Import test:"
17
+ puts " importing %s" % [ __FILE__ ]
18
+ oid = conn.lo_import( __FILE__ )
19
+ puts " imported as large object %d" % [ oid ]
20
+
21
+ # Read back 50 bytes of the imported data
22
+ puts "Read test:"
23
+ fd = conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
24
+ conn.lo_lseek( fd, 0, PG::SEEK_SET )
25
+ buf = conn.lo_read( fd, 50 )
26
+ puts " read: %p" % [ buf ]
27
+ puts " read was ok!" if buf =~ /require 'pg'/
28
+
29
+ # Append some test data onto the end of the object
30
+ puts "Write test:"
31
+ conn.lo_lseek( fd, 0, PG::SEEK_END )
32
+ buf = SAMPLE_WRITE_DATA.dup
33
+ totalbytes = 0
34
+ until buf.empty?
35
+ bytes = conn.lo_write( fd, buf )
36
+ buf.slice!( 0, bytes )
37
+ totalbytes += bytes
38
+ end
39
+ puts " appended %d bytes" % [ totalbytes ]
40
+
41
+ # Now export it
42
+ puts "Export test:"
43
+ File.unlink( SAMPLE_EXPORT_NAME ) if File.exist?( SAMPLE_EXPORT_NAME )
44
+ conn.lo_export( oid, SAMPLE_EXPORT_NAME )
45
+ puts " success!" if File.exist?( SAMPLE_EXPORT_NAME )
46
+ puts " exported as %s (%d bytes)" % [ SAMPLE_EXPORT_NAME, File.size(SAMPLE_EXPORT_NAME) ]
47
+
48
+ conn.exec( 'COMMIT' )
49
+ puts "End of transaction."
50
+
51
+
52
+ puts 'Testing read and delete from a new transaction:'
53
+ puts ' starting a new transaction'
54
+ conn.exec( 'BEGIN' )
55
+
56
+ fd = conn.lo_open( oid, PG::INV_READ )
57
+ puts ' reopened okay.'
58
+ conn.lo_lseek( fd, 50, PG::SEEK_END )
59
+ buf = conn.lo_read( fd, 50 )
60
+ puts ' read okay.' if buf == SAMPLE_WRITE_DATA
61
+
62
+ puts 'Closing and unlinking:'
63
+ conn.lo_close( fd )
64
+ puts ' closed.'
65
+ conn.lo_unlink( oid )
66
+ puts ' unlinked.'
67
+ conn.exec( 'COMMIT' )
68
+ puts 'Done.'
69
+
@@ -0,0 +1,17 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'pg'
4
+
5
+ conn = PG.connect( :dbname => 'test' )
6
+ $stderr.puts '---',
7
+ RUBY_DESCRIPTION,
8
+ PG.version_string( true ),
9
+ "Server version: #{conn.server_version}",
10
+ "Client version: #{PG.library_version}",
11
+ '---'
12
+
13
+ result = conn.exec( "SELECT * from pg_stat_activity" )
14
+
15
+ $stderr.puts %Q{Expected this to return: ["select * from pg_stat_activity"]}
16
+ p result.field_values( 'current_query' )
17
+
@@ -0,0 +1,72 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # Test script, demonstrating a non-poll notification for a table event.
4
+ #
5
+
6
+ BEGIN {
7
+ require 'pathname'
8
+ basedir = Pathname.new( __FILE__ ).expand_path.dirname.parent
9
+ libdir = basedir + 'lib'
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'pg'
14
+
15
+ TRIGGER_TABLE = %{
16
+ CREATE TABLE IF NOT EXISTS test ( message text );
17
+ }
18
+
19
+ TRIGGER_FUNCTION = %{
20
+ CREATE OR REPLACE FUNCTION notify_test()
21
+ RETURNS TRIGGER
22
+ LANGUAGE plpgsql
23
+ AS $$
24
+ BEGIN
25
+ NOTIFY woo;
26
+ RETURN NULL;
27
+ END
28
+ $$
29
+ }
30
+
31
+ DROP_TRIGGER = %{
32
+ DROP TRIGGER IF EXISTS notify_trigger ON test
33
+ }
34
+
35
+
36
+ TRIGGER = %{
37
+ CREATE TRIGGER notify_trigger
38
+ AFTER UPDATE OR INSERT OR DELETE
39
+ ON test
40
+ FOR EACH STATEMENT
41
+ EXECUTE PROCEDURE notify_test();
42
+ }
43
+
44
+ conn = PG.connect( :dbname => 'test' )
45
+
46
+ conn.exec( TRIGGER_TABLE )
47
+ conn.exec( TRIGGER_FUNCTION )
48
+ conn.exec( DROP_TRIGGER )
49
+ conn.exec( TRIGGER )
50
+
51
+ conn.exec( 'LISTEN woo' ) # register interest in the 'woo' event
52
+
53
+ notifications = []
54
+
55
+ puts "Now switch to a different term and run:",
56
+ '',
57
+ %{ psql test -c "insert into test values ('A message.')"},
58
+ ''
59
+
60
+ puts "Waiting up to 30 seconds for for an event!"
61
+ conn.wait_for_notify( 30 ) do |notify, pid|
62
+ notifications << [ pid, notify ]
63
+ end
64
+
65
+ if notifications.empty?
66
+ puts "Awww, I didn't see any events."
67
+ else
68
+ puts "I got one from pid %d: %s" % notifications.first
69
+ end
70
+
71
+
72
+
@@ -0,0 +1,285 @@
1
+ # -*- ruby -*-
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+ #
4
+ # PostgreSQL statistic gatherer.
5
+ # Mahlon E. Smith <mahlon@martini.nu>
6
+ #
7
+ # Based on queries by Kenny Gorman.
8
+ # http://www.kennygorman.com/wordpress/?page_id=491
9
+ #
10
+ # An example gnuplot input script is included in the __END__ block
11
+ # of this script. Using it, you can feed the output this script
12
+ # generates to gnuplot (after removing header lines) to generate
13
+ # some nice performance charts.
14
+ #
15
+
16
+ require 'ostruct'
17
+ require 'optparse'
18
+ require 'etc'
19
+ require 'pg'
20
+
21
+
22
+ ### PostgreSQL Stats. Fetch information from pg_stat_* tables.
23
+ ### Optionally run in a continuous loop, displaying deltas.
24
+ ###
25
+ class Stats
26
+ VERSION = %q$Id$
27
+
28
+ def initialize( opts )
29
+ @opts = opts
30
+ @db = PG.connect(
31
+ :dbname => opts.database,
32
+ :host => opts.host,
33
+ :port => opts.port,
34
+ :user => opts.user,
35
+ :password => opts.pass,
36
+ :sslmode => 'prefer'
37
+ )
38
+ @last = nil
39
+ end
40
+
41
+ ######
42
+ public
43
+ ######
44
+
45
+ ### Primary loop. Gather statistics and generate deltas.
46
+ ###
47
+ def run
48
+ run_count = 0
49
+
50
+ loop do
51
+ current_stat = self.get_stats
52
+
53
+ # First run, store and continue
54
+ #
55
+ if @last.nil?
56
+ @last = current_stat
57
+ sleep @opts.interval
58
+ next
59
+ end
60
+
61
+ # headers
62
+ #
63
+ if run_count == 0 || run_count % 50 == 0
64
+ puts "%-20s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s" % %w[
65
+ time commits rollbks blksrd blkshit bkends seqscan
66
+ seqtprd idxscn idxtrd ins upd del locks activeq
67
+ ]
68
+ end
69
+
70
+ # calculate deltas
71
+ #
72
+ delta = current_stat.inject({}) do |h, pair|
73
+ stat, val = *pair
74
+
75
+ if %w[ activeq locks bkends ].include?( stat )
76
+ h[stat] = current_stat[stat].to_i
77
+ else
78
+ h[stat] = current_stat[stat].to_i - @last[stat].to_i
79
+ end
80
+
81
+ h
82
+ end
83
+ delta[ 'time' ] = Time.now.strftime('%F %T')
84
+
85
+ # new values
86
+ #
87
+ puts "%-20s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s%12s" % [
88
+ delta['time'], delta['commits'], delta['rollbks'], delta['blksrd'],
89
+ delta['blkshit'], delta['bkends'], delta['seqscan'],
90
+ delta['seqtprd'], delta['idxscn'], delta['idxtrd'],
91
+ delta['ins'], delta['upd'], delta['del'], delta['locks'], delta['activeq']
92
+ ]
93
+
94
+ @last = current_stat
95
+ run_count += 1
96
+ sleep @opts.interval
97
+ end
98
+ end
99
+
100
+
101
+ ### Query the database for performance measurements. Returns a hash.
102
+ ###
103
+ def get_stats
104
+ res = @db.exec %Q{
105
+ SELECT
106
+ MAX(stat_db.xact_commit) AS commits,
107
+ MAX(stat_db.xact_rollback) AS rollbks,
108
+ MAX(stat_db.blks_read) AS blksrd,
109
+ MAX(stat_db.blks_hit) AS blkshit,
110
+ MAX(stat_db.numbackends) AS bkends,
111
+ SUM(stat_tables.seq_scan) AS seqscan,
112
+ SUM(stat_tables.seq_tup_read) AS seqtprd,
113
+ SUM(stat_tables.idx_scan) AS idxscn,
114
+ SUM(stat_tables.idx_tup_fetch) AS idxtrd,
115
+ SUM(stat_tables.n_tup_ins) AS ins,
116
+ SUM(stat_tables.n_tup_upd) AS upd,
117
+ SUM(stat_tables.n_tup_del) AS del,
118
+ MAX(stat_locks.locks) AS locks,
119
+ MAX(activity.sess) AS activeq
120
+ FROM
121
+ pg_stat_database AS stat_db,
122
+ pg_stat_user_tables AS stat_tables,
123
+ (SELECT COUNT(*) AS locks FROM pg_locks ) AS stat_locks,
124
+ (SELECT COUNT(*) AS sess FROM pg_stat_activity WHERE current_query <> '<IDLE>') AS activity
125
+ WHERE
126
+ stat_db.datname = '%s';
127
+ } % [ @opts.database ]
128
+
129
+ return res[0]
130
+ end
131
+ end
132
+
133
+
134
+ ### Parse command line arguments. Return a struct of global options.
135
+ ###
136
+ def parse_args( args )
137
+ options = OpenStruct.new
138
+ options.database = Etc.getpwuid( Process.uid ).name
139
+ options.host = '127.0.0.1'
140
+ options.port = 5432
141
+ options.user = Etc.getpwuid( Process.uid ).name
142
+ options.sslmode = 'disable'
143
+ options.interval = 5
144
+
145
+ opts = OptionParser.new do |opts|
146
+ opts.banner = "Usage: #{$0} [options]"
147
+
148
+ opts.separator ''
149
+ opts.separator 'Connection options:'
150
+
151
+ opts.on( '-d', '--database DBNAME',
152
+ "specify the database to connect to (default: \"#{options.database}\")" ) do |db|
153
+ options.database = db
154
+ end
155
+
156
+ opts.on( '-h', '--host HOSTNAME', 'database server host' ) do |host|
157
+ options.host = host
158
+ end
159
+
160
+ opts.on( '-p', '--port PORT', Integer,
161
+ "database server port (default: \"#{options.port}\")" ) do |port|
162
+ options.port = port
163
+ end
164
+
165
+ opts.on( '-U', '--user NAME',
166
+ "database user name (default: \"#{options.user}\")" ) do |user|
167
+ options.user = user
168
+ end
169
+
170
+ opts.on( '-W', 'force password prompt' ) do |pw|
171
+ print 'Password: '
172
+ begin
173
+ system 'stty -echo'
174
+ options.pass = gets.chomp
175
+ ensure
176
+ system 'stty echo'
177
+ puts
178
+ end
179
+ end
180
+
181
+ opts.separator ''
182
+ opts.separator 'Other options:'
183
+
184
+ opts.on( '-i', '--interval SECONDS', Integer,
185
+ "refresh interval in seconds (default: \"#{options.interval}\")") do |seconds|
186
+ options.interval = seconds
187
+ end
188
+
189
+ opts.on_tail( '--help', 'show this help, then exit' ) do
190
+ $stderr.puts opts
191
+ exit
192
+ end
193
+
194
+ opts.on_tail( '--version', 'output version information, then exit' ) do
195
+ puts Stats::VERSION
196
+ exit
197
+ end
198
+ end
199
+
200
+ opts.parse!( args )
201
+ return options
202
+ end
203
+
204
+
205
+ ### Go!
206
+ ###
207
+ if __FILE__ == $0
208
+ $stdout.sync = true
209
+ Stats.new( parse_args( ARGV ) ).run
210
+ end
211
+
212
+
213
+ __END__
214
+ ######################################################################
215
+ ### T E R M I N A L O P T I O N S
216
+ ######################################################################
217
+
218
+ #set terminal png nocrop enhanced font arial 8 size '800x600' x000000 xffffff x444444
219
+ #set output 'graph.png'
220
+
221
+ set terminal pdf linewidth 4 size 11,8
222
+ set output 'graph.pdf'
223
+
224
+ #set terminal aqua
225
+
226
+
227
+ ######################################################################
228
+ ### O P T I O N S F O R A L L G R A P H S
229
+ ######################################################################
230
+
231
+ set multiplot layout 2,1 title "PostgreSQL Statistics\n5 second sample rate (smoothed)"
232
+
233
+ set grid x y
234
+ set key right vertical outside
235
+ set key nobox
236
+ set xdata time
237
+ set timefmt "%Y-%m-%d.%H:%M:%S"
238
+ set format x "%l%p"
239
+ set xtic rotate by -45
240
+ input_file = "database_stats.txt"
241
+
242
+ # edit to taste!
243
+ set xrange ["2012-04-16.00:00:00":"2012-04-17.00:00:00"]
244
+
245
+
246
+ ######################################################################
247
+ ### G R A P H 1
248
+ ######################################################################
249
+
250
+ set title "Database Operations and Connection Totals"
251
+ set yrange [0:200]
252
+
253
+ plot \
254
+ input_file using 1:2 title "Commits" with lines smooth bezier, \
255
+ input_file using 1:3 title "Rollbacks" with lines smooth bezier, \
256
+ input_file using 1:11 title "Inserts" with lines smooth bezier, \
257
+ input_file using 1:12 title "Updates" with lines smooth bezier, \
258
+ input_file using 1:13 title "Deletes" with lines smooth bezier, \
259
+ input_file using 1:6 title "Backends (total)" with lines, \
260
+ input_file using 1:15 title "Active queries (total)" with lines smooth bezier
261
+
262
+
263
+ ######################################################################
264
+ ### G R A P H 2
265
+ ######################################################################
266
+
267
+ set title "Backend Performance"
268
+ set yrange [0:10000]
269
+
270
+ plot \
271
+ input_file using 1:4 title "Block (cache) reads" with lines smooth bezier, \
272
+ input_file using 1:5 title "Block (cache) hits" with lines smooth bezier, \
273
+ input_file using 1:7 title "Sequence scans" with lines smooth bezier, \
274
+ input_file using 1:8 title "Sequence tuple reads" with lines smooth bezier, \
275
+ input_file using 1:9 title "Index scans" with lines smooth bezier, \
276
+ input_file using 1:10 title "Index tuple reads" with lines smooth bezier
277
+
278
+
279
+ ######################################################################
280
+ ### C L E A N U P
281
+ ######################################################################
282
+
283
+ unset multiplot
284
+ reset
285
+
@@ -0,0 +1,222 @@
1
+ # -*- ruby -*-
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+ #
4
+ # Get the current WAL segment and offset from a master postgresql
5
+ # server, and compare slave servers to see how far behind they
6
+ # are in MB. This script should be easily modified for use with
7
+ # Nagios/Mon/Monit/Zabbix/whatever, or wrapping it in a display loop,
8
+ # and is suitable for both WAL shipping or streaming forms of replication.
9
+ #
10
+ # Mahlon E. Smith <mahlon@martini.nu>
11
+ #
12
+ # First argument is the master server, all other arguments are treated
13
+ # as slave machines.
14
+ #
15
+ # db_replication.monitor db-master.example.com ...
16
+ #
17
+
18
+ require 'ostruct'
19
+ require 'optparse'
20
+ require 'pathname'
21
+ require 'etc'
22
+ require 'pg'
23
+ require 'pp'
24
+
25
+
26
+ ### A class to encapsulate the PG handles.
27
+ ###
28
+ class PGMonitor
29
+
30
+ VERSION = %q$Id$
31
+
32
+ # When to consider a slave as 'behind', measured in WAL segments.
33
+ # The default WAL segment size is 16, so we'll alert after
34
+ # missing two WAL files worth of data.
35
+ #
36
+ LAG_ALERT = 32
37
+
38
+ ### Create a new PGMonitor object.
39
+ ###
40
+ def initialize( opts, hosts )
41
+ @opts = opts
42
+ @master = hosts.shift
43
+ @slaves = hosts
44
+ @current_wal = {}
45
+ @failures = []
46
+ end
47
+
48
+ attr_reader :opts, :current_wal, :master, :slaves, :failures
49
+
50
+
51
+ ### Perform the connections and check the lag.
52
+ ###
53
+ def check
54
+ # clear prior failures, get current xlog info
55
+ @failures = []
56
+ return unless self.get_current_wal
57
+
58
+ # check all slaves
59
+ self.slaves.each do |slave|
60
+ begin
61
+ slave_db = PG.connect(
62
+ :dbname => self.opts.database,
63
+ :host => slave,
64
+ :port => self.opts.port,
65
+ :user => self.opts.user,
66
+ :password => self.opts.pass,
67
+ :sslmode => 'prefer'
68
+ )
69
+
70
+ xlog = slave_db.exec( 'SELECT pg_last_xlog_receive_location()' ).getvalue( 0, 0 )
71
+ slave_db.close
72
+
73
+ lag_in_megs = ( self.find_lag( xlog ).to_f / 1024 / 1024 ).abs
74
+ if lag_in_megs >= LAG_ALERT
75
+ failures << { :host => slave,
76
+ :error => "%0.2fMB behind the master." % [ lag_in_megs ] }
77
+ end
78
+ rescue => err
79
+ failures << { :host => slave, :error => err.message }
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ #########
86
+ protected
87
+ #########
88
+
89
+ ### Ask the master for the current xlog information, to compare
90
+ ### to slaves. Returns true on succcess. On failure, populates
91
+ ### the failures array and returns false.
92
+ ###
93
+ def get_current_wal
94
+ master_db = PG.connect(
95
+ :dbname => self.opts.database,
96
+ :host => self.master,
97
+ :port => self.opts.port,
98
+ :user => self.opts.user,
99
+ :password => self.opts.pass,
100
+ :sslmode => 'prefer'
101
+ )
102
+
103
+ self.current_wal[ :segbytes ] = master_db.exec( 'SHOW wal_segment_size' ).
104
+ getvalue( 0, 0 ).sub( /\D+/, '' ).to_i << 20
105
+
106
+ current = master_db.exec( 'SELECT pg_current_xlog_location()' ).getvalue( 0, 0 )
107
+ self.current_wal[ :segment ], self.current_wal[ :offset ] = current.split( /\// )
108
+
109
+ master_db.close
110
+ return true
111
+
112
+ # If we can't get any of the info from the master, then there is no
113
+ # point in a comparison with slaves.
114
+ #
115
+ rescue => err
116
+ self.failures << { :host => self.master,
117
+ :error => 'Unable to retrieve required info from the master (%s)' % [ err.message ] }
118
+
119
+ return false
120
+ end
121
+
122
+
123
+ ### Given an +xlog+ position from a slave server, return
124
+ ### the number of bytes the slave needs to replay before it
125
+ ### is caught up to the master.
126
+ ###
127
+ def find_lag( xlog )
128
+ s_segment, s_offset = xlog.split( /\// )
129
+ m_segment = self.current_wal[ :segment ]
130
+ m_offset = self.current_wal[ :offset ]
131
+ m_segbytes = self.current_wal[ :segbytes ]
132
+
133
+ return (( m_segment.hex - s_segment.hex ) * m_segbytes) + ( m_offset.hex - s_offset.hex )
134
+ end
135
+
136
+ end
137
+
138
+
139
+ ### Parse command line arguments. Return a struct of global options.
140
+ ###
141
+ def parse_args( args )
142
+ options = OpenStruct.new
143
+ options.database = 'postgres'
144
+ options.port = 5432
145
+ options.user = Etc.getpwuid( Process.uid ).name
146
+ options.sslmode = 'prefer'
147
+
148
+ opts = OptionParser.new do |opts|
149
+ opts.banner = "Usage: #{$0} [options] <master> <slave> [slave2, slave3...]"
150
+
151
+ opts.separator ''
152
+ opts.separator 'Connection options:'
153
+
154
+ opts.on( '-d', '--database DBNAME',
155
+ "specify the database to connect to (default: \"#{options.database}\")" ) do |db|
156
+ options.database = db
157
+ end
158
+
159
+ opts.on( '-h', '--host HOSTNAME', 'database server host' ) do |host|
160
+ options.host = host
161
+ end
162
+
163
+ opts.on( '-p', '--port PORT', Integer,
164
+ "database server port (default: \"#{options.port}\")" ) do |port|
165
+ options.port = port
166
+ end
167
+
168
+ opts.on( '-U', '--user NAME',
169
+ "database user name (default: \"#{options.user}\")" ) do |user|
170
+ options.user = user
171
+ end
172
+
173
+ opts.on( '-W', 'force password prompt' ) do |pw|
174
+ print 'Password: '
175
+ begin
176
+ system 'stty -echo'
177
+ options.pass = $stdin.gets.chomp
178
+ ensure
179
+ system 'stty echo'
180
+ puts
181
+ end
182
+ end
183
+
184
+ opts.separator ''
185
+ opts.separator 'Other options:'
186
+
187
+ opts.on_tail( '--help', 'show this help, then exit' ) do
188
+ $stderr.puts opts
189
+ exit
190
+ end
191
+
192
+ opts.on_tail( '--version', 'output version information, then exit' ) do
193
+ puts PGMonitor::VERSION
194
+ exit
195
+ end
196
+ end
197
+
198
+ opts.parse!( args )
199
+ return options
200
+ end
201
+
202
+
203
+
204
+ if __FILE__ == $0
205
+ opts = parse_args( ARGV )
206
+ raise ArgumentError, "At least two PostgreSQL servers are required." if ARGV.length < 2
207
+ mon = PGMonitor.new( opts, ARGV )
208
+
209
+ mon.check
210
+ if mon.failures.empty?
211
+ puts "All is well!"
212
+ exit 0
213
+ else
214
+ puts "Database replication delayed or broken."
215
+ mon.failures.each do |bad|
216
+ puts "%s: %s" % [ bad[ :host ], bad[ :error ] ]
217
+ end
218
+ exit 1
219
+ end
220
+ end
221
+
222
+