aws_advanced_ruby_driver_wrapper 1.0.0
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/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
# Loads the gem's top-level setup (version constant, global configuration) that this client relies on,
|
|
18
|
+
# so requiring this file alone is enough to use the PostgreSQL client.
|
|
19
|
+
require_relative '../aws_advanced_ruby_driver_wrapper'
|
|
20
|
+
require_relative 'utils/connection_config_parser'
|
|
21
|
+
require_relative 'utils/sql_encoding'
|
|
22
|
+
require_relative 'services/service_utility'
|
|
23
|
+
require_relative 'ruby_method'
|
|
24
|
+
require_relative 'errors'
|
|
25
|
+
|
|
26
|
+
module AwsAdvancedRubyDriverWrapper
|
|
27
|
+
class WrapperPgConnection
|
|
28
|
+
class << self
|
|
29
|
+
def new(*, **)
|
|
30
|
+
instance = allocate
|
|
31
|
+
instance.send(:initialize, *, **)
|
|
32
|
+
return instance unless block_given?
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
yield instance
|
|
36
|
+
ensure
|
|
37
|
+
instance.close
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias open new
|
|
42
|
+
alias connect new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(*, **)
|
|
46
|
+
ensure_pg!
|
|
47
|
+
config = Utils::ConnectionConfigParser.parse(:postgresql, *, **)
|
|
48
|
+
@service_container = Services::ServiceUtility.create_standard_container(config)
|
|
49
|
+
@service_container.host_service.refresh_host_list
|
|
50
|
+
@prepared_on = {}
|
|
51
|
+
@prepared_sql = {}
|
|
52
|
+
@async_conn = nil
|
|
53
|
+
@async_sql = nil
|
|
54
|
+
@copy_conn = nil
|
|
55
|
+
@copy_sql = nil
|
|
56
|
+
@lo_conn = nil
|
|
57
|
+
conn_service = @service_container.connection_service
|
|
58
|
+
@service_container.plugin_manager.connect(conn_service.initial_host_info, conn_service.driver_props, true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Every canonical pg operation that talks to the server, and everything this class has to know about one: the
|
|
62
|
+
# name the plugins see it under, the connection it is bound to, where the SQL it carries is among its
|
|
63
|
+
# arguments, and the steps that should be performed after.
|
|
64
|
+
#
|
|
65
|
+
# An operation with no +sql_at+ takes the SQL of whatever it is bound to: the statement a prepared
|
|
66
|
+
# operation names, the statement a pending exchange was started with, or the statement a COPY was
|
|
67
|
+
# opened with. That is the whole reason each of them is remembered, since none is among the arguments
|
|
68
|
+
# of the call that reads it back.
|
|
69
|
+
OPERATIONS = {
|
|
70
|
+
exec: { method: RubyMethod::CONNECTION_EXEC, sql_at: 0, after: :remember_sql_prepared },
|
|
71
|
+
async_exec: { method: RubyMethod::CONNECTION_ASYNC_EXEC, sql_at: 0, after: :remember_sql_prepared },
|
|
72
|
+
exec_params: { method: RubyMethod::CONNECTION_EXEC_PARAMS, sql_at: 0, after: :remember_sql_prepared },
|
|
73
|
+
transaction: { method: RubyMethod::CONNECTION_TRANSACTION },
|
|
74
|
+
close: { method: RubyMethod::CONNECTION_CLOSE },
|
|
75
|
+
reset: { method: RubyMethod::CONNECTION_RESET, after: :reset_session_state },
|
|
76
|
+
reset_start: { method: RubyMethod::CONNECTION_RESET_START },
|
|
77
|
+
reset_poll: { method: RubyMethod::CONNECTION_RESET_POLL },
|
|
78
|
+
|
|
79
|
+
# A prepared statement only exists on the connection it was prepared on.
|
|
80
|
+
prepare: { method: RubyMethod::CONNECTION_PREPARE, sql_at: 1, after: :remember_prepared },
|
|
81
|
+
send_prepare: { method: RubyMethod::CONNECTION_SEND_PREPARE, sql_at: 1, after: %i[remember_prepared remember_async] },
|
|
82
|
+
exec_prepared: { method: RubyMethod::CONNECTION_EXEC_PREPARED, bound_to: :prepared },
|
|
83
|
+
describe_prepared: { method: RubyMethod::CONNECTION_DESCRIBE_PREPARED, bound_to: :prepared },
|
|
84
|
+
send_query_prepared: { method: RubyMethod::CONNECTION_SEND_QUERY_PREPARED, bound_to: :prepared, after: :remember_async },
|
|
85
|
+
send_describe_prepared: { method: RubyMethod::CONNECTION_SEND_DESCRIBE_PREPARED, bound_to: :prepared, after: :remember_async },
|
|
86
|
+
close_prepared: { method: RubyMethod::CONNECTION_CLOSE_PREPARED, bound_to: :prepared, after: :forget_prepared },
|
|
87
|
+
|
|
88
|
+
# A pending exchange, and the portal it may have left, can only be continued on the connection it
|
|
89
|
+
# was started on.
|
|
90
|
+
send_query: { method: RubyMethod::CONNECTION_SEND_QUERY, sql_at: 0, after: %i[remember_async remember_sql_prepared] },
|
|
91
|
+
send_query_params: { method: RubyMethod::CONNECTION_SEND_QUERY_PARAMS, sql_at: 0,
|
|
92
|
+
after: %i[remember_async remember_sql_prepared] },
|
|
93
|
+
get_result: { method: RubyMethod::CONNECTION_GET_RESULT, bound_to: :async, after: :forget_async_when_drained },
|
|
94
|
+
get_last_result: { method: RubyMethod::CONNECTION_GET_LAST_RESULT, bound_to: :async, after: :forget_async },
|
|
95
|
+
describe_portal: { method: RubyMethod::CONNECTION_DESCRIBE_PORTAL, bound_to: :async },
|
|
96
|
+
close_portal: { method: RubyMethod::CONNECTION_CLOSE_PORTAL, bound_to: :async },
|
|
97
|
+
send_describe_portal: { method: RubyMethod::CONNECTION_SEND_DESCRIBE_PORTAL, bound_to: :async, after: :remember_async },
|
|
98
|
+
send_flush_request: { method: RubyMethod::CONNECTION_SEND_FLUSH_REQUEST, bound_to: :async, after: :remember_async },
|
|
99
|
+
pipeline_sync: { method: RubyMethod::CONNECTION_PIPELINE_SYNC, bound_to: :async },
|
|
100
|
+
send_pipeline_sync: { method: RubyMethod::CONNECTION_SEND_PIPELINE_SYNC, bound_to: :async, after: :remember_async },
|
|
101
|
+
discard_results: { method: RubyMethod::CONNECTION_DISCARD_RESULTS, bound_to: :async, after: :forget_async },
|
|
102
|
+
block: { method: RubyMethod::CONNECTION_BLOCK, bound_to: :async },
|
|
103
|
+
|
|
104
|
+
# A COPY can only be fed or read on the connection it was started on.
|
|
105
|
+
copy_data: { method: RubyMethod::CONNECTION_COPY_DATA, sql_at: 0 },
|
|
106
|
+
put_copy_data: { method: RubyMethod::CONNECTION_PUT_COPY_DATA, bound_to: :copy },
|
|
107
|
+
get_copy_data: { method: RubyMethod::CONNECTION_GET_COPY_DATA, bound_to: :copy },
|
|
108
|
+
put_copy_end: { method: RubyMethod::CONNECTION_PUT_COPY_END, bound_to: :copy, after: :forget_copy },
|
|
109
|
+
|
|
110
|
+
cancel: { method: RubyMethod::CONNECTION_CANCEL },
|
|
111
|
+
flush: { method: RubyMethod::CONNECTION_FLUSH },
|
|
112
|
+
consume_input: { method: RubyMethod::CONNECTION_CONSUME_INPUT },
|
|
113
|
+
notifies: { method: RubyMethod::CONNECTION_NOTIFIES },
|
|
114
|
+
wait_for_notify: { method: RubyMethod::CONNECTION_WAIT_FOR_NOTIFY },
|
|
115
|
+
encrypt_password: { method: RubyMethod::CONNECTION_ENCRYPT_PASSWORD },
|
|
116
|
+
set_client_encoding: { method: RubyMethod::CONNECTION_SET_CLIENT_ENCODING },
|
|
117
|
+
set_default_encoding: { method: RubyMethod::CONNECTION_SET_DEFAULT_ENCODING },
|
|
118
|
+
'internal_encoding=': { method: RubyMethod::CONNECTION_INTERNAL_ENCODING_SET },
|
|
119
|
+
|
|
120
|
+
# A large object descriptor is only open on the connection that opened it.
|
|
121
|
+
lo_creat: { method: RubyMethod::CONNECTION_LO_CREAT },
|
|
122
|
+
lo_create: { method: RubyMethod::CONNECTION_LO_CREATE },
|
|
123
|
+
lo_import: { method: RubyMethod::CONNECTION_LO_IMPORT },
|
|
124
|
+
lo_export: { method: RubyMethod::CONNECTION_LO_EXPORT },
|
|
125
|
+
lo_unlink: { method: RubyMethod::CONNECTION_LO_UNLINK },
|
|
126
|
+
lo_open: { method: RubyMethod::CONNECTION_LO_OPEN, after: :remember_large_object },
|
|
127
|
+
lo_read: { method: RubyMethod::CONNECTION_LO_READ, bound_to: :large_object },
|
|
128
|
+
lo_write: { method: RubyMethod::CONNECTION_LO_WRITE, bound_to: :large_object },
|
|
129
|
+
lo_lseek: { method: RubyMethod::CONNECTION_LO_LSEEK, bound_to: :large_object },
|
|
130
|
+
lo_tell: { method: RubyMethod::CONNECTION_LO_TELL, bound_to: :large_object },
|
|
131
|
+
lo_truncate: { method: RubyMethod::CONNECTION_LO_TRUNCATE, bound_to: :large_object },
|
|
132
|
+
lo_close: { method: RubyMethod::CONNECTION_LO_CLOSE, bound_to: :large_object, after: :forget_large_object }
|
|
133
|
+
}.freeze
|
|
134
|
+
|
|
135
|
+
# pg gives most operations more than one spelling, and an application is free to use any of them. Each
|
|
136
|
+
# spelling here is mapped to the operation it performs, so that spellings enter the pipeline under one
|
|
137
|
+
# canonical name and get the same +bound_to+, +sql_at+ and +after+ handling its {OPERATIONS} entry asks
|
|
138
|
+
# for. Only the pipeline name is shared: the driver is still called under the original spelling.
|
|
139
|
+
#
|
|
140
|
+
# For example, the +sync_+ and +async_+ forms of an operation are two different calls, the first
|
|
141
|
+
# blocking in libpq and the second sending and then waiting on the socket from Ruby, where the
|
|
142
|
+
# wait can be interrupted. Which of the two a bare +exec+ means is itself settable, through
|
|
143
|
+
# +PG::Connection.async_api=+.
|
|
144
|
+
#
|
|
145
|
+
# A +sync_+ or +async_+ spelling that is missing here is still recognized, by {#operation_for}
|
|
146
|
+
# removing the prefix. The current spellings are written out here anyway, so that the list can
|
|
147
|
+
# be checked against the gem.
|
|
148
|
+
OPERATION_BY_SPELLING = {
|
|
149
|
+
async_query: :exec, sync_exec: :exec,
|
|
150
|
+
async_exec_params: :exec_params, sync_exec_params: :exec_params,
|
|
151
|
+
async_exec_prepared: :exec_prepared, sync_exec_prepared: :exec_prepared,
|
|
152
|
+
async_prepare: :prepare, sync_prepare: :prepare,
|
|
153
|
+
async_describe_prepared: :describe_prepared, sync_describe_prepared: :describe_prepared,
|
|
154
|
+
async_describe_portal: :describe_portal, sync_describe_portal: :describe_portal,
|
|
155
|
+
async_get_result: :get_result, sync_get_result: :get_result,
|
|
156
|
+
async_get_last_result: :get_last_result, sync_get_last_result: :get_last_result,
|
|
157
|
+
async_put_copy_data: :put_copy_data, sync_put_copy_data: :put_copy_data,
|
|
158
|
+
async_get_copy_data: :get_copy_data, sync_get_copy_data: :get_copy_data,
|
|
159
|
+
async_put_copy_end: :put_copy_end, sync_put_copy_end: :put_copy_end,
|
|
160
|
+
async_reset: :reset, sync_reset: :reset,
|
|
161
|
+
async_cancel: :cancel, sync_cancel: :cancel,
|
|
162
|
+
async_flush: :flush, sync_flush: :flush,
|
|
163
|
+
async_close_prepared: :close_prepared, sync_close_prepared: :close_prepared,
|
|
164
|
+
async_close_portal: :close_portal, sync_close_portal: :close_portal,
|
|
165
|
+
async_pipeline_sync: :pipeline_sync, sync_pipeline_sync: :pipeline_sync,
|
|
166
|
+
async_encrypt_password: :encrypt_password, sync_encrypt_password: :encrypt_password,
|
|
167
|
+
async_set_client_encoding: :set_client_encoding, sync_set_client_encoding: :set_client_encoding,
|
|
168
|
+
'client_encoding=': :set_client_encoding,
|
|
169
|
+
notifies_wait: :wait_for_notify,
|
|
170
|
+
locreat: :lo_creat, locreate: :lo_create, loimport: :lo_import, loexport: :lo_export,
|
|
171
|
+
lounlink: :lo_unlink, loopen: :lo_open, loread: :lo_read, lowrite: :lo_write,
|
|
172
|
+
loclose: :lo_close, lolseek: :lo_lseek, lo_seek: :lo_lseek, loseek: :lo_lseek,
|
|
173
|
+
lotell: :lo_tell, lotruncate: :lo_truncate
|
|
174
|
+
}.freeze
|
|
175
|
+
|
|
176
|
+
# A statement can also be prepared by sending a +PREPARE+ rather than by calling pg's own
|
|
177
|
+
# +prepare+, and the +exec_prepared+ that runs it looks no different either way. The two are read
|
|
178
|
+
# here so that a statement prepared the first way is remembered like one prepared the second, and
|
|
179
|
+
# a plugin that has to inspect the statement a call runs still has it to look at.
|
|
180
|
+
#
|
|
181
|
+
# The name is an identifier, so an unquoted one is folded to lower case. The parameter types in
|
|
182
|
+
# front of +AS+ are optional. What follows +AS+ is the statement, to the end of the string, which
|
|
183
|
+
# a +PREPARE+ shares with nothing else unless the caller sent more than one statement at once.
|
|
184
|
+
#
|
|
185
|
+
# Read with a pattern rather than a parse because this sits on the path of every statement the
|
|
186
|
+
# connection sends, and the two pieces wanted here are a name and everything after +AS+.
|
|
187
|
+
# +Utils::Parser::PgStatementAnalyzer+ reads the same construct properly, from the parse tree, for
|
|
188
|
+
# the plugin that has to know what the carried statement writes.
|
|
189
|
+
STATEMENT_NAME = /"(?:[^"]|"")+"|\w+/
|
|
190
|
+
SQL_PREPARE = /\A\s*PREPARE\s+(#{STATEMENT_NAME})\s*(?:\([^)]*\)\s*)?AS\s+(.+)\z/im
|
|
191
|
+
# +DEALLOCATE [PREPARE] { name | ALL }+ un-prepares what a +PREPARE+ prepared, which is what
|
|
192
|
+
# +close_prepared+ does to a statement prepared through the driver.
|
|
193
|
+
SQL_DEALLOCATE = /\A\s*DEALLOCATE\s+(?:PREPARE\s+)?(#{STATEMENT_NAME})\s*;?\s*\z/im
|
|
194
|
+
|
|
195
|
+
# Explicitly define critical methods (bypass method_missing to avoid method_missing overhead).
|
|
196
|
+
|
|
197
|
+
def exec(sql, *params)
|
|
198
|
+
execute_operation(:exec, [sql, *params])
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# pg spells this operation +exec+, +query+, +async_exec+ and +async_query+, all of which run the
|
|
202
|
+
# same libpq call. +query+ is defined here rather than left to method_missing because it is the
|
|
203
|
+
# spelling applications use most after +exec+, and it enters the pipeline as +connection.exec+,
|
|
204
|
+
# since that is the libpq operation being performed.
|
|
205
|
+
def query(sql, *params)
|
|
206
|
+
execute_operation(:exec, [sql, *params], spelling: :query)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def exec_params(sql, params, result_format = 0, type_map = nil)
|
|
210
|
+
execute_operation(:exec_params, [sql, params, result_format, type_map])
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def async_exec(sql, *params)
|
|
214
|
+
execute_operation(:async_exec, [sql, *params])
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def transaction(&)
|
|
218
|
+
execute_operation(:transaction, &)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def close
|
|
222
|
+
execute_operation(:close)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
alias finish close
|
|
226
|
+
|
|
227
|
+
# Resets the connection through the pipeline and returns this wrapper, so the reset connection stays
|
|
228
|
+
# usable through it. The driver's own reset tears down and re-establishes the underlying socket, which
|
|
229
|
+
# clears any server-side session state, so the tracked session state is reset to match.
|
|
230
|
+
def reset
|
|
231
|
+
execute_operation(:reset)
|
|
232
|
+
self
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# -- Prepared statements --
|
|
236
|
+
|
|
237
|
+
def prepare(stmt_name, sql, param_types = nil)
|
|
238
|
+
execute_operation(:prepare, [stmt_name, sql, param_types])
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def send_prepare(stmt_name, sql, param_types = nil)
|
|
242
|
+
execute_operation(:send_prepare, [stmt_name, sql, param_types])
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def exec_prepared(stmt_name, params = [], result_format = 0, type_map = nil)
|
|
246
|
+
execute_operation(:exec_prepared, [stmt_name, params, result_format, type_map])
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def describe_prepared(stmt_name)
|
|
250
|
+
execute_operation(:describe_prepared, [stmt_name])
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def send_query_prepared(stmt_name, params = [], result_format = 0, type_map = nil)
|
|
254
|
+
execute_operation(:send_query_prepared, [stmt_name, params, result_format, type_map])
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# -- Pending exchanges --
|
|
258
|
+
|
|
259
|
+
def send_query(sql, *params)
|
|
260
|
+
execute_operation(:send_query, [sql, *params])
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def send_query_params(sql, params, result_format = 0, type_map = nil)
|
|
264
|
+
execute_operation(:send_query_params, [sql, params, result_format, type_map])
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def get_result # rubocop:disable Naming/AccessorMethodName
|
|
268
|
+
execute_operation(:get_result)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def get_last_result # rubocop:disable Naming/AccessorMethodName
|
|
272
|
+
execute_operation(:get_last_result)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# -- COPY --
|
|
276
|
+
|
|
277
|
+
# The connection and the statement are held for as long as the block runs and let go afterward even
|
|
278
|
+
# if the block raises. The rows the block feeds or reads belong to that statement, and it is the only
|
|
279
|
+
# place they are named, so it is what the calls inside the block publish. It is held as the copy
|
|
280
|
+
# plugins inspect, made once here rather than on every row.
|
|
281
|
+
def copy_data(sql, coder = nil, &)
|
|
282
|
+
@copy_conn = current_conn
|
|
283
|
+
@copy_sql = Utils::SqlEncoding.inspectable(sql)
|
|
284
|
+
execute_operation(:copy_data, [sql, coder], &)
|
|
285
|
+
ensure
|
|
286
|
+
@copy_conn = nil
|
|
287
|
+
@copy_sql = nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def put_copy_data(buffer, encoder = nil)
|
|
291
|
+
execute_operation(:put_copy_data, [buffer, encoder])
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def get_copy_data(async = false, decoder = nil)
|
|
295
|
+
execute_operation(:get_copy_data, [async, decoder])
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def put_copy_end(error_message = nil)
|
|
299
|
+
execute_operation(:put_copy_end, [error_message])
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# -- method_missing: covers non-network calls, the other spellings pg gives a call, and the network
|
|
303
|
+
# calls that are rare enough not to be worth a method of their own --
|
|
304
|
+
|
|
305
|
+
def method_missing(method_name, *args, **kwargs, &)
|
|
306
|
+
conn = current_conn
|
|
307
|
+
raise NoMethodError, 'Connection not initialized' if conn.nil?
|
|
308
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless conn.respond_to?(method_name)
|
|
309
|
+
|
|
310
|
+
operation = operation_for(method_name)
|
|
311
|
+
return conn.send(method_name, *args, **kwargs, &) if operation.nil?
|
|
312
|
+
|
|
313
|
+
execute_operation(operation, args, kwargs, spelling: method_name, &)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def respond_to_missing?(method, include_private = false)
|
|
317
|
+
OPERATION_BY_SPELLING.key?(method) || current_conn.respond_to?(method, include_private) || super
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# A concise representation that never exposes the connection config (which carries
|
|
321
|
+
# credentials) or the cached SQL text this instance holds. Defined so a default
|
|
322
|
+
# dump - via logging, interpolation, `pp`, or a backtrace - cannot leak either.
|
|
323
|
+
def inspect
|
|
324
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
325
|
+
end
|
|
326
|
+
alias to_s inspect
|
|
327
|
+
|
|
328
|
+
def pretty_print(pp)
|
|
329
|
+
pp.text(inspect)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
private
|
|
333
|
+
|
|
334
|
+
# Runs one canonical operation through the pipeline, passing the operation's `bound_to` connection and
|
|
335
|
+
# the SQL it carries, and performing any `after` steps as necessary. The SQL is read before the call is
|
|
336
|
+
# made, since an `after` step may be what forgets it, and it is handed to the result as well, so that a
|
|
337
|
+
# plugin which has to inspect the statement still sees it when the rows are read.
|
|
338
|
+
def execute_operation(operation, args = [], kwargs = {}, spelling: operation, &)
|
|
339
|
+
spec = OPERATIONS[operation] || { method: "connection.#{operation}" }
|
|
340
|
+
conn = current_conn
|
|
341
|
+
# Guard against a missing connection. Fail loudly instead.
|
|
342
|
+
raise NoMethodError, 'Connection not initialized' if conn.nil?
|
|
343
|
+
|
|
344
|
+
# Only forward keyword arguments when there are any.
|
|
345
|
+
sql = sql_for(spec, args)
|
|
346
|
+
result =
|
|
347
|
+
if kwargs.empty?
|
|
348
|
+
pm.execute(
|
|
349
|
+
spec[:method], conn,
|
|
350
|
+
->(*a, &b) { current_conn.public_send(spelling, *a, &b) },
|
|
351
|
+
*args, bounded_conn: bounded_conn_for(spec[:bound_to], args), sql: sql, &
|
|
352
|
+
)
|
|
353
|
+
else
|
|
354
|
+
pm.execute(
|
|
355
|
+
spec[:method], conn,
|
|
356
|
+
->(*a, **opts, &b) { current_conn.public_send(spelling, *a, **opts, &b) },
|
|
357
|
+
*args, **kwargs, bounded_conn: bounded_conn_for(spec[:bound_to], args), sql: sql, &
|
|
358
|
+
)
|
|
359
|
+
end
|
|
360
|
+
Array(spec[:after]).each { |hook| send(hook, args, result, sql) }
|
|
361
|
+
wrap_pg_result(result, sql)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# The operation a call performs, whatever spelling it arrived under, or nil for a call that does not
|
|
365
|
+
# talk to the server and so has no business in the pipeline.
|
|
366
|
+
def operation_for(spelling)
|
|
367
|
+
operation = OPERATION_BY_SPELLING[spelling] || spelling
|
|
368
|
+
return operation if OPERATIONS.key?(operation)
|
|
369
|
+
|
|
370
|
+
# A spelling pg has added since {OPERATION_BY_SPELLING} was written. +sync_+ and +async_+ are its
|
|
371
|
+
# own prefixes for the two ways it performs an operation, so whatever is left once one of them is
|
|
372
|
+
# removed names that operation.
|
|
373
|
+
stripped = spelling.to_s.sub(/\A(a?sync)_/, '').to_sym
|
|
374
|
+
return stripped if OPERATIONS.key?(stripped)
|
|
375
|
+
|
|
376
|
+
# Listed by the dialect but not described in {OPERATIONS}, which means the two have got out of
|
|
377
|
+
# step. It still talks to the server, so it still goes through the plugins, under a name they can
|
|
378
|
+
# match on, though without the bounded connection check that only a named method gets.
|
|
379
|
+
spelling if network_bound_methods.include?("connection.#{spelling}")
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# @return [Object, nil] the connection the operation is bound to, if it is bound to one
|
|
383
|
+
def bounded_conn_for(bound_to, args)
|
|
384
|
+
case bound_to
|
|
385
|
+
when :prepared then @prepared_on[args.first]
|
|
386
|
+
when :async then @async_conn
|
|
387
|
+
when :copy then @copy_conn
|
|
388
|
+
when :large_object then @lo_conn
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# @return [String, nil] the SQL the operation carries, taken from its arguments when it names a
|
|
393
|
+
# statement of its own and from whatever it is bound to when it does not, as a copy that is
|
|
394
|
+
# safe to inspect
|
|
395
|
+
def sql_for(spec, args)
|
|
396
|
+
sql =
|
|
397
|
+
if spec[:sql_at]
|
|
398
|
+
args[spec[:sql_at]]
|
|
399
|
+
else
|
|
400
|
+
case spec[:bound_to]
|
|
401
|
+
when :prepared then @prepared_sql[args.first]
|
|
402
|
+
when :async then @async_sql
|
|
403
|
+
when :copy then @copy_sql
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
Utils::SqlEncoding.inspectable(sql)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# -- What an operation leaves behind, named by the +after+ entries of {OPERATIONS} --
|
|
410
|
+
|
|
411
|
+
# A reset re-establishes the underlying socket, dropping any server-side session state (open
|
|
412
|
+
# transaction, autocommit setting), so the tracked state is reset to match the fresh connection.
|
|
413
|
+
def reset_session_state(_args, _result, _sql)
|
|
414
|
+
@service_container.session_state_service.reset
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def remember_prepared(args, _result, sql)
|
|
418
|
+
@prepared_on[args.first] = current_conn
|
|
419
|
+
@prepared_sql[args.first] = sql
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def forget_prepared(args, _result, _sql)
|
|
423
|
+
@prepared_on.delete(args.first)
|
|
424
|
+
@prepared_sql.delete(args.first)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# A +PREPARE+ or +DEALLOCATE+ that was sent as a statement, treated as the +prepare+ or the
|
|
428
|
+
# +close_prepared+ it amounts to. Anything else that was sent is left alone.
|
|
429
|
+
def remember_sql_prepared(_args, _result, sql)
|
|
430
|
+
return unless sql.is_a?(String)
|
|
431
|
+
|
|
432
|
+
if (prepared = SQL_PREPARE.match(sql))
|
|
433
|
+
remember_prepared([statement_name_of(prepared[1])], nil, prepared[2].strip)
|
|
434
|
+
elsif (deallocated = SQL_DEALLOCATE.match(sql))
|
|
435
|
+
forget_sql_prepared(deallocated[1])
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# +DEALLOCATE ALL+ un-prepares every statement of the session, which +ALL+ in quotes does not: that
|
|
440
|
+
# names one statement actually called +ALL+.
|
|
441
|
+
def forget_sql_prepared(name_token)
|
|
442
|
+
if !name_token.start_with?('"') && name_token.casecmp('ALL').zero?
|
|
443
|
+
@prepared_on.clear
|
|
444
|
+
@prepared_sql.clear
|
|
445
|
+
else
|
|
446
|
+
forget_prepared([statement_name_of(name_token)], nil, nil)
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# The name a statement prepared by a +PREPARE+ ends up with. Being an identifier, it is folded to
|
|
451
|
+
# lower case unless it was quoted, and that folded name is the one the +exec_prepared+ which runs
|
|
452
|
+
# it has to give as well, so it is the one to remember it under.
|
|
453
|
+
def statement_name_of(name_token)
|
|
454
|
+
return name_token.downcase unless name_token.start_with?('"')
|
|
455
|
+
|
|
456
|
+
name_token[1..-2].gsub('""', '"')
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def remember_async(_args, _result, sql)
|
|
460
|
+
@async_conn = current_conn
|
|
461
|
+
@async_sql = sql
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def forget_async(_args, _result, _sql)
|
|
465
|
+
@async_conn = nil
|
|
466
|
+
@async_sql = nil
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# get_result answers nil once the last result of a pending exchange has been read, and there is
|
|
470
|
+
# nothing left to be bound to.
|
|
471
|
+
def forget_async_when_drained(_args, result, _sql)
|
|
472
|
+
return unless result.nil?
|
|
473
|
+
|
|
474
|
+
@async_conn = nil
|
|
475
|
+
@async_sql = nil
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def forget_copy(_args, _result, _sql)
|
|
479
|
+
@copy_conn = nil
|
|
480
|
+
@copy_sql = nil
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def remember_large_object(_args, _result, _sql)
|
|
484
|
+
@lo_conn = current_conn
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def forget_large_object(_args, _result, _sql)
|
|
488
|
+
@lo_conn = nil
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def ensure_pg!
|
|
492
|
+
require 'pg'
|
|
493
|
+
rescue LoadError
|
|
494
|
+
raise LoadError, "WrapperPgConnection requires 'pg'. Add it to your Gemfile: gem 'pg'"
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def current_conn
|
|
498
|
+
@service_container.connection_service.current_connection
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def pm
|
|
502
|
+
@service_container.plugin_manager
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def driver_dialect
|
|
506
|
+
@service_container.dialect_service.driver_dialect
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def network_bound_methods
|
|
510
|
+
@network_bound_methods ||= driver_dialect.network_bound_methods
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def wrap_pg_result(result, sql = nil)
|
|
514
|
+
return result unless result.is_a?(PG::Result)
|
|
515
|
+
|
|
516
|
+
WrapperPgResult.new(result, @service_container, current_conn, sql)
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
class WrapperPgResult
|
|
521
|
+
include Enumerable
|
|
522
|
+
|
|
523
|
+
# @param sql [String, nil] the SQL that produced the result, kept so that plugins which
|
|
524
|
+
# inspect statements still see it when the rows are read
|
|
525
|
+
def initialize(result, service_container, connection, sql = nil)
|
|
526
|
+
@result = result
|
|
527
|
+
@service_container = service_container
|
|
528
|
+
@connection = connection
|
|
529
|
+
@sql = sql
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def each(&)
|
|
533
|
+
pm.execute(RubyMethod::RESULT_EACH, current_conn, ->(&blk) { @result.each(&blk) },
|
|
534
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields }, &)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def each_row(&)
|
|
538
|
+
pm.execute(RubyMethod::RESULT_EACH_ROW, current_conn, ->(&blk) { @result.each_row(&blk) },
|
|
539
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields }, &)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def to_a
|
|
543
|
+
pm.execute(RubyMethod::RESULT_TO_A, current_conn, -> { @result.to_a },
|
|
544
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def [](index)
|
|
548
|
+
pm.execute(RubyMethod::RESULT_BRACKET, current_conn, ->(*a) { @result[*a] }, index,
|
|
549
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def values
|
|
553
|
+
pm.execute(RubyMethod::RESULT_VALUES, current_conn, -> { @result.values },
|
|
554
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def column_values(index)
|
|
558
|
+
pm.execute(RubyMethod::RESULT_COLUMN_VALUES, current_conn, ->(*a) { @result.column_values(*a) }, index,
|
|
559
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def field_values(field_name)
|
|
563
|
+
pm.execute(RubyMethod::RESULT_FIELD_VALUES, current_conn, ->(*a) { @result.field_values(*a) }, field_name,
|
|
564
|
+
bounded_conn: @connection, sql: @sql)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def tuple(index)
|
|
568
|
+
pm.execute(RubyMethod::RESULT_TUPLE, current_conn, ->(*a) { @result.tuple(*a) }, index,
|
|
569
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def tuple_values(index)
|
|
573
|
+
pm.execute(RubyMethod::RESULT_TUPLE_VALUES, current_conn, ->(*a) { @result.tuple_values(*a) }, index,
|
|
574
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def getvalue(row, column)
|
|
578
|
+
pm.execute(RubyMethod::RESULT_GETVALUE, current_conn, ->(*a) { @result.getvalue(*a) }, row, column,
|
|
579
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields })
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# The single-row-mode iterators, which read rows off the wire one at a time rather than from a
|
|
583
|
+
# buffered result; they hand out the same row shapes as +each+, +each_row+ and +tuple+.
|
|
584
|
+
def stream_each(&)
|
|
585
|
+
pm.execute(RubyMethod::RESULT_STREAM_EACH, current_conn, ->(&blk) { @result.stream_each(&blk) },
|
|
586
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields }, &)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def stream_each_row(&)
|
|
590
|
+
pm.execute(RubyMethod::RESULT_STREAM_EACH_ROW, current_conn, ->(&blk) { @result.stream_each_row(&blk) },
|
|
591
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields }, &)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def stream_each_tuple(&)
|
|
595
|
+
pm.execute(RubyMethod::RESULT_STREAM_EACH_TUPLE, current_conn, ->(&blk) { @result.stream_each_tuple(&blk) },
|
|
596
|
+
bounded_conn: @connection, sql: @sql, field_names: -> { @result.fields }, &)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Delegate non-network methods directly
|
|
600
|
+
def fields
|
|
601
|
+
@result.fields
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def ntuples
|
|
605
|
+
@result.ntuples
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def nfields
|
|
609
|
+
@result.nfields
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def cmd_tuples
|
|
613
|
+
@result.cmd_tuples
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def cmd_status
|
|
617
|
+
@result.cmd_status
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def result_status
|
|
621
|
+
@result.result_status
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def clear
|
|
625
|
+
@result.clear
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
alias num_tuples ntuples
|
|
629
|
+
alias count ntuples
|
|
630
|
+
alias size ntuples
|
|
631
|
+
|
|
632
|
+
def method_missing(method_name, *, &)
|
|
633
|
+
@result.send(method_name, *, &)
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def respond_to_missing?(method, include_private = false)
|
|
637
|
+
@result.respond_to?(method, include_private) || super
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def inspect
|
|
641
|
+
format('#<%<class>s:0x%<addr>016x>', class: self.class.name, addr: object_id << 1)
|
|
642
|
+
end
|
|
643
|
+
alias to_s inspect
|
|
644
|
+
|
|
645
|
+
def pretty_print(pp)
|
|
646
|
+
pp.text(inspect)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
private
|
|
650
|
+
|
|
651
|
+
def current_conn
|
|
652
|
+
@service_container.connection_service.current_connection
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def pm
|
|
656
|
+
@service_container.plugin_manager
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|