pg 0.15.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.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -3022
  6. data/History.rdoc +370 -4
  7. data/Manifest.txt +39 -19
  8. data/README-Windows.rdoc +17 -28
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +113 -14
  11. data/Rakefile +97 -36
  12. data/Rakefile.cross +109 -83
  13. data/ext/errorcodes.def +1032 -0
  14. data/ext/errorcodes.rb +45 -0
  15. data/ext/errorcodes.txt +494 -0
  16. data/ext/extconf.rb +55 -52
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +94 -38
  19. data/ext/pg.c +273 -121
  20. data/ext/pg.h +292 -50
  21. data/ext/pg_binary_decoder.c +229 -0
  22. data/ext/pg_binary_encoder.c +163 -0
  23. data/ext/pg_coder.c +561 -0
  24. data/ext/pg_connection.c +1811 -1051
  25. data/ext/pg_copy_coder.c +599 -0
  26. data/ext/pg_errors.c +95 -0
  27. data/ext/pg_record_coder.c +491 -0
  28. data/ext/pg_result.c +917 -203
  29. data/ext/pg_text_decoder.c +987 -0
  30. data/ext/pg_text_encoder.c +814 -0
  31. data/ext/pg_tuple.c +549 -0
  32. data/ext/pg_type_map.c +166 -0
  33. data/ext/pg_type_map_all_strings.c +116 -0
  34. data/ext/pg_type_map_by_class.c +244 -0
  35. data/ext/pg_type_map_by_column.c +313 -0
  36. data/ext/pg_type_map_by_mri_type.c +284 -0
  37. data/ext/pg_type_map_by_oid.c +356 -0
  38. data/ext/pg_type_map_in_ruby.c +299 -0
  39. data/ext/pg_util.c +149 -0
  40. data/ext/pg_util.h +65 -0
  41. data/lib/pg.rb +31 -9
  42. data/lib/pg/basic_type_mapping.rb +522 -0
  43. data/lib/pg/binary_decoder.rb +23 -0
  44. data/lib/pg/coder.rb +104 -0
  45. data/lib/pg/connection.rb +235 -30
  46. data/lib/pg/constants.rb +2 -1
  47. data/lib/pg/exceptions.rb +2 -1
  48. data/lib/pg/result.rb +33 -6
  49. data/lib/pg/text_decoder.rb +46 -0
  50. data/lib/pg/text_encoder.rb +59 -0
  51. data/lib/pg/tuple.rb +30 -0
  52. data/lib/pg/type_map_by_column.rb +16 -0
  53. data/spec/{lib/helpers.rb → helpers.rb} +154 -52
  54. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  55. data/spec/pg/connection_spec.rb +1352 -426
  56. data/spec/pg/connection_sync_spec.rb +41 -0
  57. data/spec/pg/result_spec.rb +508 -105
  58. data/spec/pg/tuple_spec.rb +333 -0
  59. data/spec/pg/type_map_by_class_spec.rb +138 -0
  60. data/spec/pg/type_map_by_column_spec.rb +226 -0
  61. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  62. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  63. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  64. data/spec/pg/type_map_spec.rb +22 -0
  65. data/spec/pg/type_spec.rb +1123 -0
  66. data/spec/pg_spec.rb +35 -16
  67. metadata +163 -84
  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,53 +36,256 @@ 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
60
+ end
61
+ else
62
+ max = CONNECT_ARGUMENT_ORDER.length
63
+ raise ArgumentError,
64
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
65
+
66
+ CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
67
+ options[ k.to_sym ] = v if v
56
68
  end
57
69
  end
58
70
 
59
- # Option string style
60
- if args.length == 1 && args.first.to_s.index( '=' )
61
- connopts.unshift( args.first )
71
+ options.merge!( hash_arg )
62
72
 
63
- # Append positional parameters
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}://" )
64
81
  else
65
- args.each_with_index do |val, i|
66
- next unless val # Skip nil placeholders
82
+ option_string += ' ' unless option_string.empty? && options.empty?
83
+ return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
84
+ end
85
+ end
86
+
87
+
88
+ # call-seq:
89
+ # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
90
+ #
91
+ # Execute a copy process for transfering data to or from the server.
92
+ #
93
+ # This issues the SQL COPY command via #exec. The response to this
94
+ # (if there is no error in the command) is a PG::Result object that
95
+ # is passed to the block, bearing a status code of PGRES_COPY_OUT or
96
+ # PGRES_COPY_IN (depending on the specified copy direction).
97
+ # The application should then use #put_copy_data or #get_copy_data
98
+ # to receive or transmit data rows and should return from the block
99
+ # when finished.
100
+ #
101
+ # #copy_data returns another PG::Result object when the data transfer
102
+ # is complete. An exception is raised if some problem was encountered,
103
+ # so it isn't required to make use of any of them.
104
+ # At this point further SQL commands can be issued via #exec.
105
+ # (It is not possible to execute other SQL commands using the same
106
+ # connection while the COPY operation is in progress.)
107
+ #
108
+ # This method ensures, that the copy process is properly terminated
109
+ # in case of client side or server side failures. Therefore, in case
110
+ # of blocking mode of operation, #copy_data is preferred to raw calls
111
+ # of #put_copy_data, #get_copy_data and #put_copy_end.
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
+ #
118
+ # Example with CSV input format:
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']
132
+ # end
133
+ #
134
+ # Example with CSV output format:
135
+ # conn.copy_data "COPY my_table TO STDOUT CSV" do
136
+ # while row=conn.get_copy_data
137
+ # p row
138
+ # end
139
+ # end
140
+ # This prints all rows of +my_table+ to stdout:
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"]
67
155
 
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)] )
156
+ def copy_data( sql, coder=nil )
157
+ res = exec( sql )
158
+
159
+ case res.result_status
160
+ when PGRES_COPY_IN
161
+ begin
162
+ if coder
163
+ old_coder = self.encoder_for_put_copy_data
164
+ self.encoder_for_put_copy_data = coder
165
+ end
166
+ yield res
167
+ rescue Exception => err
168
+ errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
169
+ put_copy_end( errmsg )
170
+ get_result
171
+ raise
172
+ else
173
+ put_copy_end
174
+ get_last_result
175
+ ensure
176
+ self.encoder_for_put_copy_data = old_coder if coder
71
177
  end
72
- end
73
178
 
74
- return connopts.join(' ')
75
- end
179
+ when PGRES_COPY_OUT
180
+ begin
181
+ if coder
182
+ old_coder = self.decoder_for_get_copy_data
183
+ self.decoder_for_get_copy_data = coder
184
+ end
185
+ yield res
186
+ rescue Exception => err
187
+ cancel
188
+ while get_copy_data
189
+ end
190
+ while get_result
191
+ end
192
+ raise
193
+ else
194
+ res = get_last_result
195
+ if !res || res.result_status != PGRES_COMMAND_OK
196
+ while get_copy_data
197
+ end
198
+ while get_result
199
+ end
200
+ raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
201
+ end
202
+ res
203
+ ensure
204
+ self.decoder_for_get_copy_data = old_coder if coder
205
+ end
76
206
 
207
+ else
208
+ raise ArgumentError, "SQL command is no COPY statement: #{sql}"
209
+ end
210
+ end
77
211
 
78
212
  # Backward-compatibility aliases for stuff that's moved into PG.
79
213
  class << self
80
214
  define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
81
215
  end
82
- end # class PG::Connection
83
216
 
84
- # Backward-compatible alias
85
- PGconn = PG::Connection
86
217
 
218
+ ### Returns an array of Hashes with connection defaults. See ::conndefaults
219
+ ### for details.
220
+ def conndefaults
221
+ return self.class.conndefaults
222
+ end
223
+
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 }
242
+
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
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