pg 1.2.3 → 1.5.4
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.appveyor.yml +42 -0
- data/.gems +6 -0
- data/.github/workflows/binary-gems.yml +117 -0
- data/.github/workflows/source-gem.yml +141 -0
- data/.gitignore +22 -0
- data/.hgsigs +34 -0
- data/.hgtags +41 -0
- data/.irbrc +23 -0
- data/.pryrc +23 -0
- data/.tm_properties +21 -0
- data/.travis.yml +49 -0
- data/Gemfile +14 -0
- data/History.md +884 -0
- data/Manifest.txt +0 -1
- data/README.ja.md +300 -0
- data/README.md +286 -0
- data/Rakefile +33 -135
- data/Rakefile.cross +12 -13
- data/certs/ged.pem +24 -0
- data/certs/larskanis-2022.pem +26 -0
- data/certs/larskanis-2023.pem +24 -0
- data/ext/errorcodes.def +12 -0
- data/ext/errorcodes.rb +0 -0
- data/ext/errorcodes.txt +4 -1
- data/ext/extconf.rb +104 -25
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +73 -58
- data/ext/pg.h +28 -5
- data/ext/pg_binary_decoder.c +80 -1
- data/ext/pg_binary_encoder.c +225 -1
- data/ext/pg_coder.c +96 -33
- data/ext/pg_connection.c +1010 -704
- data/ext/pg_copy_coder.c +351 -33
- data/ext/pg_errors.c +1 -1
- data/ext/pg_record_coder.c +50 -19
- data/ext/pg_result.c +177 -64
- data/ext/pg_text_decoder.c +29 -11
- data/ext/pg_text_encoder.c +29 -16
- data/ext/pg_tuple.c +83 -60
- data/ext/pg_type_map.c +44 -10
- data/ext/pg_type_map_all_strings.c +17 -3
- data/ext/pg_type_map_by_class.c +54 -27
- data/ext/pg_type_map_by_column.c +73 -31
- data/ext/pg_type_map_by_mri_type.c +48 -19
- data/ext/pg_type_map_by_oid.c +59 -27
- data/ext/pg_type_map_in_ruby.c +55 -21
- data/ext/pg_util.c +2 -2
- data/lib/pg/basic_type_map_based_on_result.rb +67 -0
- data/lib/pg/basic_type_map_for_queries.rb +198 -0
- data/lib/pg/basic_type_map_for_results.rb +104 -0
- data/lib/pg/basic_type_registry.rb +299 -0
- data/lib/pg/binary_decoder/date.rb +9 -0
- data/lib/pg/binary_decoder/timestamp.rb +26 -0
- data/lib/pg/binary_encoder/timestamp.rb +20 -0
- data/lib/pg/coder.rb +15 -13
- data/lib/pg/connection.rb +743 -83
- data/lib/pg/exceptions.rb +14 -1
- data/lib/pg/text_decoder/date.rb +18 -0
- data/lib/pg/text_decoder/inet.rb +9 -0
- data/lib/pg/text_decoder/json.rb +14 -0
- data/lib/pg/text_decoder/numeric.rb +9 -0
- data/lib/pg/text_decoder/timestamp.rb +30 -0
- data/lib/pg/text_encoder/date.rb +12 -0
- data/lib/pg/text_encoder/inet.rb +28 -0
- data/lib/pg/text_encoder/json.rb +14 -0
- data/lib/pg/text_encoder/numeric.rb +9 -0
- data/lib/pg/text_encoder/timestamp.rb +24 -0
- data/lib/pg/version.rb +4 -0
- data/lib/pg.rb +94 -39
- data/misc/openssl-pg-segfault.rb +31 -0
- data/misc/postgres/History.txt +9 -0
- data/misc/postgres/Manifest.txt +5 -0
- data/misc/postgres/README.txt +21 -0
- data/misc/postgres/Rakefile +21 -0
- data/misc/postgres/lib/postgres.rb +16 -0
- data/misc/ruby-pg/History.txt +9 -0
- data/misc/ruby-pg/Manifest.txt +5 -0
- data/misc/ruby-pg/README.txt +21 -0
- data/misc/ruby-pg/Rakefile +21 -0
- data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
- data/pg.gemspec +34 -0
- data/rakelib/task_extension.rb +46 -0
- data/sample/array_insert.rb +20 -0
- data/sample/async_api.rb +102 -0
- data/sample/async_copyto.rb +39 -0
- data/sample/async_mixed.rb +56 -0
- data/sample/check_conn.rb +21 -0
- data/sample/copydata.rb +71 -0
- data/sample/copyfrom.rb +81 -0
- data/sample/copyto.rb +19 -0
- data/sample/cursor.rb +21 -0
- data/sample/disk_usage_report.rb +177 -0
- data/sample/issue-119.rb +94 -0
- data/sample/losample.rb +69 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/notify_wait.rb +72 -0
- data/sample/pg_statistics.rb +285 -0
- data/sample/replication_monitor.rb +222 -0
- data/sample/test_binary_values.rb +33 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +311 -0
- data/translation/.po4a-version +7 -0
- data/translation/po/all.pot +936 -0
- data/translation/po/ja.po +1036 -0
- data/translation/po4a.cfg +12 -0
- data.tar.gz.sig +0 -0
- metadata +135 -207
- metadata.gz.sig +0 -0
- data/ChangeLog +0 -0
- data/History.rdoc +0 -578
- data/README.ja.rdoc +0 -13
- data/README.rdoc +0 -213
- data/lib/pg/basic_type_mapping.rb +0 -522
- data/lib/pg/binary_decoder.rb +0 -23
- data/lib/pg/constants.rb +0 -12
- data/lib/pg/text_decoder.rb +0 -46
- data/lib/pg/text_encoder.rb +0 -59
- data/spec/data/expected_trace.out +0 -26
- data/spec/data/random_binary_data +0 -0
- data/spec/helpers.rb +0 -380
- data/spec/pg/basic_type_mapping_spec.rb +0 -630
- data/spec/pg/connection_spec.rb +0 -1949
- data/spec/pg/connection_sync_spec.rb +0 -41
- data/spec/pg/result_spec.rb +0 -681
- data/spec/pg/tuple_spec.rb +0 -333
- data/spec/pg/type_map_by_class_spec.rb +0 -138
- data/spec/pg/type_map_by_column_spec.rb +0 -226
- data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
- data/spec/pg/type_map_by_oid_spec.rb +0 -149
- data/spec/pg/type_map_in_ruby_spec.rb +0 -164
- data/spec/pg/type_map_spec.rb +0 -22
- data/spec/pg/type_spec.rb +0 -1123
- data/spec/pg_spec.rb +0 -50
|
@@ -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,299 @@
|
|
|
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, nil],
|
|
131
|
+
[1, :decoder, nil],
|
|
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
|
+
# The key of these hashs maps to the `typname` column from the table pg_type.
|
|
175
|
+
@coders_by_name = []
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Retrieve a Hash of all en- or decoders for a given wire format.
|
|
179
|
+
# The hash key is the name as defined in table +pg_type+.
|
|
180
|
+
# The hash value is the registered coder object.
|
|
181
|
+
def coders_for(format, direction)
|
|
182
|
+
check_format_and_direction(format, direction)
|
|
183
|
+
@coders_by_name[format]&.[](direction)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Register an encoder or decoder instance for casting a PostgreSQL type.
|
|
187
|
+
#
|
|
188
|
+
# Coder#name must correspond to the +typname+ column in the +pg_type+ table.
|
|
189
|
+
# Coder#format can be 0 for text format and 1 for binary.
|
|
190
|
+
def register_coder(coder)
|
|
191
|
+
h = @coders_by_name[coder.format] ||= { encoder: {}, decoder: {} }
|
|
192
|
+
name = coder.name || raise(ArgumentError, "name of #{coder.inspect} must be defined")
|
|
193
|
+
h[:encoder][name] = coder if coder.respond_to?(:encode)
|
|
194
|
+
h[:decoder][name] = coder if coder.respond_to?(:decode)
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Register the given +encoder_class+ and/or +decoder_class+ for casting a PostgreSQL type.
|
|
199
|
+
#
|
|
200
|
+
# +name+ must correspond to the +typname+ column in the +pg_type+ table.
|
|
201
|
+
# +format+ can be 0 for text format and 1 for binary.
|
|
202
|
+
def register_type(format, name, encoder_class, decoder_class)
|
|
203
|
+
register_coder(encoder_class.new(name: name, format: format).freeze) if encoder_class
|
|
204
|
+
register_coder(decoder_class.new(name: name, format: format).freeze) if decoder_class
|
|
205
|
+
self
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Alias the +old+ type to the +new+ type.
|
|
209
|
+
def alias_type(format, new, old)
|
|
210
|
+
[:encoder, :decoder].each do |ende|
|
|
211
|
+
enc = @coders_by_name[format][ende][old]
|
|
212
|
+
if enc
|
|
213
|
+
@coders_by_name[format][ende][new] = enc
|
|
214
|
+
else
|
|
215
|
+
@coders_by_name[format][ende].delete(new)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
self
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Populate the registry with all builtin types of ruby-pg
|
|
222
|
+
def register_default_types
|
|
223
|
+
register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
|
|
224
|
+
alias_type 0, 'int4', 'int2'
|
|
225
|
+
alias_type 0, 'int8', 'int2'
|
|
226
|
+
alias_type 0, 'oid', 'int2'
|
|
227
|
+
|
|
228
|
+
register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
|
|
229
|
+
register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
|
|
230
|
+
alias_type 0, 'varchar', 'text'
|
|
231
|
+
alias_type 0, 'char', 'text'
|
|
232
|
+
alias_type 0, 'bpchar', 'text'
|
|
233
|
+
alias_type 0, 'xml', 'text'
|
|
234
|
+
alias_type 0, 'name', 'text'
|
|
235
|
+
|
|
236
|
+
# FIXME: why are we keeping these types as strings?
|
|
237
|
+
# alias_type 'tsvector', 'text'
|
|
238
|
+
# alias_type 'interval', 'text'
|
|
239
|
+
# alias_type 'macaddr', 'text'
|
|
240
|
+
# alias_type 'uuid', 'text'
|
|
241
|
+
#
|
|
242
|
+
# register_type 'money', OID::Money.new
|
|
243
|
+
register_type 0, 'bytea', PG::TextEncoder::Bytea, PG::TextDecoder::Bytea
|
|
244
|
+
register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
|
|
245
|
+
# register_type 'bit', OID::Bit.new
|
|
246
|
+
# register_type 'varbit', OID::Bit.new
|
|
247
|
+
|
|
248
|
+
register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
|
|
249
|
+
alias_type 0, 'float8', 'float4'
|
|
250
|
+
|
|
251
|
+
# For compatibility reason the timestamp in text format is encoded as local time (TimestampWithoutTimeZone) instead of UTC
|
|
252
|
+
register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
|
|
253
|
+
register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
|
|
254
|
+
register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
|
|
255
|
+
# register_type 'time', OID::Time.new
|
|
256
|
+
#
|
|
257
|
+
# register_type 'path', OID::Text.new
|
|
258
|
+
# register_type 'point', OID::Point.new
|
|
259
|
+
# register_type 'polygon', OID::Text.new
|
|
260
|
+
# register_type 'circle', OID::Text.new
|
|
261
|
+
# register_type 'hstore', OID::Hstore.new
|
|
262
|
+
register_type 0, 'json', PG::TextEncoder::JSON, PG::TextDecoder::JSON
|
|
263
|
+
alias_type 0, 'jsonb', 'json'
|
|
264
|
+
# register_type 'citext', OID::Text.new
|
|
265
|
+
# register_type 'ltree', OID::Text.new
|
|
266
|
+
#
|
|
267
|
+
register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
|
|
268
|
+
alias_type 0, 'cidr', 'inet'
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
|
|
273
|
+
register_type 1, 'int4', PG::BinaryEncoder::Int4, PG::BinaryDecoder::Integer
|
|
274
|
+
register_type 1, 'int8', PG::BinaryEncoder::Int8, PG::BinaryDecoder::Integer
|
|
275
|
+
alias_type 1, 'oid', 'int2'
|
|
276
|
+
|
|
277
|
+
register_type 1, 'text', PG::BinaryEncoder::String, PG::BinaryDecoder::String
|
|
278
|
+
alias_type 1, 'varchar', 'text'
|
|
279
|
+
alias_type 1, 'char', 'text'
|
|
280
|
+
alias_type 1, 'bpchar', 'text'
|
|
281
|
+
alias_type 1, 'xml', 'text'
|
|
282
|
+
alias_type 1, 'name', 'text'
|
|
283
|
+
|
|
284
|
+
register_type 1, 'bytea', PG::BinaryEncoder::Bytea, PG::BinaryDecoder::Bytea
|
|
285
|
+
register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
|
|
286
|
+
register_type 1, 'float4', PG::BinaryEncoder::Float4, PG::BinaryDecoder::Float
|
|
287
|
+
register_type 1, 'float8', PG::BinaryEncoder::Float8, PG::BinaryDecoder::Float
|
|
288
|
+
register_type 1, 'timestamp', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtc
|
|
289
|
+
register_type 1, 'timestamptz', PG::BinaryEncoder::TimestampUtc, PG::BinaryDecoder::TimestampUtcToLocal
|
|
290
|
+
register_type 1, 'date', PG::BinaryEncoder::Date, PG::BinaryDecoder::Date
|
|
291
|
+
|
|
292
|
+
self
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
alias define_default_types register_default_types
|
|
296
|
+
|
|
297
|
+
DEFAULT_TYPE_REGISTRY = PG.make_shareable(PG::BasicTypeRegistry.new.register_default_types)
|
|
298
|
+
private_constant :DEFAULT_TYPE_REGISTRY
|
|
299
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
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(hash={}, **kwargs)
|
|
9
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
class TimestampUtcToLocal < Timestamp
|
|
14
|
+
def initialize(hash={}, **kwargs)
|
|
15
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
class TimestampLocal < Timestamp
|
|
20
|
+
def initialize(hash={}, **kwargs)
|
|
21
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
22
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end # module PG
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module PG
|
|
5
|
+
module BinaryEncoder
|
|
6
|
+
# Convenience classes for timezone options
|
|
7
|
+
class TimestampUtc < Timestamp
|
|
8
|
+
def initialize(hash={}, **kwargs)
|
|
9
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
class TimestampLocal < Timestamp
|
|
14
|
+
def initialize(hash={}, **kwargs)
|
|
15
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end # module PG
|
data/lib/pg/coder.rb
CHANGED
|
@@ -6,22 +6,24 @@ module PG
|
|
|
6
6
|
class Coder
|
|
7
7
|
|
|
8
8
|
module BinaryFormatting
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
super(
|
|
9
|
+
def initialize(hash={}, **kwargs)
|
|
10
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
|
11
|
+
super(format: 1, **hash, **kwargs)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# Create a new coder object based on the attribute Hash.
|
|
17
|
-
def initialize(
|
|
18
|
-
|
|
17
|
+
def initialize(hash=nil, **kwargs)
|
|
18
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) if hash
|
|
19
|
+
|
|
20
|
+
(hash || kwargs).each do |key, val|
|
|
19
21
|
send("#{key}=", val)
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def dup
|
|
24
|
-
self.class.new(to_h)
|
|
26
|
+
self.class.new(**to_h)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
# Returns coder attributes as Hash.
|
|
@@ -43,7 +45,7 @@ module PG
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def marshal_load(str)
|
|
46
|
-
initialize
|
|
48
|
+
initialize(**Marshal.load(str))
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def inspect
|
|
@@ -70,11 +72,11 @@ module PG
|
|
|
70
72
|
|
|
71
73
|
class CompositeCoder < Coder
|
|
72
74
|
def to_h
|
|
73
|
-
super
|
|
75
|
+
{ **super,
|
|
74
76
|
elements_type: elements_type,
|
|
75
77
|
needs_quotation: needs_quotation?,
|
|
76
78
|
delimiter: delimiter,
|
|
77
|
-
}
|
|
79
|
+
}
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def inspect
|
|
@@ -86,19 +88,19 @@ module PG
|
|
|
86
88
|
|
|
87
89
|
class CopyCoder < Coder
|
|
88
90
|
def to_h
|
|
89
|
-
super
|
|
91
|
+
{ **super,
|
|
90
92
|
type_map: type_map,
|
|
91
93
|
delimiter: delimiter,
|
|
92
94
|
null_string: null_string,
|
|
93
|
-
}
|
|
95
|
+
}
|
|
94
96
|
end
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
class RecordCoder < Coder
|
|
98
100
|
def to_h
|
|
99
|
-
super
|
|
101
|
+
{ **super,
|
|
100
102
|
type_map: type_map,
|
|
101
|
-
}
|
|
103
|
+
}
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
end # module PG
|