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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +42 -0
  3. data/.gems +6 -0
  4. data/.gemtest +0 -0
  5. data/.github/workflows/binary-gems.yml +117 -0
  6. data/.github/workflows/source-gem.yml +143 -0
  7. data/.gitignore +24 -0
  8. data/.hgsigs +34 -0
  9. data/.hgtags +41 -0
  10. data/.irbrc +23 -0
  11. data/.pryrc +23 -0
  12. data/.tm_properties +21 -0
  13. data/.travis.yml +49 -0
  14. data/BSDL +22 -0
  15. data/Contributors.rdoc +46 -0
  16. data/Gemfile +18 -0
  17. data/History.md +901 -0
  18. data/LICENSE +56 -0
  19. data/Manifest.txt +73 -0
  20. data/POSTGRES +23 -0
  21. data/README-OS_X.rdoc +68 -0
  22. data/README-Windows.rdoc +56 -0
  23. data/README.ja.md +302 -0
  24. data/README.md +373 -0
  25. data/Rakefile +118 -0
  26. data/Rakefile.cross +299 -0
  27. data/certs/ged.pem +24 -0
  28. data/certs/kanis@comcard.de.pem +20 -0
  29. data/certs/larskanis-2022.pem +26 -0
  30. data/certs/larskanis-2023.pem +24 -0
  31. data/certs/larskanis-2024.pem +24 -0
  32. data/ext/errorcodes.def +1044 -0
  33. data/ext/errorcodes.rb +45 -0
  34. data/ext/errorcodes.txt +497 -0
  35. data/ext/extconf.rb +174 -0
  36. data/ext/gvl_wrappers.c +21 -0
  37. data/ext/gvl_wrappers.h +264 -0
  38. data/ext/pg.c +692 -0
  39. data/ext/pg.h +392 -0
  40. data/ext/pg_binary_decoder.c +308 -0
  41. data/ext/pg_binary_encoder.c +387 -0
  42. data/ext/pg_coder.c +624 -0
  43. data/ext/pg_connection.c +4681 -0
  44. data/ext/pg_copy_coder.c +917 -0
  45. data/ext/pg_errors.c +95 -0
  46. data/ext/pg_record_coder.c +522 -0
  47. data/ext/pg_result.c +1766 -0
  48. data/ext/pg_text_decoder.c +1005 -0
  49. data/ext/pg_text_encoder.c +827 -0
  50. data/ext/pg_tuple.c +572 -0
  51. data/ext/pg_type_map.c +200 -0
  52. data/ext/pg_type_map_all_strings.c +130 -0
  53. data/ext/pg_type_map_by_class.c +271 -0
  54. data/ext/pg_type_map_by_column.c +355 -0
  55. data/ext/pg_type_map_by_mri_type.c +313 -0
  56. data/ext/pg_type_map_by_oid.c +388 -0
  57. data/ext/pg_type_map_in_ruby.c +333 -0
  58. data/ext/pg_util.c +149 -0
  59. data/ext/pg_util.h +65 -0
  60. data/ext/vc/pg.sln +26 -0
  61. data/ext/vc/pg_18/pg.vcproj +216 -0
  62. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  63. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  64. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  65. data/lib/pg/basic_type_map_for_results.rb +104 -0
  66. data/lib/pg/basic_type_registry.rb +303 -0
  67. data/lib/pg/binary_decoder/date.rb +9 -0
  68. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  69. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  70. data/lib/pg/coder.rb +106 -0
  71. data/lib/pg/connection.rb +990 -0
  72. data/lib/pg/exceptions.rb +25 -0
  73. data/lib/pg/load_balance_service.rb +406 -0
  74. data/lib/pg/result.rb +43 -0
  75. data/lib/pg/text_decoder/date.rb +18 -0
  76. data/lib/pg/text_decoder/inet.rb +9 -0
  77. data/lib/pg/text_decoder/json.rb +14 -0
  78. data/lib/pg/text_decoder/numeric.rb +9 -0
  79. data/lib/pg/text_decoder/timestamp.rb +30 -0
  80. data/lib/pg/text_encoder/date.rb +12 -0
  81. data/lib/pg/text_encoder/inet.rb +28 -0
  82. data/lib/pg/text_encoder/json.rb +14 -0
  83. data/lib/pg/text_encoder/numeric.rb +9 -0
  84. data/lib/pg/text_encoder/timestamp.rb +24 -0
  85. data/lib/pg/tuple.rb +30 -0
  86. data/lib/pg/type_map_by_column.rb +16 -0
  87. data/lib/pg/version.rb +5 -0
  88. data/lib/yugabyte_ysql.rb +130 -0
  89. data/misc/openssl-pg-segfault.rb +31 -0
  90. data/misc/postgres/History.txt +9 -0
  91. data/misc/postgres/Manifest.txt +5 -0
  92. data/misc/postgres/README.txt +21 -0
  93. data/misc/postgres/Rakefile +21 -0
  94. data/misc/postgres/lib/postgres.rb +16 -0
  95. data/misc/ruby-pg/History.txt +9 -0
  96. data/misc/ruby-pg/Manifest.txt +5 -0
  97. data/misc/ruby-pg/README.txt +21 -0
  98. data/misc/ruby-pg/Rakefile +21 -0
  99. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  100. data/rakelib/task_extension.rb +46 -0
  101. data/sample/array_insert.rb +20 -0
  102. data/sample/async_api.rb +102 -0
  103. data/sample/async_copyto.rb +39 -0
  104. data/sample/async_mixed.rb +56 -0
  105. data/sample/check_conn.rb +21 -0
  106. data/sample/copydata.rb +71 -0
  107. data/sample/copyfrom.rb +81 -0
  108. data/sample/copyto.rb +19 -0
  109. data/sample/cursor.rb +21 -0
  110. data/sample/disk_usage_report.rb +177 -0
  111. data/sample/issue-119.rb +94 -0
  112. data/sample/losample.rb +69 -0
  113. data/sample/minimal-testcase.rb +17 -0
  114. data/sample/notify_wait.rb +72 -0
  115. data/sample/pg_statistics.rb +285 -0
  116. data/sample/replication_monitor.rb +222 -0
  117. data/sample/test_binary_values.rb +33 -0
  118. data/sample/wal_shipper.rb +434 -0
  119. data/sample/warehouse_partitions.rb +311 -0
  120. data/yugabyte_ysql.gemspec +33 -0
  121. 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,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module YugabyteYSQL
5
+ module TextDecoder
6
+ # Init C part of the decoder
7
+ init_inet
8
+ end
9
+ 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,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module YugabyteYSQL
5
+ module TextDecoder
6
+ # Init C part of the decoder
7
+ init_numeric
8
+ end
9
+ 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,12 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module YugabyteYSQL
5
+ module TextEncoder
6
+ class Date < SimpleEncoder
7
+ def encode(value)
8
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
9
+ end
10
+ end
11
+ end
12
+ 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,14 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+
6
+ module YugabyteYSQL
7
+ module TextEncoder
8
+ class JSON < SimpleEncoder
9
+ def encode(value)
10
+ ::JSON.generate(value, quirks_mode: true)
11
+ end
12
+ end
13
+ end
14
+ end # module PG
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module YugabyteYSQL
5
+ module TextEncoder
6
+ # Init C part of the decoder
7
+ init_numeric
8
+ end
9
+ 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