pg 0.17.1 → 1.2.3

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 (86) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +0 -3506
  5. data/History.rdoc +308 -0
  6. data/Manifest.txt +35 -19
  7. data/README-Windows.rdoc +17 -28
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +113 -14
  10. data/Rakefile +67 -30
  11. data/Rakefile.cross +109 -83
  12. data/ext/errorcodes.def +101 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +33 -2
  15. data/ext/extconf.rb +55 -58
  16. data/ext/gvl_wrappers.c +4 -0
  17. data/ext/gvl_wrappers.h +27 -39
  18. data/ext/pg.c +262 -130
  19. data/ext/pg.h +266 -54
  20. data/ext/pg_binary_decoder.c +229 -0
  21. data/ext/pg_binary_encoder.c +163 -0
  22. data/ext/pg_coder.c +561 -0
  23. data/ext/pg_connection.c +1689 -990
  24. data/ext/pg_copy_coder.c +599 -0
  25. data/ext/pg_errors.c +6 -0
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +897 -164
  28. data/ext/pg_text_decoder.c +987 -0
  29. data/ext/pg_text_encoder.c +814 -0
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +166 -0
  32. data/ext/pg_type_map_all_strings.c +116 -0
  33. data/ext/pg_type_map_by_class.c +244 -0
  34. data/ext/pg_type_map_by_column.c +313 -0
  35. data/ext/pg_type_map_by_mri_type.c +284 -0
  36. data/ext/pg_type_map_by_oid.c +356 -0
  37. data/ext/pg_type_map_in_ruby.c +299 -0
  38. data/ext/pg_util.c +149 -0
  39. data/ext/pg_util.h +65 -0
  40. data/lib/pg/basic_type_mapping.rb +522 -0
  41. data/lib/pg/binary_decoder.rb +23 -0
  42. data/lib/pg/coder.rb +104 -0
  43. data/lib/pg/connection.rb +153 -41
  44. data/lib/pg/constants.rb +2 -1
  45. data/lib/pg/exceptions.rb +2 -1
  46. data/lib/pg/result.rb +33 -6
  47. data/lib/pg/text_decoder.rb +46 -0
  48. data/lib/pg/text_encoder.rb +59 -0
  49. data/lib/pg/tuple.rb +30 -0
  50. data/lib/pg/type_map_by_column.rb +16 -0
  51. data/lib/pg.rb +29 -9
  52. data/spec/{lib/helpers.rb → helpers.rb} +151 -64
  53. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  54. data/spec/pg/connection_spec.rb +1180 -477
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +456 -120
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +138 -0
  59. data/spec/pg/type_map_by_column_spec.rb +226 -0
  60. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  61. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  62. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  63. data/spec/pg/type_map_spec.rb +22 -0
  64. data/spec/pg/type_spec.rb +1123 -0
  65. data/spec/pg_spec.rb +26 -20
  66. data.tar.gz.sig +0 -0
  67. metadata +148 -91
  68. metadata.gz.sig +0 -0
  69. data/sample/array_insert.rb +0 -20
  70. data/sample/async_api.rb +0 -106
  71. data/sample/async_copyto.rb +0 -39
  72. data/sample/async_mixed.rb +0 -56
  73. data/sample/check_conn.rb +0 -21
  74. data/sample/copyfrom.rb +0 -81
  75. data/sample/copyto.rb +0 -19
  76. data/sample/cursor.rb +0 -21
  77. data/sample/disk_usage_report.rb +0 -186
  78. data/sample/issue-119.rb +0 -94
  79. data/sample/losample.rb +0 -69
  80. data/sample/minimal-testcase.rb +0 -17
  81. data/sample/notify_wait.rb +0 -72
  82. data/sample/pg_statistics.rb +0 -294
  83. data/sample/replication_monitor.rb +0 -231
  84. data/sample/test_binary_values.rb +0 -33
  85. data/sample/wal_shipper.rb +0 -434
  86. data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module BinaryDecoder
6
+ # Convenience classes for timezone options
7
+ class TimestampUtc < Timestamp
8
+ def initialize(params={})
9
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
10
+ end
11
+ end
12
+ class TimestampUtcToLocal < Timestamp
13
+ def initialize(params={})
14
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
15
+ end
16
+ end
17
+ class TimestampLocal < Timestamp
18
+ def initialize(params={})
19
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
20
+ end
21
+ end
22
+ end
23
+ end # module PG
data/lib/pg/coder.rb ADDED
@@ -0,0 +1,104 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+
6
+ class Coder
7
+
8
+ module BinaryFormatting
9
+ Params = { format: 1 }
10
+ def initialize( params={} )
11
+ super(params.merge(Params))
12
+ end
13
+ end
14
+
15
+
16
+ # Create a new coder object based on the attribute Hash.
17
+ def initialize(params={})
18
+ params.each do |key, val|
19
+ send("#{key}=", val)
20
+ end
21
+ end
22
+
23
+ def dup
24
+ self.class.new(to_h)
25
+ end
26
+
27
+ # Returns coder attributes as Hash.
28
+ def to_h
29
+ {
30
+ oid: oid,
31
+ format: format,
32
+ flags: flags,
33
+ name: name,
34
+ }
35
+ end
36
+
37
+ def ==(v)
38
+ self.class == v.class && to_h == v.to_h
39
+ end
40
+
41
+ def marshal_dump
42
+ Marshal.dump(to_h)
43
+ end
44
+
45
+ def marshal_load(str)
46
+ initialize Marshal.load(str)
47
+ end
48
+
49
+ def inspect
50
+ str = self.to_s
51
+ oid_str = " oid=#{oid}" unless oid==0
52
+ format_str = " format=#{format}" unless format==0
53
+ name_str = " #{name.inspect}" if name
54
+ str[-1,0] = "#{name_str} #{oid_str}#{format_str}"
55
+ str
56
+ end
57
+
58
+ def inspect_short
59
+ str = case format
60
+ when 0 then "T"
61
+ when 1 then "B"
62
+ else format.to_s
63
+ end
64
+ str += "E" if respond_to?(:encode)
65
+ str += "D" if respond_to?(:decode)
66
+
67
+ "#{name || self.class.name}:#{str}"
68
+ end
69
+ end
70
+
71
+ class CompositeCoder < Coder
72
+ def to_h
73
+ super.merge!({
74
+ elements_type: elements_type,
75
+ needs_quotation: needs_quotation?,
76
+ delimiter: delimiter,
77
+ })
78
+ end
79
+
80
+ def inspect
81
+ str = super
82
+ str[-1,0] = " elements_type=#{elements_type.inspect} #{needs_quotation? ? 'needs' : 'no'} quotation"
83
+ str
84
+ end
85
+ end
86
+
87
+ class CopyCoder < Coder
88
+ def to_h
89
+ super.merge!({
90
+ type_map: type_map,
91
+ delimiter: delimiter,
92
+ null_string: null_string,
93
+ })
94
+ end
95
+ end
96
+
97
+ class RecordCoder < Coder
98
+ def to_h
99
+ super.merge!({
100
+ type_map: type_map,
101
+ })
102
+ end
103
+ end
104
+ end # module PG
data/lib/pg/connection.rb CHANGED
@@ -1,6 +1,8 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
5
+ require 'uri'
4
6
 
5
7
  # The PostgreSQL connection class. The interface for this class is based on
6
8
  # {libpq}[http://www.postgresql.org/docs/9.2/interactive/libpq.html], the C
@@ -34,48 +36,57 @@ class PG::Connection
34
36
  def self::parse_connect_args( *args )
35
37
  return '' if args.empty?
36
38
 
37
- # This will be swapped soon for code that makes options like those required for
38
- # PQconnectdbParams()/PQconnectStartParams(). For now, stick to an options string for
39
- # PQconnectdb()/PQconnectStart().
39
+ hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
40
+ option_string = ''
41
+ options = {}
40
42
 
41
43
  # Parameter 'fallback_application_name' was introduced in PostgreSQL 9.0
42
44
  # together with PQescapeLiteral().
43
- if PG::Connection.instance_methods.find{|m| m.to_sym == :escape_literal }
44
- appname = $0.sub(/^(.{30}).{4,}(.{30})$/){ $1+"..."+$2 }
45
- appname = PG::Connection.quote_connstr( appname )
46
- connopts = ["fallback_application_name=#{appname}"]
47
- else
48
- connopts = []
45
+ if PG::Connection.instance_methods.find {|m| m.to_sym == :escape_literal }
46
+ options[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
49
47
  end
50
48
 
51
- # Handle an options hash first
52
- if args.last.is_a?( Hash )
53
- opthash = args.pop
54
- opthash.each do |key, val|
55
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val)] )
49
+ if args.length == 1
50
+ case args.first
51
+ when URI, /\A#{URI::ABS_URI_REF}\z/
52
+ uri = URI(args.first)
53
+ options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
54
+ when /=/
55
+ # Option string style
56
+ option_string = args.first.to_s
57
+ else
58
+ # Positional parameters
59
+ options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
56
60
  end
57
- end
58
-
59
- # Option string style
60
- if args.length == 1 && args.first.to_s.index( '=' )
61
- connopts.unshift( args.first )
62
-
63
- # Append positional parameters
64
61
  else
65
- args.each_with_index do |val, i|
66
- next unless val # Skip nil placeholders
62
+ max = CONNECT_ARGUMENT_ORDER.length
63
+ raise ArgumentError,
64
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
67
65
 
68
- key = CONNECT_ARGUMENT_ORDER[ i ] or
69
- raise ArgumentError, "Extra positional parameter %d: %p" % [ i+1, val ]
70
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val.to_s)] )
66
+ CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
67
+ options[ k.to_sym ] = v if v
71
68
  end
72
69
  end
73
70
 
74
- return connopts.join(' ')
71
+ options.merge!( hash_arg )
72
+
73
+ if uri
74
+ uri.host = nil if options[:host]
75
+ uri.port = nil if options[:port]
76
+ uri.user = nil if options[:user]
77
+ uri.password = nil if options[:password]
78
+ uri.path = '' if options[:dbname]
79
+ uri.query = URI.encode_www_form( options )
80
+ return uri.to_s.sub( /^#{uri.scheme}:(?!\/\/)/, "#{uri.scheme}://" )
81
+ else
82
+ option_string += ' ' unless option_string.empty? && options.empty?
83
+ return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
84
+ end
75
85
  end
76
86
 
87
+
77
88
  # call-seq:
78
- # conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
89
+ # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
79
90
  #
80
91
  # Execute a copy process for transfering data to or from the server.
81
92
  #
@@ -99,13 +110,26 @@ class PG::Connection
99
110
  # of blocking mode of operation, #copy_data is preferred to raw calls
100
111
  # of #put_copy_data, #get_copy_data and #put_copy_end.
101
112
  #
113
+ # _coder_ can be a PG::Coder derivation
114
+ # (typically PG::TextEncoder::CopyRow or PG::TextDecoder::CopyRow).
115
+ # This enables encoding of data fields given to #put_copy_data
116
+ # or decoding of fields received by #get_copy_data.
117
+ #
102
118
  # Example with CSV input format:
103
- # conn.exec "create table my_table (a text,b text,c text,d text,e text)"
104
- # conn.copy_data "COPY my_table FROM STDOUT CSV" do
105
- # conn.put_copy_data "some,csv,data,to,copy\n"
106
- # conn.put_copy_data "more,csv,data,to,copy\n"
119
+ # conn.exec "create table my_table (a text,b text,c text,d text)"
120
+ # conn.copy_data "COPY my_table FROM STDIN CSV" do
121
+ # conn.put_copy_data "some,data,to,copy\n"
122
+ # conn.put_copy_data "more,data,to,copy\n"
123
+ # end
124
+ # This creates +my_table+ and inserts two CSV rows.
125
+ #
126
+ # The same with text format encoder PG::TextEncoder::CopyRow
127
+ # and Array input:
128
+ # enco = PG::TextEncoder::CopyRow.new
129
+ # conn.copy_data "COPY my_table FROM STDIN", enco do
130
+ # conn.put_copy_data ['some', 'data', 'to', 'copy']
131
+ # conn.put_copy_data ['more', 'data', 'to', 'copy']
107
132
  # end
108
- # This creates +my_table+ and inserts two rows.
109
133
  #
110
134
  # Example with CSV output format:
111
135
  # conn.copy_data "COPY my_table TO STDOUT CSV" do
@@ -114,14 +138,31 @@ class PG::Connection
114
138
  # end
115
139
  # end
116
140
  # This prints all rows of +my_table+ to stdout:
117
- # "some,csv,data,to,copy\n"
118
- # "more,csv,data,to,copy\n"
119
- def copy_data( sql )
141
+ # "some,data,to,copy\n"
142
+ # "more,data,to,copy\n"
143
+ #
144
+ # The same with text format decoder PG::TextDecoder::CopyRow
145
+ # and Array output:
146
+ # deco = PG::TextDecoder::CopyRow.new
147
+ # conn.copy_data "COPY my_table TO STDOUT", deco do
148
+ # while row=conn.get_copy_data
149
+ # p row
150
+ # end
151
+ # end
152
+ # This receives all rows of +my_table+ as ruby array:
153
+ # ["some", "data", "to", "copy"]
154
+ # ["more", "data", "to", "copy"]
155
+
156
+ def copy_data( sql, coder=nil )
120
157
  res = exec( sql )
121
158
 
122
159
  case res.result_status
123
160
  when PGRES_COPY_IN
124
161
  begin
162
+ if coder
163
+ old_coder = self.encoder_for_put_copy_data
164
+ self.encoder_for_put_copy_data = coder
165
+ end
125
166
  yield res
126
167
  rescue Exception => err
127
168
  errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
@@ -131,10 +172,16 @@ class PG::Connection
131
172
  else
132
173
  put_copy_end
133
174
  get_last_result
175
+ ensure
176
+ self.encoder_for_put_copy_data = old_coder if coder
134
177
  end
135
178
 
136
179
  when PGRES_COPY_OUT
137
180
  begin
181
+ if coder
182
+ old_coder = self.decoder_for_get_copy_data
183
+ self.decoder_for_get_copy_data = coder
184
+ end
138
185
  yield res
139
186
  rescue Exception => err
140
187
  cancel
@@ -145,7 +192,7 @@ class PG::Connection
145
192
  raise
146
193
  else
147
194
  res = get_last_result
148
- if res.result_status != PGRES_COMMAND_OK
195
+ if !res || res.result_status != PGRES_COMMAND_OK
149
196
  while get_copy_data
150
197
  end
151
198
  while get_result
@@ -153,6 +200,8 @@ class PG::Connection
153
200
  raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
154
201
  end
155
202
  res
203
+ ensure
204
+ self.decoder_for_get_copy_data = old_coder if coder
156
205
  end
157
206
 
158
207
  else
@@ -172,8 +221,71 @@ class PG::Connection
172
221
  return self.class.conndefaults
173
222
  end
174
223
 
175
- end # class PG::Connection
224
+ ### Return the Postgres connection defaults structure as a Hash keyed by option
225
+ ### keyword (as a Symbol).
226
+ ###
227
+ ### See also #conndefaults
228
+ def self.conndefaults_hash
229
+ return self.conndefaults.each_with_object({}) do |info, hash|
230
+ hash[ info[:keyword].to_sym ] = info[:val]
231
+ end
232
+ end
233
+
234
+ ### Returns a Hash with connection defaults. See ::conndefaults_hash
235
+ ### for details.
236
+ def conndefaults_hash
237
+ return self.class.conndefaults_hash
238
+ end
239
+
240
+ # Method 'conninfo' was introduced in PostgreSQL 9.3.
241
+ if self.instance_methods.find{|m| m.to_sym == :conninfo }
176
242
 
177
- # Backward-compatible alias
178
- PGconn = PG::Connection
243
+ ### Return the Postgres connection info structure as a Hash keyed by option
244
+ ### keyword (as a Symbol).
245
+ ###
246
+ ### See also #conninfo
247
+ def conninfo_hash
248
+ return self.conninfo.each_with_object({}) do |info, hash|
249
+ hash[ info[:keyword].to_sym ] = info[:val]
250
+ end
251
+ end
252
+ end
179
253
 
254
+ # Method 'ssl_attribute' was introduced in PostgreSQL 9.5.
255
+ if self.instance_methods.find{|m| m.to_sym == :ssl_attribute }
256
+ # call-seq:
257
+ # conn.ssl_attributes -> Hash<String,String>
258
+ #
259
+ # Returns SSL-related information about the connection as key/value pairs
260
+ #
261
+ # The available attributes varies depending on the SSL library being used,
262
+ # and the type of connection.
263
+ #
264
+ # See also #ssl_attribute
265
+ def ssl_attributes
266
+ ssl_attribute_names.each.with_object({}) do |n,h|
267
+ h[n] = ssl_attribute(n)
268
+ end
269
+ end
270
+ end
271
+
272
+ REDIRECT_METHODS = {
273
+ :exec => [:async_exec, :sync_exec],
274
+ :query => [:async_exec, :sync_exec],
275
+ :exec_params => [:async_exec_params, :sync_exec_params],
276
+ :prepare => [:async_prepare, :sync_prepare],
277
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
278
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
279
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
280
+ }
281
+
282
+ def self.async_api=(enable)
283
+ REDIRECT_METHODS.each do |ali, (async, sync)|
284
+ remove_method(ali) if method_defined?(ali)
285
+ alias_method( ali, enable ? async : sync )
286
+ end
287
+ end
288
+
289
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
290
+ self.async_api = true
291
+ end # class PG::Connection
data/lib/pg/constants.rb CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
data/lib/pg/exceptions.rb CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
data/lib/pg/result.rb CHANGED
@@ -1,16 +1,43 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
5
6
 
6
7
  class PG::Result
7
8
 
8
- ### Returns all tuples as an array of arrays
9
- def values
10
- return enum_for(:each_row).to_a
9
+ # Apply a type map for all value retrieving methods.
10
+ #
11
+ # +type_map+: a PG::TypeMap instance.
12
+ #
13
+ # This method is equal to #type_map= , but returns self, so that calls can be chained.
14
+ #
15
+ # See also PG::BasicTypeMapForResults
16
+ def map_types!(type_map)
17
+ self.type_map = type_map
18
+ return self
19
+ end
20
+
21
+ # Set the data type for all field name returning methods.
22
+ #
23
+ # +type+: a Symbol defining the field name type.
24
+ #
25
+ # This method is equal to #field_name_type= , but returns self, so that calls can be chained.
26
+ def field_names_as(type)
27
+ self.field_name_type = type
28
+ return self
29
+ end
30
+
31
+ ### Return a String representation of the object suitable for debugging.
32
+ def inspect
33
+ str = self.to_s
34
+ str[-1,0] = if cleared?
35
+ " cleared"
36
+ else
37
+ " status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
38
+ end
39
+ return str
11
40
  end
12
41
 
13
42
  end # class PG::Result
14
43
 
15
- # Backward-compatible alias
16
- PGresult = PG::Result