pg 0.21.0 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.rdoc +98 -0
- data/Manifest.txt +5 -1
- data/README.rdoc +14 -4
- data/Rakefile +4 -5
- data/Rakefile.cross +17 -21
- data/ext/errorcodes.def +12 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +4 -1
- data/ext/extconf.rb +14 -32
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -39
- data/ext/pg.c +23 -50
- data/ext/pg.h +51 -81
- data/ext/pg_binary_decoder.c +73 -6
- data/ext/pg_coder.c +52 -3
- data/ext/pg_connection.c +369 -219
- data/ext/pg_copy_coder.c +10 -5
- data/ext/pg_result.c +343 -119
- data/ext/pg_text_decoder.c +597 -37
- data/ext/pg_text_encoder.c +6 -7
- data/ext/pg_tuple.c +541 -0
- data/ext/util.c +6 -6
- data/ext/util.h +2 -2
- data/lib/pg.rb +5 -7
- data/lib/pg/basic_type_mapping.rb +40 -7
- data/lib/pg/binary_decoder.rb +22 -0
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +27 -3
- data/lib/pg/constants.rb +1 -1
- data/lib/pg/exceptions.rb +1 -1
- data/lib/pg/result.rb +1 -1
- data/lib/pg/text_decoder.rb +19 -23
- data/lib/pg/text_encoder.rb +35 -1
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +1 -1
- data/spec/helpers.rb +49 -21
- data/spec/pg/basic_type_mapping_spec.rb +230 -27
- data/spec/pg/connection_spec.rb +473 -277
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +48 -13
- data/spec/pg/tuple_spec.rb +280 -0
- data/spec/pg/type_map_by_class_spec.rb +1 -1
- data/spec/pg/type_map_by_column_spec.rb +1 -1
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_map_by_oid_spec.rb +1 -1
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +184 -12
- data/spec/pg_spec.rb +2 -2
- metadata +37 -33
- metadata.gz.sig +0 -0
- data/lib/pg/deprecated_constants.rb +0 -21
data/ext/util.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* util.c - Utils for ruby-pg
|
3
|
-
* $Id: util.c,v
|
3
|
+
* $Id: util.c,v fc1c4deb1398 2018/06/25 12:02:09 kanis $
|
4
4
|
*
|
5
5
|
*/
|
6
6
|
|
@@ -15,9 +15,9 @@ static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk
|
|
15
15
|
* in-place (with _out_ == _in_).
|
16
16
|
*/
|
17
17
|
void
|
18
|
-
base64_encode( char *out, char *in, int len)
|
18
|
+
base64_encode( char *out, const char *in, int len)
|
19
19
|
{
|
20
|
-
unsigned char *in_ptr = (unsigned char *)in + len;
|
20
|
+
const unsigned char *in_ptr = (const unsigned char *)in + len;
|
21
21
|
char *out_ptr = out + BASE64_ENCODED_SIZE(len);
|
22
22
|
int part_len = len % 3;
|
23
23
|
|
@@ -72,12 +72,12 @@ static const unsigned char base64_decode_table[] =
|
|
72
72
|
* It is possible to decode a string in-place (with _out_ == _in_).
|
73
73
|
*/
|
74
74
|
int
|
75
|
-
base64_decode( char *out, char *in, unsigned int len)
|
75
|
+
base64_decode( char *out, const char *in, unsigned int len)
|
76
76
|
{
|
77
77
|
unsigned char a, b, c, d;
|
78
|
-
unsigned char *in_ptr = (unsigned char *)in;
|
78
|
+
const unsigned char *in_ptr = (const unsigned char *)in;
|
79
79
|
unsigned char *out_ptr = (unsigned char *)out;
|
80
|
-
unsigned char *iend_ptr = (unsigned char *)in + len;
|
80
|
+
const unsigned char *iend_ptr = (unsigned char *)in + len;
|
81
81
|
|
82
82
|
for(;;){
|
83
83
|
if( in_ptr+3 < iend_ptr &&
|
data/ext/util.h
CHANGED
@@ -57,8 +57,8 @@
|
|
57
57
|
#define BASE64_ENCODED_SIZE(strlen) (((strlen) + 2) / 3 * 4)
|
58
58
|
#define BASE64_DECODED_SIZE(base64len) (((base64len) + 3) / 4 * 3)
|
59
59
|
|
60
|
-
void base64_encode( char *out, char *in, int len);
|
61
|
-
int base64_decode( char *out, char *in, unsigned int len);
|
60
|
+
void base64_encode( char *out, const char *in, int len);
|
61
|
+
int base64_decode( char *out, const char *in, unsigned int len);
|
62
62
|
|
63
63
|
int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
|
64
64
|
|
data/lib/pg.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'pg_ext'
|
@@ -35,10 +35,10 @@ end
|
|
35
35
|
module PG
|
36
36
|
|
37
37
|
# Library version
|
38
|
-
VERSION = '
|
38
|
+
VERSION = '1.1.4'
|
39
39
|
|
40
40
|
# VCS revision
|
41
|
-
REVISION = %q$Revision:
|
41
|
+
REVISION = %q$Revision: 6f611e78845a $
|
42
42
|
|
43
43
|
class NotAllCopyDataRetrieved < PG::Error
|
44
44
|
end
|
@@ -60,17 +60,15 @@ module PG
|
|
60
60
|
require 'pg/exceptions'
|
61
61
|
require 'pg/constants'
|
62
62
|
require 'pg/coder'
|
63
|
+
require 'pg/binary_decoder'
|
63
64
|
require 'pg/text_encoder'
|
64
65
|
require 'pg/text_decoder'
|
65
66
|
require 'pg/basic_type_mapping'
|
66
67
|
require 'pg/type_map_by_column'
|
67
68
|
require 'pg/connection'
|
68
69
|
require 'pg/result'
|
70
|
+
require 'pg/tuple'
|
69
71
|
|
70
72
|
end # module PG
|
71
73
|
|
72
74
|
|
73
|
-
autoload :PGError, 'pg/deprecated_constants'
|
74
|
-
autoload :PGconn, 'pg/deprecated_constants'
|
75
|
-
autoload :PGresult, 'pg/deprecated_constants'
|
76
|
-
|
@@ -1,7 +1,27 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'pg' unless defined?( PG )
|
4
4
|
|
5
|
+
# This module defines the mapping between OID and encoder/decoder classes for PG::BasicTypeMapForResults, PG::BasicTypeMapForQueries and PG::BasicTypeMapBasedOnResult.
|
6
|
+
#
|
7
|
+
# Additional types can be added like so:
|
8
|
+
#
|
9
|
+
# require 'pg'
|
10
|
+
# require 'ipaddr'
|
11
|
+
#
|
12
|
+
# class InetDecoder < PG::SimpleDecoder
|
13
|
+
# def decode(string, tuple=nil, field=nil)
|
14
|
+
# IPAddr.new(string)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# class InetEncoder < PG::SimpleEncoder
|
18
|
+
# def encode(ip_addr)
|
19
|
+
# ip_addr.to_s
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # 0 if for text format, can also be 1 for binary
|
24
|
+
# PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
|
5
25
|
module PG::BasicTypeRegistry
|
6
26
|
# An instance of this class stores the coders that should be used for a given wire format (text or binary)
|
7
27
|
# and type cast direction (encoder or decoder).
|
@@ -137,6 +157,7 @@ module PG::BasicTypeRegistry
|
|
137
157
|
# Register an OID type named +name+ with a typecasting encoder and decoder object in
|
138
158
|
# +type+. +name+ should correspond to the `typname` column in
|
139
159
|
# the `pg_type` table.
|
160
|
+
# +format+ can be 0 for text format and 1 for binary.
|
140
161
|
def self.register_type(format, name, encoder_class, decoder_class)
|
141
162
|
CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
|
142
163
|
CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
|
@@ -145,8 +166,14 @@ module PG::BasicTypeRegistry
|
|
145
166
|
|
146
167
|
# Alias the +old+ type to the +new+ type.
|
147
168
|
def self.alias_type(format, new, old)
|
148
|
-
|
149
|
-
|
169
|
+
[:encoder, :decoder].each do |ende|
|
170
|
+
enc = CODERS_BY_NAME[format][ende][old]
|
171
|
+
if enc
|
172
|
+
CODERS_BY_NAME[format][ende][new] = enc
|
173
|
+
else
|
174
|
+
CODERS_BY_NAME[format][ende].delete(new)
|
175
|
+
end
|
176
|
+
end
|
150
177
|
end
|
151
178
|
|
152
179
|
register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
|
@@ -154,7 +181,7 @@ module PG::BasicTypeRegistry
|
|
154
181
|
alias_type 0, 'int8', 'int2'
|
155
182
|
alias_type 0, 'oid', 'int2'
|
156
183
|
|
157
|
-
|
184
|
+
register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
|
158
185
|
register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
|
159
186
|
alias_type 0, 'varchar', 'text'
|
160
187
|
alias_type 0, 'char', 'text'
|
@@ -193,8 +220,8 @@ module PG::BasicTypeRegistry
|
|
193
220
|
# register_type 'citext', OID::Text.new
|
194
221
|
# register_type 'ltree', OID::Text.new
|
195
222
|
#
|
196
|
-
|
197
|
-
|
223
|
+
register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
|
224
|
+
alias_type 0, 'cidr', 'inet'
|
198
225
|
|
199
226
|
|
200
227
|
|
@@ -213,6 +240,8 @@ module PG::BasicTypeRegistry
|
|
213
240
|
register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
|
214
241
|
register_type 1, 'float4', nil, PG::BinaryDecoder::Float
|
215
242
|
register_type 1, 'float8', nil, PG::BinaryDecoder::Float
|
243
|
+
register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtc
|
244
|
+
register_type 1, 'timestamptz', nil, PG::BinaryDecoder::TimestampUtcToLocal
|
216
245
|
end
|
217
246
|
|
218
247
|
# Simple set of rules for type casting common PostgreSQL types to Ruby.
|
@@ -258,7 +287,7 @@ end
|
|
258
287
|
# This prints the rows with type casted columns:
|
259
288
|
# ["a", 123, [5, 4, 3]]
|
260
289
|
#
|
261
|
-
# See also PG::BasicTypeMapBasedOnResult for the encoder direction.
|
290
|
+
# See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
|
262
291
|
class PG::BasicTypeMapForResults < PG::TypeMapByOid
|
263
292
|
include PG::BasicTypeRegistry
|
264
293
|
|
@@ -412,6 +441,10 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
|
|
412
441
|
# to unnecessary type conversions on server side.
|
413
442
|
Integer => [0, 'int8'],
|
414
443
|
Float => [0, 'float8'],
|
444
|
+
BigDecimal => [0, 'numeric'],
|
445
|
+
# We use text format and no type OID for IPAddr, because setting the OID can lead
|
446
|
+
# to unnecessary inet/cidr conversions on the server side.
|
447
|
+
IPAddr => [0, 'inet'],
|
415
448
|
Array => :get_array_type,
|
416
449
|
}
|
417
450
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
module PG
|
4
|
+
module BinaryDecoder
|
5
|
+
# Convenience classes for timezone options
|
6
|
+
class TimestampUtc < Timestamp
|
7
|
+
def initialize(params={})
|
8
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
class TimestampUtcToLocal < Timestamp
|
12
|
+
def initialize(params={})
|
13
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class TimestampLocal < Timestamp
|
17
|
+
def initialize(params={})
|
18
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end # module PG
|
data/lib/pg/coder.rb
CHANGED
data/lib/pg/connection.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'pg' unless defined?( PG )
|
4
4
|
require 'uri'
|
@@ -47,7 +47,7 @@ class PG::Connection
|
|
47
47
|
|
48
48
|
if args.length == 1
|
49
49
|
case args.first
|
50
|
-
when URI, URI.regexp
|
50
|
+
when URI, /\A#{URI.regexp}\z/
|
51
51
|
uri = URI(args.first)
|
52
52
|
options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
|
53
53
|
when /=/
|
@@ -85,7 +85,7 @@ class PG::Connection
|
|
85
85
|
|
86
86
|
|
87
87
|
# call-seq:
|
88
|
-
# conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
|
88
|
+
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
89
89
|
#
|
90
90
|
# Execute a copy process for transfering data to or from the server.
|
91
91
|
#
|
@@ -109,6 +109,11 @@ class PG::Connection
|
|
109
109
|
# of blocking mode of operation, #copy_data is preferred to raw calls
|
110
110
|
# of #put_copy_data, #get_copy_data and #put_copy_end.
|
111
111
|
#
|
112
|
+
# _coder_ can be a PG::Coder derivation
|
113
|
+
# (typically PG::TextEncoder::CopyRow or PG::TextDecoder::CopyRow).
|
114
|
+
# This enables encoding of data fields given to #put_copy_data
|
115
|
+
# or decoding of fields received by #get_copy_data.
|
116
|
+
#
|
112
117
|
# Example with CSV input format:
|
113
118
|
# conn.exec "create table my_table (a text,b text,c text,d text)"
|
114
119
|
# conn.copy_data "COPY my_table FROM STDIN CSV" do
|
@@ -263,5 +268,24 @@ class PG::Connection
|
|
263
268
|
end
|
264
269
|
end
|
265
270
|
|
271
|
+
REDIRECT_METHODS = {
|
272
|
+
:exec => [:async_exec, :sync_exec],
|
273
|
+
:query => [:async_exec, :sync_exec],
|
274
|
+
:exec_params => [:async_exec_params, :sync_exec_params],
|
275
|
+
:prepare => [:async_prepare, :sync_prepare],
|
276
|
+
:exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
|
277
|
+
:describe_portal => [:async_describe_portal, :sync_describe_portal],
|
278
|
+
:describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
|
279
|
+
}
|
280
|
+
|
281
|
+
def self.async_api=(enable)
|
282
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
283
|
+
remove_method(ali) if method_defined?(ali)
|
284
|
+
alias_method( ali, enable ? async : sync )
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
289
|
+
self.async_api = true
|
266
290
|
end # class PG::Connection
|
267
291
|
|
data/lib/pg/constants.rb
CHANGED
data/lib/pg/exceptions.rb
CHANGED
data/lib/pg/result.rb
CHANGED
data/lib/pg/text_decoder.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
require 'json'
|
@@ -17,35 +17,31 @@ module PG
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
class
|
21
|
-
ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
22
|
-
|
20
|
+
class JSON < SimpleDecoder
|
23
21
|
def decode(string, tuple=nil, field=nil)
|
24
|
-
|
25
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
|
26
|
-
else
|
27
|
-
string
|
28
|
-
end
|
22
|
+
::JSON.parse(string, quirks_mode: true)
|
29
23
|
end
|
30
24
|
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if string =~ ISO_DATETIME_WITH_TIMEZONE
|
37
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}"
|
38
|
-
else
|
39
|
-
string
|
40
|
-
end
|
26
|
+
# Convenience classes for timezone options
|
27
|
+
class TimestampUtc < Timestamp
|
28
|
+
def initialize(params={})
|
29
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
|
41
30
|
end
|
42
31
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
::JSON.parse(string, quirks_mode: true)
|
32
|
+
class TimestampUtcToLocal < Timestamp
|
33
|
+
def initialize(params={})
|
34
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
|
47
35
|
end
|
48
36
|
end
|
37
|
+
class TimestampLocal < Timestamp
|
38
|
+
def initialize(params={})
|
39
|
+
super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# For backward compatibility:
|
44
|
+
TimestampWithoutTimeZone = TimestampLocal
|
45
|
+
TimestampWithTimeZone = Timestamp
|
49
46
|
end
|
50
47
|
end # module PG
|
51
|
-
|
data/lib/pg/text_encoder.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'json'
|
4
|
+
require 'ipaddr'
|
4
5
|
|
5
6
|
module PG
|
6
7
|
module TextEncoder
|
@@ -18,6 +19,13 @@ module PG
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
class TimestampUtc < SimpleEncoder
|
23
|
+
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC = "%Y-%m-%d %H:%M:%S.%N".freeze
|
24
|
+
def encode(value)
|
25
|
+
value.respond_to?(:utc) ? value.utc.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC) : value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
21
29
|
class TimestampWithTimeZone < SimpleEncoder
|
22
30
|
STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
|
23
31
|
def encode(value)
|
@@ -25,11 +33,37 @@ module PG
|
|
25
33
|
end
|
26
34
|
end
|
27
35
|
|
36
|
+
class Numeric < SimpleEncoder
|
37
|
+
def encode(value)
|
38
|
+
value.is_a?(BigDecimal) ? value.to_s('F') : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
class JSON < SimpleEncoder
|
29
43
|
def encode(value)
|
30
44
|
::JSON.generate(value, quirks_mode: true)
|
31
45
|
end
|
32
46
|
end
|
47
|
+
|
48
|
+
class Inet < SimpleEncoder
|
49
|
+
def encode(value)
|
50
|
+
case value
|
51
|
+
when IPAddr
|
52
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
53
|
+
s = value.to_s
|
54
|
+
if value.respond_to?(:prefix)
|
55
|
+
prefix = value.prefix
|
56
|
+
else
|
57
|
+
range = value.to_range
|
58
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
59
|
+
end
|
60
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
61
|
+
s
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
33
67
|
end
|
34
68
|
end # module PG
|
35
69
|
|
data/lib/pg/tuple.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pg' unless defined?( PG )
|
5
|
+
|
6
|
+
|
7
|
+
class PG::Tuple
|
8
|
+
|
9
|
+
### Return a String representation of the object suitable for debugging.
|
10
|
+
def inspect
|
11
|
+
"#<#{self.class} #{self.map{|k,v| "#{k}: #{v.inspect}" }.join(", ") }>"
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_key?(key)
|
15
|
+
field_map.has_key?(key)
|
16
|
+
end
|
17
|
+
alias key? has_key?
|
18
|
+
|
19
|
+
def keys
|
20
|
+
field_names || field_map.keys.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_key(&block)
|
24
|
+
if fn=field_names
|
25
|
+
fn.each(&block)
|
26
|
+
else
|
27
|
+
field_map.each_key(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|