yugabytedb-ysql 0.3

Sign up to get free protection for your applications and to get access to all the features.
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 +381 -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 +997 -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/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/yugabytedb-ysql.gemspec +33 -0
  121. metadata +232 -0
@@ -0,0 +1,25 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'ysql' unless defined?( YSQL )
5
+
6
+
7
+ module YSQL
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 < YSQL::Error
18
+ end
19
+ class LostCopyState < YSQL::Error
20
+ end
21
+ class NotInBlockingMode < YSQL::Error
22
+ end
23
+
24
+ end # module PG
25
+
@@ -0,0 +1,406 @@
1
+ # frozen_string_literal: true
2
+ require 'ysql' unless defined?( YSQL )
3
+ require 'concurrent'
4
+
5
+ class YSQL::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 = YSQL.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 = YSQL.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(YSQL::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
+ # 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 YSQL::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 'ysql' unless defined?( YSQL )
5
+
6
+
7
+ class YSQL::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 YSQL
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 YSQL
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 YSQL
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 YSQL
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 YSQL
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: YSQL::Coder::TIMESTAMP_DB_UTC | YSQL::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: YSQL::Coder::TIMESTAMP_DB_UTC | YSQL::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: YSQL::Coder::TIMESTAMP_DB_LOCAL | YSQL::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 YSQL
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 YSQL
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 YSQL
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 YSQL
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 YSQL
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 'ysql' unless defined?( YSQL )
5
+
6
+
7
+ class YSQL::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 'ysql' unless defined?( YSQL )
5
+
6
+ class YSQL::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