yugabyte_ysql 0.1
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 +7 -0
- data/.appveyor.yml +42 -0
- data/.gems +6 -0
- data/.gemtest +0 -0
- data/.github/workflows/binary-gems.yml +117 -0
- data/.github/workflows/source-gem.yml +143 -0
- data/.gitignore +24 -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/BSDL +22 -0
- data/Contributors.rdoc +46 -0
- data/Gemfile +18 -0
- data/History.md +901 -0
- data/LICENSE +56 -0
- data/Manifest.txt +73 -0
- data/POSTGRES +23 -0
- data/README-OS_X.rdoc +68 -0
- data/README-Windows.rdoc +56 -0
- data/README.ja.md +302 -0
- data/README.md +373 -0
- data/Rakefile +118 -0
- data/Rakefile.cross +299 -0
- data/certs/ged.pem +24 -0
- data/certs/kanis@comcard.de.pem +20 -0
- data/certs/larskanis-2022.pem +26 -0
- data/certs/larskanis-2023.pem +24 -0
- data/certs/larskanis-2024.pem +24 -0
- data/ext/errorcodes.def +1044 -0
- data/ext/errorcodes.rb +45 -0
- data/ext/errorcodes.txt +497 -0
- data/ext/extconf.rb +174 -0
- data/ext/gvl_wrappers.c +21 -0
- data/ext/gvl_wrappers.h +264 -0
- data/ext/pg.c +692 -0
- data/ext/pg.h +392 -0
- data/ext/pg_binary_decoder.c +308 -0
- data/ext/pg_binary_encoder.c +387 -0
- data/ext/pg_coder.c +624 -0
- data/ext/pg_connection.c +4681 -0
- data/ext/pg_copy_coder.c +917 -0
- data/ext/pg_errors.c +95 -0
- data/ext/pg_record_coder.c +522 -0
- data/ext/pg_result.c +1766 -0
- data/ext/pg_text_decoder.c +1005 -0
- data/ext/pg_text_encoder.c +827 -0
- data/ext/pg_tuple.c +572 -0
- data/ext/pg_type_map.c +200 -0
- data/ext/pg_type_map_all_strings.c +130 -0
- data/ext/pg_type_map_by_class.c +271 -0
- data/ext/pg_type_map_by_column.c +355 -0
- data/ext/pg_type_map_by_mri_type.c +313 -0
- data/ext/pg_type_map_by_oid.c +388 -0
- data/ext/pg_type_map_in_ruby.c +333 -0
- data/ext/pg_util.c +149 -0
- data/ext/pg_util.h +65 -0
- data/ext/vc/pg.sln +26 -0
- data/ext/vc/pg_18/pg.vcproj +216 -0
- data/ext/vc/pg_19/pg_19.vcproj +209 -0
- data/lib/pg/basic_type_map_based_on_result.rb +67 -0
- data/lib/pg/basic_type_map_for_queries.rb +202 -0
- data/lib/pg/basic_type_map_for_results.rb +104 -0
- data/lib/pg/basic_type_registry.rb +303 -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 +106 -0
- data/lib/pg/connection.rb +990 -0
- data/lib/pg/exceptions.rb +25 -0
- data/lib/pg/load_balance_service.rb +406 -0
- data/lib/pg/result.rb +43 -0
- 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/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +16 -0
- data/lib/pg/version.rb +5 -0
- data/lib/yugabyte_ysql.rb +130 -0
- 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/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/yugabyte_ysql.gemspec +33 -0
- metadata +232 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yugabyte_ysql' unless defined?( YugabyteYSQL )
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module YugabyteYSQL
|
|
8
|
+
|
|
9
|
+
class Error < StandardError
|
|
10
|
+
def initialize(msg=nil, connection: nil, result: nil)
|
|
11
|
+
@connection = connection
|
|
12
|
+
@result = result
|
|
13
|
+
super(msg)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class NotAllCopyDataRetrieved < YugabyteYSQL::Error
|
|
18
|
+
end
|
|
19
|
+
class LostCopyState < YugabyteYSQL::Error
|
|
20
|
+
end
|
|
21
|
+
class NotInBlockingMode < YugabyteYSQL::Error
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end # module PG
|
|
25
|
+
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'yugabyte_ysql' unless defined?( YugabyteYSQL )
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
class YugabyteYSQL::LoadBalanceService
|
|
6
|
+
|
|
7
|
+
LBProperties = Struct.new(:placements_info, :refresh_interval, :fallback_to_tk_only, :failed_host_reconnect_delay)
|
|
8
|
+
Node = Struct.new(:host, :port, :cloud, :region, :zone, :public_ip, :count, :is_down, :down_since)
|
|
9
|
+
CloudPlacement = Struct.new(:cloud, :region, :zone)
|
|
10
|
+
@@mutex = Concurrent::ReentrantReadWriteLock.new
|
|
11
|
+
@@last_refresh_time = -1
|
|
12
|
+
@@control_connection = nil
|
|
13
|
+
@@cluster_info = { }
|
|
14
|
+
@@useHostColumn = nil
|
|
15
|
+
|
|
16
|
+
def self.get_load(host)
|
|
17
|
+
if @@cluster_info[host]
|
|
18
|
+
@@cluster_info[host].count
|
|
19
|
+
else
|
|
20
|
+
0
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.decrement_connection_count(host)
|
|
25
|
+
@@mutex.acquire_write_lock
|
|
26
|
+
begin
|
|
27
|
+
info = @@cluster_info[host]
|
|
28
|
+
unless info.nil?
|
|
29
|
+
info.count -= 1
|
|
30
|
+
if info.count < 0
|
|
31
|
+
# Can go negative if we are here because of a connection that was created in a non-LB fashion
|
|
32
|
+
info.count = 0
|
|
33
|
+
end
|
|
34
|
+
return true
|
|
35
|
+
end
|
|
36
|
+
ensure
|
|
37
|
+
@@mutex.release_write_lock
|
|
38
|
+
end
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.connect_to_lb_hosts(lb_props, iopts)
|
|
43
|
+
refresh_done = false
|
|
44
|
+
@@mutex.acquire_write_lock
|
|
45
|
+
begin
|
|
46
|
+
if metadata_needs_refresh lb_props.refresh_interval
|
|
47
|
+
while !refresh_done
|
|
48
|
+
if @@control_connection == nil
|
|
49
|
+
begin
|
|
50
|
+
@@control_connection = create_control_connection(iopts)
|
|
51
|
+
rescue
|
|
52
|
+
return nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
begin
|
|
56
|
+
refresh_yb_servers(lb_props.failed_host_reconnect_delay, @@control_connection)
|
|
57
|
+
refresh_done = true
|
|
58
|
+
rescue => err
|
|
59
|
+
if iopts[:host] == @@control_connection.host
|
|
60
|
+
if @@cluster_info[iopts[:host]]
|
|
61
|
+
@@cluster_info[iopts[:host]].is_down = true
|
|
62
|
+
@@cluster_info[iopts[:host]].down_since = Time.now.to_i
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
new_list = @@cluster_info.select {|k, v| !v.is_down }
|
|
66
|
+
if new_list.length > 0
|
|
67
|
+
h = new_list.keys.first
|
|
68
|
+
iopts[:port] = new_list[h].port
|
|
69
|
+
iopts[:host] = h
|
|
70
|
+
else
|
|
71
|
+
return nil
|
|
72
|
+
# raise(PG::Error, "Unable to create a control connection")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
@@control_connection = create_control_connection(iopts)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
ensure
|
|
80
|
+
@@mutex.release_write_lock
|
|
81
|
+
end
|
|
82
|
+
success = false
|
|
83
|
+
new_request = true
|
|
84
|
+
placement_index = 1
|
|
85
|
+
until success
|
|
86
|
+
@@mutex.acquire_write_lock
|
|
87
|
+
begin
|
|
88
|
+
host_port = get_least_loaded_server(lb_props.placements_info, lb_props.fallback_to_tk_only, new_request, placement_index)
|
|
89
|
+
new_request = false
|
|
90
|
+
ensure
|
|
91
|
+
@@mutex.release_write_lock
|
|
92
|
+
end
|
|
93
|
+
unless host_port
|
|
94
|
+
break
|
|
95
|
+
end
|
|
96
|
+
lb_host = host_port[0]
|
|
97
|
+
lb_port = host_port[1]
|
|
98
|
+
placement_index = host_port[2]
|
|
99
|
+
if lb_host.empty?
|
|
100
|
+
break
|
|
101
|
+
end
|
|
102
|
+
# modify iopts args
|
|
103
|
+
begin
|
|
104
|
+
iopts[:host] = lb_host
|
|
105
|
+
iopts[:port] = lb_port
|
|
106
|
+
# iopts = resolve_hosts(iopts)
|
|
107
|
+
connection = YugabyteYSQL.connect(iopts)
|
|
108
|
+
success = true
|
|
109
|
+
rescue => e
|
|
110
|
+
@@mutex.acquire_write_lock
|
|
111
|
+
begin
|
|
112
|
+
@@cluster_info[lb_host].is_down = true
|
|
113
|
+
@@cluster_info[lb_host].down_since = Time.now.to_i
|
|
114
|
+
@@cluster_info[lb_host].count -= 1
|
|
115
|
+
if @@cluster_info[lb_host].count < 0
|
|
116
|
+
@@cluster_info[lb_host].count = 0
|
|
117
|
+
end
|
|
118
|
+
ensure
|
|
119
|
+
@@mutex.release_write_lock
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
connection
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.create_control_connection(iopts)
|
|
127
|
+
conn = nil
|
|
128
|
+
success = false
|
|
129
|
+
# Iterate until control connection is successful or all nodes are tried
|
|
130
|
+
until success
|
|
131
|
+
begin
|
|
132
|
+
conn = YugabyteYSQL.connect(iopts)
|
|
133
|
+
success = true
|
|
134
|
+
rescue => e
|
|
135
|
+
if @@cluster_info[iopts[:host]]
|
|
136
|
+
@@cluster_info[iopts[:host]].is_down = true
|
|
137
|
+
@@cluster_info[iopts[:host]].down_since = Time.now.to_i
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
new_list = @@cluster_info.select {|k, v| !v.is_down }
|
|
141
|
+
if new_list.length > 0
|
|
142
|
+
h = new_list.keys.first
|
|
143
|
+
iopts[:port] = new_list[h].port
|
|
144
|
+
iopts[:host] = h
|
|
145
|
+
else
|
|
146
|
+
raise(YugabyteYSQL::Error, "Unable to create a control connection")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
conn
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.refresh_yb_servers(failed_host_reconnect_delay_secs, conn)
|
|
154
|
+
rs = conn.exec("select * from yb_servers()")
|
|
155
|
+
found_public_ip = false
|
|
156
|
+
rs.each do |row|
|
|
157
|
+
# Take the first address of resolved host addresses
|
|
158
|
+
host = resolve_host(row['host'])[0][0] # 2D array
|
|
159
|
+
port = row['port']
|
|
160
|
+
cloud = row['cloud']
|
|
161
|
+
region = row['region']
|
|
162
|
+
zone = row['zone']
|
|
163
|
+
public_ip = row['public_ip']
|
|
164
|
+
public_ip = resolve_host(public_ip)[0][0] if public_ip
|
|
165
|
+
if not public_ip.nil? and not public_ip.empty?
|
|
166
|
+
found_public_ip = true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# todo set useHostColumn field
|
|
170
|
+
if @@useHostColumn.nil?
|
|
171
|
+
if host.eql? conn.host
|
|
172
|
+
@@useHostColumn = true
|
|
173
|
+
end
|
|
174
|
+
if !public_ip.nil? && (public_ip.eql? conn.host)
|
|
175
|
+
@@useHostColumn = false
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
old = @@cluster_info[host]
|
|
179
|
+
if old
|
|
180
|
+
if old.is_down
|
|
181
|
+
if Time.now.to_i - old.down_since > failed_host_reconnect_delay_secs
|
|
182
|
+
old.is_down = false
|
|
183
|
+
end
|
|
184
|
+
@@cluster_info[host] = old
|
|
185
|
+
end
|
|
186
|
+
else
|
|
187
|
+
node = Node.new(host, port, cloud, region, zone, public_ip, 0, false, 0)
|
|
188
|
+
@@cluster_info[host] = node
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
if @@useHostColumn.nil?
|
|
192
|
+
if found_public_ip
|
|
193
|
+
@@useHostColumn = false
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
@@last_refresh_time = Time.now.to_i
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def self.get_least_loaded_server(allowed_placements, fallback_to_tk_only, new_request, placement_index)
|
|
200
|
+
current_index = 1
|
|
201
|
+
selected = Array.new
|
|
202
|
+
unless allowed_placements.nil? # topology-aware
|
|
203
|
+
eligible_hosts = Array.new
|
|
204
|
+
(placement_index..10).each { |idx|
|
|
205
|
+
current_index = idx
|
|
206
|
+
selected.clear
|
|
207
|
+
min_connections = 1000000 # Using some really high value
|
|
208
|
+
@@cluster_info.each do |host, node_info|
|
|
209
|
+
unless node_info.is_down
|
|
210
|
+
unless allowed_placements[idx].nil?
|
|
211
|
+
allowed_placements[idx].each do |cp|
|
|
212
|
+
if cp[0] == node_info.cloud && cp[1] == node_info.region && (cp[2] == node_info.zone || cp[2] == "*")
|
|
213
|
+
eligible_hosts << host
|
|
214
|
+
if node_info.count < min_connections
|
|
215
|
+
min_connections = node_info.count
|
|
216
|
+
selected.clear
|
|
217
|
+
selected.push(host)
|
|
218
|
+
elsif node_info.count == min_connections
|
|
219
|
+
selected.push(host)
|
|
220
|
+
end
|
|
221
|
+
break # Move to the next node
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
if selected.length > 0
|
|
228
|
+
break
|
|
229
|
+
end
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if allowed_placements.nil? || (selected.empty? && !fallback_to_tk_only) # cluster-aware || fallback_to_tk_only = false
|
|
234
|
+
unless allowed_placements.nil?
|
|
235
|
+
end
|
|
236
|
+
min_connections = 1000000 # Using some really high value
|
|
237
|
+
selected = Array.new
|
|
238
|
+
@@cluster_info.each do |host, node_info|
|
|
239
|
+
unless node_info.is_down
|
|
240
|
+
if node_info.count < min_connections
|
|
241
|
+
min_connections = node_info.count
|
|
242
|
+
selected.clear
|
|
243
|
+
selected.push(host)
|
|
244
|
+
elsif node_info.count == min_connections
|
|
245
|
+
selected.push(host)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
if selected.empty?
|
|
252
|
+
nil
|
|
253
|
+
else
|
|
254
|
+
index = rand(selected.size)
|
|
255
|
+
selected_node = selected[index]
|
|
256
|
+
@@cluster_info[selected_node].count += 1
|
|
257
|
+
if !@@useHostColumn.nil? && !@@useHostColumn
|
|
258
|
+
selected_node = @@cluster_info[selected_node].public_ip
|
|
259
|
+
end
|
|
260
|
+
Array[selected_node, @@cluster_info[selected_node].port, current_index]
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def self.parse_lb_args_from_url(conn_string)
|
|
265
|
+
string_parts = conn_string.split('?', -1)
|
|
266
|
+
if string_parts.length != 2
|
|
267
|
+
return conn_string, nil
|
|
268
|
+
else
|
|
269
|
+
base_string = string_parts[0] + "?"
|
|
270
|
+
lb_props = Hash.new
|
|
271
|
+
tokens = string_parts[1].split('&', -1)
|
|
272
|
+
tokens.each {
|
|
273
|
+
|token|
|
|
274
|
+
unless token.empty?
|
|
275
|
+
k, v = token.split('=', 2)
|
|
276
|
+
case k
|
|
277
|
+
when "load_balance"
|
|
278
|
+
lb_props[:load_balance] = v
|
|
279
|
+
when "topology_keys"
|
|
280
|
+
lb_props[:topology_keys] = v
|
|
281
|
+
when "yb_servers_refresh_interval"
|
|
282
|
+
lb_props[:yb_servers_refresh_interval] = v
|
|
283
|
+
when "failed_host_reconnect_delay_secs"
|
|
284
|
+
lb_props[:failed_host_reconnect_delay_secs] = v
|
|
285
|
+
when "fallback_to_topology_keys_only"
|
|
286
|
+
lb_props[:fallback_to_topology_keys_only] = v
|
|
287
|
+
else
|
|
288
|
+
# not LB-specific
|
|
289
|
+
base_string << "#{k}=#{v}&"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
base_string = base_string.chop if base_string[-1] == "&"
|
|
295
|
+
base_string = base_string.chop if base_string[-1] == "?"
|
|
296
|
+
if not lb_props.empty? and lb_props[:load_balance].to_s.downcase == "true"
|
|
297
|
+
return base_string, parse_connect_lb_args(lb_props)
|
|
298
|
+
else
|
|
299
|
+
return base_string, nil
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def self.parse_connect_lb_args(hash_arg)
|
|
305
|
+
lb = hash_arg.delete(:load_balance)
|
|
306
|
+
tk = hash_arg.delete(:topology_keys)
|
|
307
|
+
ri = hash_arg.delete(:yb_servers_refresh_interval)
|
|
308
|
+
ttl = hash_arg.delete(:failed_host_reconnect_delay_secs)
|
|
309
|
+
fb = hash_arg.delete(:fallback_to_topology_keys_only)
|
|
310
|
+
|
|
311
|
+
if lb && lb.to_s.downcase == "true"
|
|
312
|
+
lb_properties = LBProperties.new(nil, 300, false, 5)
|
|
313
|
+
if tk
|
|
314
|
+
lb_properties.placements_info = Hash.new
|
|
315
|
+
tk_parts = tk.split(',', -1)
|
|
316
|
+
tk_parts.each {
|
|
317
|
+
|single_tk|
|
|
318
|
+
if single_tk.empty?
|
|
319
|
+
raise ArgumentError, "Empty value for topology_keys specified"
|
|
320
|
+
end
|
|
321
|
+
single_tk_parts = single_tk.split(':', -1)
|
|
322
|
+
if single_tk_parts.length > 2
|
|
323
|
+
raise ArgumentError, "Invalid preference value '#{single_tk_parts}' specified for topology_keys: " + tk
|
|
324
|
+
end
|
|
325
|
+
cp = single_tk_parts[0].split('.', -1)
|
|
326
|
+
if cp.length != 3
|
|
327
|
+
raise ArgumentError, "Invalid cloud placement value '#{single_tk_parts[0]}' specified for topology_keys: " + tk
|
|
328
|
+
end
|
|
329
|
+
preference_value = 1
|
|
330
|
+
if single_tk_parts.length == 2
|
|
331
|
+
preference = single_tk_parts[1]
|
|
332
|
+
if preference == ""
|
|
333
|
+
raise ArgumentError, "No preference value specified for topology_keys: " + tk
|
|
334
|
+
end
|
|
335
|
+
begin
|
|
336
|
+
preference_value = Integer(preference).to_i
|
|
337
|
+
rescue
|
|
338
|
+
raise ArgumentError, "Invalid preference value '#{preference}' for topology_keys: " + tk
|
|
339
|
+
ensure
|
|
340
|
+
if preference_value < 1 || preference_value > 10
|
|
341
|
+
raise ArgumentError, "Invalid preference value '#{preference_value}' for topology_keys: " + tk
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
unless lb_properties.placements_info[preference_value]
|
|
346
|
+
lb_properties.placements_info[preference_value] = Set.new
|
|
347
|
+
end
|
|
348
|
+
lb_properties.placements_info[preference_value] << CloudPlacement.new(cp[0], cp[1], cp[2])
|
|
349
|
+
}
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
begin
|
|
353
|
+
lb_properties.refresh_interval = Integer(ri).to_i if ri
|
|
354
|
+
rescue ArgumentError => ae
|
|
355
|
+
lb_properties.refresh_interval = 300
|
|
356
|
+
ensure
|
|
357
|
+
if lb_properties.refresh_interval < 0 || lb_properties.refresh_interval > 600
|
|
358
|
+
lb_properties.refresh_interval = 300
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
begin
|
|
363
|
+
lb_properties.failed_host_reconnect_delay = Integer(ttl).to_i if ttl
|
|
364
|
+
rescue ArgumentError
|
|
365
|
+
ensure
|
|
366
|
+
if lb_properties.failed_host_reconnect_delay < 0 || lb_properties.failed_host_reconnect_delay > 60
|
|
367
|
+
lb_properties.failed_host_reconnect_delay = 5
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
lb_properties.fallback_to_tk_only = fb.to_s.downcase == "true" if fb
|
|
372
|
+
|
|
373
|
+
else
|
|
374
|
+
lb_properties = nil
|
|
375
|
+
end
|
|
376
|
+
lb_properties
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def self.metadata_needs_refresh(refresh_interval)
|
|
380
|
+
if Time.now.to_i - @@last_refresh_time >= refresh_interval # || force_refresh == true
|
|
381
|
+
true
|
|
382
|
+
else
|
|
383
|
+
false
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def self.resolve_host(mhost)
|
|
388
|
+
if YugabyteYSQL::Connection.host_is_named_pipe?(mhost)
|
|
389
|
+
# No hostname to resolve (UnixSocket)
|
|
390
|
+
hostaddrs = [nil]
|
|
391
|
+
else
|
|
392
|
+
if Fiber.respond_to?(:scheduler) &&
|
|
393
|
+
Fiber.scheduler &&
|
|
394
|
+
RUBY_VERSION < '3.1.'
|
|
395
|
+
|
|
396
|
+
# Use a second thread to avoid blocking of the scheduler.
|
|
397
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
|
398
|
+
hostaddrs = Thread.new { Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
|
399
|
+
else
|
|
400
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost] }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
end
|
data/lib/pg/result.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yugabyte_ysql' unless defined?( YugabyteYSQL )
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class YugabyteYSQL::Result
|
|
8
|
+
|
|
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
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end # class PG::Result
|
|
43
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
module YugabyteYSQL
|
|
7
|
+
module TextDecoder
|
|
8
|
+
class Date < SimpleDecoder
|
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
|
10
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
|
11
|
+
::Date.new $1.to_i, $2.to_i, $3.to_i
|
|
12
|
+
else
|
|
13
|
+
string
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end # module PG
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module YugabyteYSQL
|
|
7
|
+
module TextDecoder
|
|
8
|
+
class JSON < SimpleDecoder
|
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
|
10
|
+
::JSON.parse(string, quirks_mode: true)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end # module PG
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module YugabyteYSQL
|
|
5
|
+
module TextDecoder
|
|
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: YugabyteYSQL::Coder::TIMESTAMP_DB_UTC | YugabyteYSQL::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: YugabyteYSQL::Coder::TIMESTAMP_DB_UTC | YugabyteYSQL::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: YugabyteYSQL::Coder::TIMESTAMP_DB_LOCAL | YugabyteYSQL::Coder::TIMESTAMP_APP_LOCAL)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# For backward compatibility:
|
|
27
|
+
TimestampWithoutTimeZone = TimestampLocal
|
|
28
|
+
TimestampWithTimeZone = Timestamp
|
|
29
|
+
end
|
|
30
|
+
end # module PG
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'ipaddr'
|
|
5
|
+
|
|
6
|
+
module YugabyteYSQL
|
|
7
|
+
module TextEncoder
|
|
8
|
+
class Inet < SimpleEncoder
|
|
9
|
+
def encode(value)
|
|
10
|
+
case value
|
|
11
|
+
when IPAddr
|
|
12
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
|
13
|
+
s = value.to_s
|
|
14
|
+
if value.respond_to?(:prefix)
|
|
15
|
+
prefix = value.prefix
|
|
16
|
+
else
|
|
17
|
+
range = value.to_range
|
|
18
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
|
19
|
+
end
|
|
20
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
|
21
|
+
s
|
|
22
|
+
else
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end # module PG
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module YugabyteYSQL
|
|
5
|
+
module TextEncoder
|
|
6
|
+
class TimestampWithoutTimeZone < SimpleEncoder
|
|
7
|
+
def encode(value)
|
|
8
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class TimestampUtc < SimpleEncoder
|
|
13
|
+
def encode(value)
|
|
14
|
+
value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class TimestampWithTimeZone < SimpleEncoder
|
|
19
|
+
def encode(value)
|
|
20
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end # module PG
|
data/lib/pg/tuple.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yugabyte_ysql' unless defined?( YugabyteYSQL )
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class YugabyteYSQL::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
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- ruby -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yugabyte_ysql' unless defined?( YugabyteYSQL )
|
|
5
|
+
|
|
6
|
+
class YugabyteYSQL::TypeMapByColumn
|
|
7
|
+
# Returns the type oids of the assigned coders.
|
|
8
|
+
def oids
|
|
9
|
+
coders.map{|c| c.oid if c }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inspect
|
|
13
|
+
type_strings = coders.map{|c| c ? c.inspect_short : 'nil' }
|
|
14
|
+
"#<#{self.class} #{type_strings.join(' ')}>"
|
|
15
|
+
end
|
|
16
|
+
end
|