cipherstash-pg 1.0.0.beta.1-arm64-darwin-21

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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/BSDL +22 -0
  3. data/Contributors.rdoc +46 -0
  4. data/Gemfile +14 -0
  5. data/History.rdoc +789 -0
  6. data/LICENSE +56 -0
  7. data/Manifest.txt +72 -0
  8. data/POSTGRES +23 -0
  9. data/README-OS_X.rdoc +68 -0
  10. data/README-Windows.rdoc +56 -0
  11. data/README.ja.rdoc +13 -0
  12. data/README.rdoc +233 -0
  13. data/Rakefile +115 -0
  14. data/certs/ged.pem +24 -0
  15. data/certs/larskanis-2022.pem +26 -0
  16. data/cipherstash-pg.gemspec +31 -0
  17. data/lib/2.7/pg_ext.bundle +0 -0
  18. data/lib/3.0/pg_ext.bundle +0 -0
  19. data/lib/3.1/pg_ext.bundle +0 -0
  20. data/lib/3.2/pg_ext.bundle +0 -0
  21. data/lib/cipherstash-pg.rb +15 -0
  22. data/lib/libpq.5.dylib +0 -0
  23. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  24. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  25. data/lib/pg/basic_type_map_for_results.rb +81 -0
  26. data/lib/pg/basic_type_registry.rb +301 -0
  27. data/lib/pg/binary_decoder.rb +23 -0
  28. data/lib/pg/coder.rb +104 -0
  29. data/lib/pg/connection.rb +878 -0
  30. data/lib/pg/constants.rb +12 -0
  31. data/lib/pg/exceptions.rb +18 -0
  32. data/lib/pg/result.rb +43 -0
  33. data/lib/pg/text_decoder.rb +46 -0
  34. data/lib/pg/text_encoder.rb +59 -0
  35. data/lib/pg/tuple.rb +30 -0
  36. data/lib/pg/type_map_by_column.rb +16 -0
  37. data/lib/pg/version.rb +4 -0
  38. data/lib/pg.rb +55 -0
  39. data/misc/openssl-pg-segfault.rb +31 -0
  40. data/misc/postgres/History.txt +9 -0
  41. data/misc/postgres/Manifest.txt +5 -0
  42. data/misc/postgres/README.txt +21 -0
  43. data/misc/postgres/Rakefile +21 -0
  44. data/misc/postgres/lib/postgres.rb +16 -0
  45. data/misc/ruby-pg/History.txt +9 -0
  46. data/misc/ruby-pg/Manifest.txt +5 -0
  47. data/misc/ruby-pg/README.txt +21 -0
  48. data/misc/ruby-pg/Rakefile +21 -0
  49. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  50. data/rakelib/task_extension.rb +46 -0
  51. data/sample/array_insert.rb +20 -0
  52. data/sample/async_api.rb +102 -0
  53. data/sample/async_copyto.rb +39 -0
  54. data/sample/async_mixed.rb +56 -0
  55. data/sample/check_conn.rb +21 -0
  56. data/sample/copydata.rb +71 -0
  57. data/sample/copyfrom.rb +81 -0
  58. data/sample/copyto.rb +19 -0
  59. data/sample/cursor.rb +21 -0
  60. data/sample/disk_usage_report.rb +177 -0
  61. data/sample/issue-119.rb +94 -0
  62. data/sample/losample.rb +69 -0
  63. data/sample/minimal-testcase.rb +17 -0
  64. data/sample/notify_wait.rb +72 -0
  65. data/sample/pg_statistics.rb +285 -0
  66. data/sample/replication_monitor.rb +222 -0
  67. data/sample/test_binary_values.rb +33 -0
  68. data/sample/wal_shipper.rb +434 -0
  69. data/sample/warehouse_partitions.rb +311 -0
  70. data/vendor/database-extensions/install.sql +317 -0
  71. data/vendor/database-extensions/uninstall.sql +20 -0
  72. metadata +118 -0
@@ -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 success. 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
+
@@ -0,0 +1,33 @@
1
+ # -*- ruby -*-1.9.1
2
+
3
+ require 'pg'
4
+
5
+ db = PG.connect( :dbname => 'test' )
6
+ db.exec "DROP TABLE IF EXISTS test"
7
+ db.exec "CREATE TABLE test (a INTEGER, b BYTEA)"
8
+
9
+ a = 42
10
+ b = [1, 2, 3]
11
+ db.exec "INSERT INTO test(a, b) VALUES($1::int, $2::bytea)",
12
+ [a, {:value => b.pack('N*'), :format => 1}]
13
+
14
+ db.exec( "SELECT a::int, b::bytea FROM test LIMIT 1", [], 1 ) do |res|
15
+
16
+ res.nfields.times do |i|
17
+ puts "Field %d is: %s, a %s (%s) column from table %p" % [
18
+ i,
19
+ res.fname( i ),
20
+ db.exec( "SELECT format_type($1,$2)", [res.ftype(i), res.fmod(1)] ).getvalue(0,0),
21
+ res.fformat( i ).zero? ? "string" : "binary",
22
+ res.ftable( i ),
23
+ ]
24
+ end
25
+
26
+ res.each do |row|
27
+ puts "a = #{row['a'].inspect}"
28
+ puts "a (unpacked) = #{row['a'].unpack('N*').inspect}"
29
+ puts "b = #{row['b'].unpack('N*').inspect}"
30
+ end
31
+ end
32
+
33
+