pg 1.6.0.rc1-x86_64-linux

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 (118) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +4 -0
  3. data/BSDL +22 -0
  4. data/Contributors.rdoc +46 -0
  5. data/Gemfile +23 -0
  6. data/History.md +958 -0
  7. data/LICENSE +56 -0
  8. data/Manifest.txt +72 -0
  9. data/POSTGRES +23 -0
  10. data/README-OS_X.rdoc +68 -0
  11. data/README-Windows.rdoc +56 -0
  12. data/README.ja.md +300 -0
  13. data/README.md +286 -0
  14. data/Rakefile +161 -0
  15. data/certs/ged.pem +24 -0
  16. data/certs/kanis@comcard.de.pem +20 -0
  17. data/certs/larskanis-2022.pem +26 -0
  18. data/certs/larskanis-2023.pem +24 -0
  19. data/certs/larskanis-2024.pem +24 -0
  20. data/ext/errorcodes.def +1043 -0
  21. data/ext/errorcodes.rb +45 -0
  22. data/ext/errorcodes.txt +494 -0
  23. data/ext/extconf.rb +282 -0
  24. data/ext/gvl_wrappers.c +32 -0
  25. data/ext/gvl_wrappers.h +297 -0
  26. data/ext/pg.c +703 -0
  27. data/ext/pg.h +390 -0
  28. data/ext/pg_binary_decoder.c +460 -0
  29. data/ext/pg_binary_encoder.c +583 -0
  30. data/ext/pg_cancel_connection.c +360 -0
  31. data/ext/pg_coder.c +622 -0
  32. data/ext/pg_connection.c +4869 -0
  33. data/ext/pg_copy_coder.c +921 -0
  34. data/ext/pg_errors.c +95 -0
  35. data/ext/pg_record_coder.c +522 -0
  36. data/ext/pg_result.c +1764 -0
  37. data/ext/pg_text_decoder.c +1008 -0
  38. data/ext/pg_text_encoder.c +833 -0
  39. data/ext/pg_tuple.c +572 -0
  40. data/ext/pg_type_map.c +200 -0
  41. data/ext/pg_type_map_all_strings.c +130 -0
  42. data/ext/pg_type_map_by_class.c +271 -0
  43. data/ext/pg_type_map_by_column.c +355 -0
  44. data/ext/pg_type_map_by_mri_type.c +313 -0
  45. data/ext/pg_type_map_by_oid.c +388 -0
  46. data/ext/pg_type_map_in_ruby.c +333 -0
  47. data/ext/pg_util.c +149 -0
  48. data/ext/pg_util.h +65 -0
  49. data/ext/vc/pg.sln +26 -0
  50. data/ext/vc/pg_18/pg.vcproj +216 -0
  51. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  52. data/lib/2.7/pg_ext.so +0 -0
  53. data/lib/3.0/pg_ext.so +0 -0
  54. data/lib/3.1/pg_ext.so +0 -0
  55. data/lib/3.2/pg_ext.so +0 -0
  56. data/lib/3.3/pg_ext.so +0 -0
  57. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  58. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  59. data/lib/pg/basic_type_map_for_results.rb +104 -0
  60. data/lib/pg/basic_type_registry.rb +311 -0
  61. data/lib/pg/binary_decoder/date.rb +9 -0
  62. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  63. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  64. data/lib/pg/cancel_connection.rb +30 -0
  65. data/lib/pg/coder.rb +106 -0
  66. data/lib/pg/connection.rb +1027 -0
  67. data/lib/pg/exceptions.rb +31 -0
  68. data/lib/pg/result.rb +43 -0
  69. data/lib/pg/text_decoder/date.rb +21 -0
  70. data/lib/pg/text_decoder/inet.rb +9 -0
  71. data/lib/pg/text_decoder/json.rb +17 -0
  72. data/lib/pg/text_decoder/numeric.rb +9 -0
  73. data/lib/pg/text_decoder/timestamp.rb +30 -0
  74. data/lib/pg/text_encoder/date.rb +13 -0
  75. data/lib/pg/text_encoder/inet.rb +31 -0
  76. data/lib/pg/text_encoder/json.rb +17 -0
  77. data/lib/pg/text_encoder/numeric.rb +9 -0
  78. data/lib/pg/text_encoder/timestamp.rb +24 -0
  79. data/lib/pg/tuple.rb +30 -0
  80. data/lib/pg/type_map_by_column.rb +16 -0
  81. data/lib/pg/version.rb +4 -0
  82. data/lib/pg.rb +144 -0
  83. data/misc/openssl-pg-segfault.rb +31 -0
  84. data/misc/postgres/History.txt +9 -0
  85. data/misc/postgres/Manifest.txt +5 -0
  86. data/misc/postgres/README.txt +21 -0
  87. data/misc/postgres/Rakefile +21 -0
  88. data/misc/postgres/lib/postgres.rb +16 -0
  89. data/misc/ruby-pg/History.txt +9 -0
  90. data/misc/ruby-pg/Manifest.txt +5 -0
  91. data/misc/ruby-pg/README.txt +21 -0
  92. data/misc/ruby-pg/Rakefile +21 -0
  93. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  94. data/pg.gemspec +36 -0
  95. data/ports/x86_64-linux/lib/libpq-ruby-pg.so.1 +0 -0
  96. data/rakelib/task_extension.rb +46 -0
  97. data/sample/array_insert.rb +20 -0
  98. data/sample/async_api.rb +102 -0
  99. data/sample/async_copyto.rb +39 -0
  100. data/sample/async_mixed.rb +56 -0
  101. data/sample/check_conn.rb +21 -0
  102. data/sample/copydata.rb +71 -0
  103. data/sample/copyfrom.rb +81 -0
  104. data/sample/copyto.rb +19 -0
  105. data/sample/cursor.rb +21 -0
  106. data/sample/disk_usage_report.rb +177 -0
  107. data/sample/issue-119.rb +94 -0
  108. data/sample/losample.rb +69 -0
  109. data/sample/minimal-testcase.rb +17 -0
  110. data/sample/notify_wait.rb +72 -0
  111. data/sample/pg_statistics.rb +285 -0
  112. data/sample/replication_monitor.rb +222 -0
  113. data/sample/test_binary_values.rb +33 -0
  114. data/sample/wal_shipper.rb +434 -0
  115. data/sample/warehouse_partitions.rb +311 -0
  116. data.tar.gz.sig +0 -0
  117. metadata +252 -0
  118. metadata.gz.sig +0 -0
@@ -0,0 +1,67 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common PostgreSQL types from Ruby
7
+ # to PostgreSQL.
8
+ #
9
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
10
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapBasedOnResult.new .
11
+ #
12
+ # This class works equal to PG::BasicTypeMapForResults, but does not define decoders for
13
+ # the given result OIDs, but encoders. So it can be used to type cast field values based on
14
+ # the type OID retrieved by a separate SQL query.
15
+ #
16
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate a result independent
17
+ # PG::TypeMapByColumn type map, which can subsequently be used to cast query bind parameters
18
+ # or #put_copy_data fields.
19
+ #
20
+ # Example:
21
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
22
+ #
23
+ # # Retrieve table OIDs per empty result set.
24
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
25
+ # # Build a type map for common ruby to database type encoders.
26
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
27
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
28
+ # tm = btm.build_column_map( res )
29
+ # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
30
+ #
31
+ # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
32
+ # conn.put_copy_data ['a', 123, [5,4,3]]
33
+ # end
34
+ # This inserts a single row into copytable with type casts from ruby to
35
+ # database types using text format.
36
+ #
37
+ # Very similar with binary format:
38
+ #
39
+ # conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, blob bytea, created_at timestamp)" )
40
+ # # Retrieve table OIDs per empty result set in binary format.
41
+ # res = conn.exec_params( "SELECT * FROM copytable LIMIT 0", [], 1 )
42
+ # # Build a type map for common ruby to database type encoders.
43
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
44
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
45
+ # tm = btm.build_column_map( res )
46
+ # row_encoder = PG::BinaryEncoder::CopyRow.new type_map: tm
47
+ #
48
+ # conn.copy_data( "COPY copytable FROM STDIN WITH (FORMAT binary)", row_encoder ) do |res|
49
+ # conn.put_copy_data ['a', 123, "\xff\x00".b, Time.now]
50
+ # end
51
+ #
52
+ # This inserts a single row into copytable with type casts from ruby to
53
+ # database types using binary copy and value format.
54
+ # Binary COPY is faster than text format but less portable and less readable and pg offers fewer en-/decoders of database types.
55
+ #
56
+ class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
57
+ include PG::BasicTypeRegistry::Checker
58
+
59
+ def initialize(connection_or_coder_maps, registry: nil)
60
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
61
+
62
+ # Populate TypeMapByOid hash with encoders
63
+ @coder_maps.each_format(:encoder).flat_map{|f| f.coders }.each do |coder|
64
+ add_coder(coder)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,202 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common Ruby types to PostgreSQL.
7
+ #
8
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
9
+ # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
10
+ #
11
+ # Query params are type casted based on the class of the given value.
12
+ #
13
+ # Higher level libraries will most likely not make use of this class, but use their
14
+ # own derivation of PG::TypeMapByClass or another set of rules to choose suitable
15
+ # encoders and decoders for the values to be sent.
16
+ #
17
+ # Example:
18
+ # conn = PG::Connection.new
19
+ # # Assign a default ruleset for type casts of input and output values.
20
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
21
+ # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
22
+ # # The format of the parameter is set to 0 (text) and the OID of this parameter is set to 20 (int8).
23
+ # res = conn.exec_params( "SELECT $1", [5] )
24
+ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
25
+ # Helper class for submission of binary strings into bytea columns.
26
+ #
27
+ # Since PG::BasicTypeMapForQueries chooses the encoder to be used by the class of the submitted value,
28
+ # it's necessary to send binary strings as BinaryData.
29
+ # That way they're distinct from text strings.
30
+ # Please note however that PG::BasicTypeMapForResults delivers bytea columns as plain String
31
+ # with binary encoding.
32
+ #
33
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
34
+ # conn.exec("CREATE TEMP TABLE test (data bytea)")
35
+ # bd = PG::BasicTypeMapForQueries::BinaryData.new("ab\xff\0cd")
36
+ # conn.exec_params("INSERT INTO test (data) VALUES ($1)", [bd])
37
+ class BinaryData < String
38
+ end
39
+
40
+ class UndefinedEncoder < RuntimeError
41
+ end
42
+
43
+ include PG::BasicTypeRegistry::Checker
44
+
45
+ # Create a new type map for query submission
46
+ #
47
+ # Options:
48
+ # * +registry+: Custom type registry, nil for default global registry
49
+ # * +if_undefined+: Optional +Proc+ object which is called, if no type for an parameter class is not defined in the registry.
50
+ # The +Proc+ object is called with the name and format of the missing type.
51
+ # Its return value is not used.
52
+ def initialize(connection_or_coder_maps, registry: nil, if_undefined: nil)
53
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
54
+ @array_encoders_by_klass = array_encoders_by_klass
55
+ @encode_array_as = :array
56
+ @if_undefined = if_undefined || method(:raise_undefined_type).to_proc
57
+ init_encoders
58
+ end
59
+
60
+ private def raise_undefined_type(oid_name, format)
61
+ raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
62
+ end
63
+
64
+ # Change the mechanism that is used to encode ruby array values
65
+ #
66
+ # Possible values:
67
+ # * +:array+ : Encode the ruby array as a PostgreSQL array.
68
+ # The array element type is inferred from the class of the first array element. This is the default.
69
+ # * +:json+ : Encode the ruby array as a JSON document.
70
+ # * +:record+ : Encode the ruby array as a composite type row.
71
+ # * <code>"_type"</code> : Encode the ruby array as a particular PostgreSQL type.
72
+ # All PostgreSQL array types are supported.
73
+ # If there's an encoder registered for the elements +type+, it will be used.
74
+ # Otherwise a string conversion (by +value.to_s+) is done.
75
+ def encode_array_as=(pg_type)
76
+ case pg_type
77
+ when :array
78
+ when :json
79
+ when :record
80
+ when /\A_/
81
+ else
82
+ raise ArgumentError, "invalid pg_type #{pg_type.inspect}"
83
+ end
84
+
85
+ @encode_array_as = pg_type
86
+
87
+ init_encoders
88
+ end
89
+
90
+ attr_reader :encode_array_as
91
+
92
+ private
93
+
94
+ def init_encoders
95
+ coders.each { |kl, c| self[kl] = nil } # Clear type map
96
+ populate_encoder_list
97
+ @textarray_encoder = coder_by_name(0, :encoder, '_text')
98
+ end
99
+
100
+ def coder_by_name(format, direction, name)
101
+ check_format_and_direction(format, direction)
102
+ @coder_maps.map_for(format, direction).coder_by_name(name)
103
+ end
104
+
105
+ def undefined(name, format)
106
+ @if_undefined.call(name, format)
107
+ end
108
+
109
+ def populate_encoder_list
110
+ DEFAULT_TYPE_MAP.each do |klass, selector|
111
+ if Array === selector
112
+ format, name, oid_name = selector
113
+ coder = coder_by_name(format, :encoder, name).dup
114
+ if coder
115
+ if oid_name
116
+ oid_coder = coder_by_name(format, :encoder, oid_name)
117
+ if oid_coder
118
+ coder.oid = oid_coder.oid
119
+ else
120
+ undefined(oid_name, format)
121
+ end
122
+ else
123
+ coder.oid = 0
124
+ end
125
+ self[klass] = coder
126
+ else
127
+ undefined(name, format)
128
+ end
129
+ else
130
+
131
+ case @encode_array_as
132
+ when :array
133
+ self[klass] = selector
134
+ when :json
135
+ self[klass] = PG::TextEncoder::JSON.new
136
+ when :record
137
+ self[klass] = PG::TextEncoder::Record.new type_map: self
138
+ when /\A_/
139
+ coder = coder_by_name(0, :encoder, @encode_array_as)
140
+ if coder
141
+ self[klass] = coder
142
+ else
143
+ undefined(@encode_array_as, format)
144
+ end
145
+ else
146
+ raise ArgumentError, "invalid pg_type #{@encode_array_as.inspect}"
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def array_encoders_by_klass
153
+ DEFAULT_ARRAY_TYPE_MAP.inject({}) do |h, (klass, (format, name))|
154
+ h[klass] = coder_by_name(format, :encoder, name)
155
+ h
156
+ end
157
+ end
158
+
159
+ def get_array_type(value)
160
+ elem = value
161
+ while elem.kind_of?(Array)
162
+ elem = elem.first
163
+ end
164
+ @array_encoders_by_klass[elem.class] ||
165
+ elem.class.ancestors.lazy.map{|ancestor| @array_encoders_by_klass[ancestor] }.find{|a| a } ||
166
+ @textarray_encoder
167
+ end
168
+
169
+ begin
170
+ PG.require_bigdecimal_without_warning
171
+ has_bigdecimal = true
172
+ rescue LoadError
173
+ end
174
+
175
+ DEFAULT_TYPE_MAP = PG.make_shareable({
176
+ TrueClass => [1, 'bool', 'bool'],
177
+ FalseClass => [1, 'bool', 'bool'],
178
+ # We use text format and no type OID for numbers, because setting the OID can lead
179
+ # to unnecessary type conversions on server side.
180
+ Integer => [0, 'int8'],
181
+ Float => [0, 'float8'],
182
+ Time => [0, 'timestamptz'],
183
+ # We use text format and no type OID for IPAddr, because setting the OID can lead
184
+ # to unnecessary inet/cidr conversions on the server side.
185
+ IPAddr => [0, 'inet'],
186
+ Hash => [0, 'json'],
187
+ Array => :get_array_type,
188
+ BinaryData => [1, 'bytea'],
189
+ }.merge(has_bigdecimal ? {BigDecimal => [0, 'numeric']} : {}))
190
+ private_constant :DEFAULT_TYPE_MAP
191
+
192
+ DEFAULT_ARRAY_TYPE_MAP = PG.make_shareable({
193
+ TrueClass => [0, '_bool'],
194
+ FalseClass => [0, '_bool'],
195
+ Integer => [0, '_int8'],
196
+ String => [0, '_text'],
197
+ Float => [0, '_float8'],
198
+ Time => [0, '_timestamptz'],
199
+ IPAddr => [0, '_inet'],
200
+ }.merge(has_bigdecimal ? {BigDecimal => [0, '_numeric']} : {}))
201
+ private_constant :DEFAULT_ARRAY_TYPE_MAP
202
+ end
@@ -0,0 +1,104 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # Simple set of rules for type casting common PostgreSQL types to Ruby.
7
+ #
8
+ # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
9
+ # PostgreSQL's +pg_type+ table in PG::BasicTypeMapForResults.new .
10
+ #
11
+ # Result values are type casted based on the type OID of the given result column.
12
+ #
13
+ # Higher level libraries will most likely not make use of this class, but use their
14
+ # own set of rules to choose suitable encoders and decoders.
15
+ #
16
+ # Example:
17
+ # conn = PG::Connection.new
18
+ # # Assign a default ruleset for type casts of output values.
19
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
20
+ # # Execute a query.
21
+ # res = conn.exec_params( "SELECT $1::INT", ['5'] )
22
+ # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
23
+ # # is done by PG::TextDecoder::Integer internally for all value retrieval methods.
24
+ # res.values # => [[5]]
25
+ #
26
+ # PG::TypeMapByOid#build_column_map(result) can be used to generate
27
+ # a result independent PG::TypeMapByColumn type map, which can subsequently be used
28
+ # to cast #get_copy_data fields:
29
+ #
30
+ # For the following table:
31
+ # conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '{5,4,3}'::INT[])" )
32
+ #
33
+ # # Retrieve table OIDs per empty result set.
34
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
35
+ # # Build a type map for common database to ruby type decoders.
36
+ # btm = PG::BasicTypeMapForResults.new(conn)
37
+ # # Build a PG::TypeMapByColumn with decoders suitable for copytable.
38
+ # tm = btm.build_column_map( res )
39
+ # row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
40
+ #
41
+ # conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
42
+ # while row=conn.get_copy_data
43
+ # p row
44
+ # end
45
+ # end
46
+ # This prints the rows with type casted columns:
47
+ # ["a", 123, [5, 4, 3]]
48
+ #
49
+ # Very similar with binary format:
50
+ #
51
+ # conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '2023-03-19 18:39:44'::TIMESTAMP)" )
52
+ #
53
+ # # Retrieve table OIDs per empty result set in binary format.
54
+ # res = conn.exec_params( "SELECT * FROM copytable LIMIT 0", [], 1 )
55
+ # # Build a type map for common database to ruby type decoders.
56
+ # btm = PG::BasicTypeMapForResults.new(conn)
57
+ # # Build a PG::TypeMapByColumn with decoders suitable for copytable.
58
+ # tm = btm.build_column_map( res )
59
+ # row_decoder = PG::BinaryDecoder::CopyRow.new type_map: tm
60
+ #
61
+ # conn.copy_data( "COPY copytable TO STDOUT WITH (FORMAT binary)", row_decoder ) do |res|
62
+ # while row=conn.get_copy_data
63
+ # p row
64
+ # end
65
+ # end
66
+ # This prints the rows with type casted columns:
67
+ # ["a", 123, 2023-03-19 18:39:44 UTC]
68
+ #
69
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
70
+ class PG::BasicTypeMapForResults < PG::TypeMapByOid
71
+ include PG::BasicTypeRegistry::Checker
72
+
73
+ class WarningTypeMap < PG::TypeMapInRuby
74
+ def initialize(typenames)
75
+ @already_warned = {}
76
+ @typenames_by_oid = typenames
77
+ end
78
+
79
+ def typecast_result_value(result, _tuple, field)
80
+ format = result.fformat(field)
81
+ oid = result.ftype(field)
82
+ unless @already_warned.dig(format, oid)
83
+ warn "Warning: no type cast defined for type #{@typenames_by_oid[oid].inspect} format #{format} with oid #{oid}. Please cast this type explicitly to TEXT to be safe for future changes."
84
+ unless frozen?
85
+ @already_warned[format] ||= {}
86
+ @already_warned[format][oid] = true
87
+ end
88
+ end
89
+ super
90
+ end
91
+ end
92
+
93
+ def initialize(connection_or_coder_maps, registry: nil)
94
+ @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
95
+
96
+ # Populate TypeMapByOid hash with decoders
97
+ @coder_maps.each_format(:decoder).flat_map{|f| f.coders }.each do |coder|
98
+ add_coder(coder)
99
+ end
100
+
101
+ typenames = @coder_maps.typenames_by_oid
102
+ self.default_type_map = WarningTypeMap.new(typenames)
103
+ end
104
+ end
@@ -0,0 +1,311 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+ # This class defines the mapping between PostgreSQL types and encoder/decoder classes for PG::BasicTypeMapForResults, PG::BasicTypeMapForQueries and PG::BasicTypeMapBasedOnResult.
7
+ #
8
+ # Additional types can be added like so:
9
+ #
10
+ # require 'pg'
11
+ # require 'ipaddr'
12
+ #
13
+ # class InetDecoder < PG::SimpleDecoder
14
+ # def decode(string, tuple=nil, field=nil)
15
+ # IPAddr.new(string)
16
+ # end
17
+ # end
18
+ # class InetEncoder < PG::SimpleEncoder
19
+ # def encode(ip_addr)
20
+ # ip_addr.to_s
21
+ # end
22
+ # end
23
+ #
24
+ # conn = PG.connect
25
+ # regi = PG::BasicTypeRegistry.new.register_default_types
26
+ # regi.register_type(0, 'inet', InetEncoder, InetDecoder)
27
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: regi)
28
+ class PG::BasicTypeRegistry
29
+ # An instance of this class stores the coders that should be used for a particular wire format (text or binary)
30
+ # and type cast direction (encoder or decoder).
31
+ #
32
+ # Each coder object is filled with the PostgreSQL type name, OID, wire format and array coders are filled with the base elements_type.
33
+ class CoderMap
34
+ # Hash of text types that don't require quotation, when used within composite types.
35
+ # type.name => true
36
+ DONT_QUOTE_TYPES = %w[
37
+ int2 int4 int8
38
+ float4 float8
39
+ oid
40
+ bool
41
+ date timestamp timestamptz
42
+ ].inject({}){|h,e| h[e] = true; h }.freeze
43
+ private_constant :DONT_QUOTE_TYPES
44
+
45
+ def initialize(result, coders_by_name, format, arraycoder)
46
+ coder_map = {}
47
+
48
+ arrays, nodes = result.partition { |row| row['typinput'] == 'array_in' }
49
+
50
+ # populate the base types
51
+ nodes.find_all { |row| coders_by_name.key?(row['typname']) }.each do |row|
52
+ coder = coders_by_name[row['typname']].dup
53
+ coder.oid = row['oid'].to_i
54
+ coder.name = row['typname']
55
+ coder.format = format
56
+ coder_map[coder.oid] = coder.freeze
57
+ end
58
+
59
+ if arraycoder
60
+ # populate array types
61
+ arrays.each do |row|
62
+ elements_coder = coder_map[row['typelem'].to_i]
63
+ next unless elements_coder
64
+
65
+ coder = arraycoder.new
66
+ coder.oid = row['oid'].to_i
67
+ coder.name = row['typname']
68
+ coder.format = format
69
+ coder.elements_type = elements_coder
70
+ coder.needs_quotation = !DONT_QUOTE_TYPES[elements_coder.name]
71
+ coder_map[coder.oid] = coder.freeze
72
+ end
73
+ end
74
+
75
+ @coders = coder_map.values.freeze
76
+ @coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }.freeze
77
+ @coders_by_oid = @coders.inject({}){|h, t| h[t.oid] = t; h }.freeze
78
+ freeze
79
+ end
80
+
81
+ attr_reader :coders
82
+ attr_reader :coders_by_oid
83
+ attr_reader :coders_by_name
84
+
85
+ def coder_by_name(name)
86
+ @coders_by_name[name]
87
+ end
88
+
89
+ def coder_by_oid(oid)
90
+ @coders_by_oid[oid]
91
+ end
92
+ end
93
+
94
+ # An instance of this class stores CoderMap instances to be used for text and binary wire formats
95
+ # as well as encoder and decoder directions.
96
+ #
97
+ # A PG::BasicTypeRegistry::CoderMapsBundle instance retrieves all type definitions from the PostgreSQL server and matches them with the coder definitions of the global PG::BasicTypeRegistry .
98
+ # It provides 4 separate CoderMap instances for the combinations of the two formats and directions.
99
+ #
100
+ # A PG::BasicTypeRegistry::CoderMapsBundle instance can be used to initialize an instance of
101
+ # * PG::BasicTypeMapForResults
102
+ # * PG::BasicTypeMapForQueries
103
+ # * PG::BasicTypeMapBasedOnResult
104
+ # by passing it instead of the connection object like so:
105
+ #
106
+ # conn = PG::Connection.new
107
+ # maps = PG::BasicTypeRegistry::CoderMapsBundle.new(conn)
108
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(maps)
109
+ #
110
+ class CoderMapsBundle
111
+ attr_reader :typenames_by_oid
112
+
113
+ def initialize(connection, registry: nil)
114
+ registry ||= DEFAULT_TYPE_REGISTRY
115
+
116
+ result = connection.exec(<<-SQL).to_a
117
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, ti.proname AS typinput
118
+ FROM pg_type as t
119
+ JOIN pg_proc as ti ON ti.oid = t.typinput
120
+ SQL
121
+
122
+ init_maps(registry, result.freeze)
123
+ freeze
124
+ end
125
+
126
+ private def init_maps(registry, result)
127
+ @maps = [
128
+ [0, :encoder, PG::TextEncoder::Array],
129
+ [0, :decoder, PG::TextDecoder::Array],
130
+ [1, :encoder, PG::BinaryEncoder::Array],
131
+ [1, :decoder, PG::BinaryDecoder::Array],
132
+ ].inject([]) do |h, (format, direction, arraycoder)|
133
+ coders = registry.coders_for(format, direction) || {}
134
+ h[format] ||= {}
135
+ h[format][direction] = CoderMap.new(result, coders, format, arraycoder)
136
+ h
137
+ end.each{|h| h.freeze }.freeze
138
+
139
+ @typenames_by_oid = result.inject({}){|h, t| h[t['oid'].to_i] = t['typname']; h }.freeze
140
+ end
141
+
142
+ def each_format(direction)
143
+ @maps.map { |f| f[direction] }
144
+ end
145
+
146
+ def map_for(format, direction)
147
+ @maps[format][direction]
148
+ end
149
+ end
150
+
151
+ module Checker
152
+ ValidFormats = { 0 => true, 1 => true }.freeze
153
+ ValidDirections = { :encoder => true, :decoder => true }.freeze
154
+ private_constant :ValidFormats, :ValidDirections
155
+
156
+ protected def check_format_and_direction(format, direction)
157
+ raise(ArgumentError, "Invalid format value %p" % format) unless ValidFormats[format]
158
+ raise(ArgumentError, "Invalid direction %p" % direction) unless ValidDirections[direction]
159
+ end
160
+
161
+ protected def build_coder_maps(conn_or_maps, registry: nil)
162
+ if conn_or_maps.is_a?(PG::BasicTypeRegistry::CoderMapsBundle)
163
+ raise ArgumentError, "registry argument must be given to CoderMapsBundle" if registry
164
+ conn_or_maps
165
+ else
166
+ PG::BasicTypeRegistry::CoderMapsBundle.new(conn_or_maps, registry: registry).freeze
167
+ end
168
+ end
169
+ end
170
+
171
+ include Checker
172
+
173
+ def initialize
174
+ # @coders_by_name has a content of
175
+ # Array< Hash< Symbol: Hash< String: Coder > > >
176
+ #
177
+ # The layers are:
178
+ # * index of Array is 0 (text) and 1 (binary)
179
+ # * Symbol key in the middle Hash is :encoder and :decoder
180
+ # * String key in the inner Hash corresponds to the `typname` column in the table pg_type
181
+ # * Coder value in the inner Hash is the associated coder object
182
+ @coders_by_name = []
183
+ end
184
+
185
+ # Retrieve a Hash of all en- or decoders for a given wire format.
186
+ # The hash key is the name as defined in table +pg_type+.
187
+ # The hash value is the registered coder object.
188
+ def coders_for(format, direction)
189
+ check_format_and_direction(format, direction)
190
+ @coders_by_name[format]&.[](direction)
191
+ end
192
+
193
+ # Register an encoder or decoder instance for casting a PostgreSQL type.
194
+ #
195
+ # Coder#name must correspond to the +typname+ column in the +pg_type+ table.
196
+ # Coder#format can be 0 for text format and 1 for binary.
197
+ def register_coder(coder)
198
+ h = @coders_by_name[coder.format] ||= { encoder: {}, decoder: {} }
199
+ name = coder.name || raise(ArgumentError, "name of #{coder.inspect} must be defined")
200
+ h[:encoder][name] = coder if coder.respond_to?(:encode)
201
+ h[:decoder][name] = coder if coder.respond_to?(:decode)
202
+ self
203
+ end
204
+
205
+ # Register the given +encoder_class+ and/or +decoder_class+ for casting a PostgreSQL type.
206
+ #
207
+ # +name+ must correspond to the +typname+ column in the +pg_type+ table.
208
+ # +format+ can be 0 for text format and 1 for binary.
209
+ def register_type(format, name, encoder_class, decoder_class)
210
+ register_coder(encoder_class.new(name: name, format: format).freeze) if encoder_class
211
+ register_coder(decoder_class.new(name: name, format: format).freeze) if decoder_class
212
+ self
213
+ end
214
+
215
+ # Alias the +old+ type to the +new+ type.
216
+ def alias_type(format, new, old)
217
+ [:encoder, :decoder].each do |ende|
218
+ enc = @coders_by_name[format][ende][old]
219
+ if enc
220
+ @coders_by_name[format][ende][new] = enc
221
+ else
222
+ @coders_by_name[format][ende].delete(new)
223
+ end
224
+ end
225
+ self
226
+ end
227
+
228
+ # Populate the registry with all builtin types of ruby-pg
229
+ def register_default_types
230
+ register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
231
+ alias_type 0, 'int4', 'int2'
232
+ alias_type 0, 'int8', 'int2'
233
+ alias_type 0, 'oid', 'int2'
234
+
235
+ begin
236
+ PG.require_bigdecimal_without_warning
237
+ register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
238
+ rescue LoadError
239
+ end
240
+ register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
241
+ alias_type 0, 'varchar', 'text'
242
+ alias_type 0, 'char', 'text'
243
+ alias_type 0, 'bpchar', 'text'
244
+ alias_type 0, 'xml', 'text'
245
+ alias_type 0, 'name', 'text'
246
+
247
+ # FIXME: why are we keeping these types as strings?
248
+ # alias_type 'tsvector', 'text'
249
+ # alias_type 'interval', 'text'
250
+ # alias_type 'macaddr', 'text'
251
+ # alias_type 'uuid', 'text'
252
+ #
253
+ # register_type 'money', OID::Money.new
254
+ register_type 0, 'bytea', PG::TextEncoder::Bytea, PG::TextDecoder::Bytea
255
+ register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
256
+ # register_type 'bit', OID::Bit.new
257
+ # register_type 'varbit', OID::Bit.new
258
+
259
+ register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
260
+ alias_type 0, 'float8', 'float4'
261
+
262
+ # For compatibility reason the timestamp in text format is encoded as local time (TimestampWithoutTimeZone) instead of UTC
263
+ register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
264
+ register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
265
+ register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
266
+ # register_type 'time', OID::Time.new
267
+ #
268
+ # register_type 'path', OID::Text.new
269
+ # register_type 'point', OID::Point.new
270
+ # register_type 'polygon', OID::Text.new
271
+ # register_type 'circle', OID::Text.new
272
+ # register_type 'hstore', OID::Hstore.new
273
+ register_type 0, 'json', PG::TextEncoder::JSON, PG::TextDecoder::JSON
274
+ alias_type 0, 'jsonb', 'json'
275
+ # register_type 'citext', OID::Text.new
276
+ # register_type 'ltree', OID::Text.new
277
+ #
278
+ register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
279
+ alias_type 0, 'cidr', 'inet'
280
+
281
+ register_type 0, 'record', PG::TextEncoder::Record, PG::TextDecoder::Record
282
+
283
+
284
+ register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
285
+ register_type 1, 'int4', PG::BinaryEncoder::Int4, PG::BinaryDecoder::Integer
286
+ register_type 1, 'int8', PG::BinaryEncoder::Int8, PG::BinaryDecoder::Integer
287
+ alias_type 1, 'oid', 'int2'
288
+
289
+ register_type 1, 'text', PG::BinaryEncoder::String, PG::BinaryDecoder::String
290
+ alias_type 1, 'varchar', 'text'
291
+ alias_type 1, 'char', 'text'
292
+ alias_type 1, 'bpchar', 'text'
293
+ alias_type 1, 'xml', 'text'
294
+ alias_type 1, 'name', 'text'
295
+
296
+ register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
297
+ register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
298
+ register_type 1, 'float4', PG::BinaryEncoder::Float4, PG::BinaryDecoder::Float
299
+ register_type 1, 'float8', PG::BinaryEncoder::Float8, PG::BinaryDecoder::Float
300
+ register_type 1, 'timestamp', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtc
301
+ register_type 1, 'timestamptz', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtcToLocal
302
+ register_type 1, 'date', PG::BinaryEncoder::Date, PG::BinaryDecoder::Date
303
+
304
+ self
305
+ end
306
+
307
+ alias define_default_types register_default_types
308
+
309
+ DEFAULT_TYPE_REGISTRY = PG.make_shareable(PG::BasicTypeRegistry.new.register_default_types)
310
+ private_constant :DEFAULT_TYPE_REGISTRY
311
+ end
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module BinaryDecoder
6
+ # Init C part of the decoder
7
+ init_date
8
+ end
9
+ end # module PG