activerecord-yugabytedb-adapter 7.0.4.1 → 7.1.3.4
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 +4 -4
- data/Gemfile.lock +21 -9
- data/README.md +8 -2
- data/activerecord-yugabytedb-adapter.gemspec +2 -2
- data/lib/active_record/connection_adapters/yugabytedb/column.rb +16 -3
- data/lib/active_record/connection_adapters/yugabytedb/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/yugabytedb/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/yugabytedb/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/yugabytedb/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/yugabytedb/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/yugabytedb/oid/timestamp_with_time_zone.rb +2 -2
- data/lib/active_record/connection_adapters/yugabytedb/quoting.rb +42 -9
- data/lib/active_record/connection_adapters/yugabytedb/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/yugabytedb/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/yugabytedb/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/yugabytedb/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/yugabytedb/schema_statements.rb +362 -60
- data/lib/active_record/connection_adapters/yugabytedb/utils.rb +11 -12
- data/lib/active_record/connection_adapters/yugabytedb_adapter.rb +625 -464
- data/lib/arel/visitors/yugabytedb.rb +12 -1
- data/lib/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2cc03a2de664cf174a7f51d10d8a7d466a1746a2a664e65c84a063b1690c176
|
4
|
+
data.tar.gz: 81ca6a9131426b34378264ffac4495387b1351c5c23cf476ab32334f07676696
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ffc610ae76a0d049e8bd35eeea4b5c7ef557c5774768be0b7d28c78da446893813fd2342b339d91b11363ccd3fbfcefda0514b7ed1976d87088ec85b12a82e8
|
7
|
+
data.tar.gz: d2a122f7b7af63ab3434ae6dfbfa7d05993a6f3aeb7fc6ae6149196857f7125aa3b712babd67ce18b963076ea52ae9362c93fd8fa2f7a7a083c1dc66e8f0f294
|
data/Gemfile.lock
CHANGED
@@ -2,29 +2,40 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
activerecord-yugabytedb-adapter (7.0.4.1)
|
5
|
-
activerecord (= 7.
|
6
|
-
|
5
|
+
activerecord (= 7.1.3.4)
|
6
|
+
yugabytedb-ysql (~> 0.3)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (7.
|
12
|
-
activesupport (= 7.
|
13
|
-
activerecord (7.
|
14
|
-
activemodel (= 7.
|
15
|
-
activesupport (= 7.
|
16
|
-
|
11
|
+
activemodel (7.1.3.4)
|
12
|
+
activesupport (= 7.1.3.4)
|
13
|
+
activerecord (7.1.3.4)
|
14
|
+
activemodel (= 7.1.3.4)
|
15
|
+
activesupport (= 7.1.3.4)
|
16
|
+
timeout (>= 0.4.0)
|
17
|
+
activesupport (7.1.3.4)
|
18
|
+
base64
|
19
|
+
bigdecimal
|
17
20
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
|
+
connection_pool (>= 2.2.5)
|
22
|
+
drb
|
18
23
|
i18n (>= 1.6, < 2)
|
19
24
|
minitest (>= 5.1)
|
25
|
+
mutex_m
|
20
26
|
tzinfo (~> 2.0)
|
21
27
|
ast (2.4.2)
|
28
|
+
base64 (0.2.0)
|
29
|
+
bigdecimal (3.1.8)
|
22
30
|
concurrent-ruby (1.3.3)
|
31
|
+
connection_pool (2.4.1)
|
32
|
+
drb (2.2.1)
|
23
33
|
i18n (1.14.5)
|
24
34
|
concurrent-ruby (~> 1.0)
|
25
35
|
json (2.7.2)
|
26
36
|
language_server-protocol (3.17.0.3)
|
27
37
|
minitest (5.24.0)
|
38
|
+
mutex_m (0.2.0)
|
28
39
|
parallel (1.25.1)
|
29
40
|
parser (3.3.3.0)
|
30
41
|
ast (~> 2.4.1)
|
@@ -50,10 +61,11 @@ GEM
|
|
50
61
|
parser (>= 3.3.1.0)
|
51
62
|
ruby-progressbar (1.13.0)
|
52
63
|
strscan (3.1.0)
|
64
|
+
timeout (0.4.1)
|
53
65
|
tzinfo (2.0.6)
|
54
66
|
concurrent-ruby (~> 1.0)
|
55
67
|
unicode-display_width (2.5.0)
|
56
|
-
|
68
|
+
yugabytedb-ysql (0.3)
|
57
69
|
|
58
70
|
PLATFORMS
|
59
71
|
x86_64-linux
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActiveRecord YugabyteDB Adapter
|
2
2
|
|
3
|
-
This is an adapter for YugabyteDB for ActiveRecord. The adapter uses YugabyteDB's Ruby smart driver underneath. The adapter code is derived from the [PostgreSQL adapter v7.
|
3
|
+
This is an adapter for YugabyteDB for ActiveRecord. The adapter uses YugabyteDB's Ruby smart driver underneath. The adapter code is derived from the [PostgreSQL adapter v7.1.3.4 for ActiveRecord in the Rails repository](https://github.com/rails/rails/tree/v7.1.3.4/activerecord/lib/active_record/connection_adapters).
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -14,7 +14,13 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
14
14
|
|
15
15
|
## Usage
|
16
16
|
|
17
|
-
|
17
|
+
Include the gem in your `Gemfile`
|
18
|
+
```ruby
|
19
|
+
gem 'rails', '7.1.3.4'
|
20
|
+
gem 'activerecord-yugabytedb-adapter', '7.1.3.4'
|
21
|
+
```
|
22
|
+
|
23
|
+
Check this [simple example](https://github.com/YugabyteDB-Samples/orm-examples/tree/ruby-smart-driver/ruby/ror) for usage of this adapter.
|
18
24
|
|
19
25
|
## Development
|
20
26
|
|
@@ -30,8 +30,8 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
# Uncomment to register a new dependency of your gem
|
32
32
|
# spec.add_dependency "example-gem", "~> 1.0"
|
33
|
-
spec.add_dependency "activerecord", "7.
|
34
|
-
spec.add_dependency "
|
33
|
+
spec.add_dependency "activerecord", "7.1.3.4"
|
34
|
+
spec.add_dependency "yugabytedb-ysql", "~> 0.3"
|
35
35
|
|
36
36
|
|
37
37
|
# For more information and examples about making a new gem, check out our
|
@@ -1,23 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/object/blank"
|
4
|
-
|
5
3
|
module ActiveRecord
|
6
4
|
module ConnectionAdapters
|
7
5
|
module YugabyteDB
|
8
6
|
class Column < ConnectionAdapters::Column # :nodoc:
|
9
7
|
delegate :oid, :fmod, to: :sql_type_metadata
|
10
8
|
|
11
|
-
def initialize(*, serial: nil, generated: nil, **)
|
9
|
+
def initialize(*, serial: nil, identity: nil, generated: nil, **)
|
12
10
|
super
|
13
11
|
@serial = serial
|
12
|
+
@identity = identity
|
14
13
|
@generated = generated
|
15
14
|
end
|
16
15
|
|
16
|
+
def identity?
|
17
|
+
@identity
|
18
|
+
end
|
19
|
+
|
17
20
|
def serial?
|
18
21
|
@serial
|
19
22
|
end
|
20
23
|
|
24
|
+
def auto_incremented_by_db?
|
25
|
+
serial? || identity?
|
26
|
+
end
|
27
|
+
|
21
28
|
def virtual?
|
22
29
|
# We assume every generated column is virtual, no matter the concrete type
|
23
30
|
@generated.present?
|
@@ -42,17 +49,22 @@ module ActiveRecord
|
|
42
49
|
|
43
50
|
def init_with(coder)
|
44
51
|
@serial = coder["serial"]
|
52
|
+
@identity = coder["identity"]
|
53
|
+
@generated = coder["generated"]
|
45
54
|
super
|
46
55
|
end
|
47
56
|
|
48
57
|
def encode_with(coder)
|
49
58
|
coder["serial"] = @serial
|
59
|
+
coder["identity"] = @identity
|
60
|
+
coder["generated"] = @generated
|
50
61
|
super
|
51
62
|
end
|
52
63
|
|
53
64
|
def ==(other)
|
54
65
|
other.is_a?(Column) &&
|
55
66
|
super &&
|
67
|
+
identity? == other.identity? &&
|
56
68
|
serial? == other.serial?
|
57
69
|
end
|
58
70
|
alias :eql? :==
|
@@ -60,6 +72,7 @@ module ActiveRecord
|
|
60
72
|
def hash
|
61
73
|
Column.hash ^
|
62
74
|
super.hash ^
|
75
|
+
identity?.hash ^
|
63
76
|
serial?.hash
|
64
77
|
end
|
65
78
|
end
|
@@ -4,19 +4,21 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module YugabyteDB
|
6
6
|
module DatabaseStatements
|
7
|
-
def explain(arel, binds = [])
|
8
|
-
sql
|
9
|
-
|
7
|
+
def explain(arel, binds = [], options = [])
|
8
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
9
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
10
|
+
YugabyteDB::ExplainPrettyPrinter.new.pp(result)
|
10
11
|
end
|
11
12
|
|
12
13
|
# Queries the database and returns the results in an Array-like object
|
13
14
|
def query(sql, name = nil) # :nodoc:
|
14
|
-
materialize_transactions
|
15
15
|
mark_transaction_written_if_write(sql)
|
16
16
|
|
17
17
|
log(sql, name) do
|
18
|
-
|
19
|
-
|
18
|
+
with_raw_connection do |conn|
|
19
|
+
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
20
|
+
verified!
|
21
|
+
result
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -34,34 +36,38 @@ module ActiveRecord
|
|
34
36
|
|
35
37
|
# Executes an SQL statement, returning a PG::Result object on success
|
36
38
|
# or raising a PG::Error exception otherwise.
|
39
|
+
#
|
40
|
+
# Setting +allow_retry+ to true causes the db to reconnect and retry
|
41
|
+
# executing the SQL statement in case of a connection-related exception.
|
42
|
+
# This option should only be enabled for known idempotent queries.
|
43
|
+
#
|
37
44
|
# Note: the PG::Result object is manually memory managed; if you don't
|
38
45
|
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
39
|
-
def execute(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
mark_transaction_written_if_write(sql)
|
46
|
+
def execute(...) # :nodoc:
|
47
|
+
super
|
48
|
+
ensure
|
49
|
+
@notice_receiver_sql_warnings = []
|
50
|
+
end
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
52
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
53
|
+
log(sql, name, async: async) do
|
54
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
55
|
+
result = conn.async_exec(sql)
|
56
|
+
verified!
|
57
|
+
handle_warnings(result)
|
58
|
+
result
|
49
59
|
end
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
53
|
-
def
|
54
|
-
execute_and_clear(sql, name, binds, prepare: prepare, async: async) do |result|
|
63
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
64
|
+
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
55
65
|
types = {}
|
56
66
|
fields = result.fields
|
57
67
|
fields.each_with_index do |fname, i|
|
58
68
|
ftype = result.ftype i
|
59
69
|
fmod = result.fmod i
|
60
|
-
|
61
|
-
when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
|
62
|
-
# skip if a column has already been type casted by pg decoders
|
63
|
-
else types[fname] = type
|
64
|
-
end
|
70
|
+
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
65
71
|
end
|
66
72
|
build_result(columns: fields, rows: result.values, column_types: types)
|
67
73
|
end
|
@@ -72,26 +78,11 @@ module ActiveRecord
|
|
72
78
|
end
|
73
79
|
alias :exec_update :exec_delete
|
74
80
|
|
75
|
-
def
|
76
|
-
if pk.nil?
|
77
|
-
# Extract the table from the insert sql. Yuck.
|
78
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
79
|
-
pk = primary_key(table_ref) if table_ref
|
80
|
-
end
|
81
|
-
|
82
|
-
if pk = suppress_composite_primary_key(pk)
|
83
|
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
84
|
-
end
|
85
|
-
|
86
|
-
super
|
87
|
-
end
|
88
|
-
private :sql_for_insert
|
89
|
-
|
90
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
|
81
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
91
82
|
if use_insert_returning? || pk == false
|
92
83
|
super
|
93
84
|
else
|
94
|
-
result =
|
85
|
+
result = internal_exec_query(sql, name, binds)
|
95
86
|
unless sequence_name
|
96
87
|
table_ref = extract_table_ref_from_insert_sql(sql)
|
97
88
|
if table_ref
|
@@ -107,22 +98,27 @@ module ActiveRecord
|
|
107
98
|
|
108
99
|
# Begins a transaction.
|
109
100
|
def begin_db_transaction # :nodoc:
|
110
|
-
|
101
|
+
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
111
102
|
end
|
112
103
|
|
113
104
|
def begin_isolated_db_transaction(isolation) # :nodoc:
|
114
|
-
|
115
|
-
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
105
|
+
internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
116
106
|
end
|
117
107
|
|
118
108
|
# Commits a transaction.
|
119
109
|
def commit_db_transaction # :nodoc:
|
120
|
-
|
110
|
+
internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
121
111
|
end
|
122
112
|
|
123
113
|
# Aborts a transaction.
|
124
114
|
def exec_rollback_db_transaction # :nodoc:
|
125
|
-
|
115
|
+
cancel_any_running_query
|
116
|
+
internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def exec_restart_db_transaction # :nodoc:
|
120
|
+
cancel_any_running_query
|
121
|
+
internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
126
122
|
end
|
127
123
|
|
128
124
|
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
@@ -133,7 +129,24 @@ module ActiveRecord
|
|
133
129
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
134
130
|
end
|
135
131
|
|
132
|
+
def build_explain_clause(options = [])
|
133
|
+
return "EXPLAIN" if options.empty?
|
134
|
+
|
135
|
+
"EXPLAIN (#{options.join(", ").upcase})"
|
136
|
+
end
|
137
|
+
|
136
138
|
private
|
139
|
+
IDLE_TRANSACTION_STATUSES = [YSQL::PQTRANS_IDLE, YSQL::PQTRANS_INTRANS, YSQL::PQTRANS_INERROR]
|
140
|
+
private_constant :IDLE_TRANSACTION_STATUSES
|
141
|
+
|
142
|
+
def cancel_any_running_query
|
143
|
+
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
144
|
+
|
145
|
+
@raw_connection.cancel
|
146
|
+
@raw_connection.block
|
147
|
+
rescue YSQL::Error
|
148
|
+
end
|
149
|
+
|
137
150
|
def execute_batch(statements, name = nil)
|
138
151
|
execute(combine_multi_statements(statements))
|
139
152
|
end
|
@@ -144,12 +157,29 @@ module ActiveRecord
|
|
144
157
|
|
145
158
|
# Returns the current ID of a table's sequence.
|
146
159
|
def last_insert_id_result(sequence_name)
|
147
|
-
|
160
|
+
internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
161
|
+
end
|
162
|
+
|
163
|
+
def returning_column_values(result)
|
164
|
+
result.rows.first
|
148
165
|
end
|
149
166
|
|
150
167
|
def suppress_composite_primary_key(pk)
|
151
168
|
pk unless pk.is_a?(Array)
|
152
169
|
end
|
170
|
+
|
171
|
+
def handle_warnings(sql)
|
172
|
+
@notice_receiver_sql_warnings.each do |warning|
|
173
|
+
next if warning_ignored?(warning)
|
174
|
+
|
175
|
+
warning.sql = sql
|
176
|
+
ActiveRecord.db_warnings_action.call(warning)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def warning_ignored?(warning)
|
181
|
+
["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
|
182
|
+
end
|
153
183
|
end
|
154
184
|
end
|
155
185
|
end
|
@@ -16,8 +16,8 @@ module ActiveRecord
|
|
16
16
|
@subtype = subtype
|
17
17
|
@delimiter = delimiter
|
18
18
|
|
19
|
-
@pg_encoder =
|
20
|
-
@pg_decoder =
|
19
|
+
@pg_encoder = YSQL::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
|
20
|
+
@pg_decoder = YSQL::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
|
21
21
|
end
|
22
22
|
|
23
23
|
def deserialize(value)
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def map(value, &block)
|
68
|
-
value.map(&block)
|
68
|
+
value.map { |v| subtype.map(v, &block) }
|
69
69
|
end
|
70
70
|
|
71
71
|
def changed_in_place?(raw_old_value, new_value)
|
@@ -27,9 +27,10 @@ module ActiveRecord
|
|
27
27
|
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
28
|
case value
|
29
29
|
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
|
-
value.
|
30
|
+
value.delete!("^-0-9.")
|
31
31
|
when /^-?\D*+[\d.]+,\d{2}$/ # (2)
|
32
|
-
value.
|
32
|
+
value.delete!("^-0-9,")
|
33
|
+
value.tr!(",", ".")
|
33
34
|
end
|
34
35
|
|
35
36
|
super(value)
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def cast_value(value)
|
21
|
-
return if
|
21
|
+
return if ["empty", ""].include? value
|
22
22
|
return value unless value.is_a?(::String)
|
23
23
|
|
24
24
|
extracted = extract_bounds(value)
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
if !infinity?(from) && extracted[:exclude_start]
|
29
29
|
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
30
30
|
end
|
31
|
-
::Range.new(from, to, extracted[:exclude_end])
|
31
|
+
::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end])
|
32
32
|
end
|
33
33
|
|
34
34
|
def serialize(value)
|
@@ -76,6 +76,15 @@ module ActiveRecord
|
|
76
76
|
}
|
77
77
|
end
|
78
78
|
|
79
|
+
INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
|
80
|
+
|
81
|
+
def sanitize_bounds(from, to)
|
82
|
+
[
|
83
|
+
(from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from,
|
84
|
+
(to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
79
88
|
# When formatting the bound values of range types, PostgreSQL quotes
|
80
89
|
# the bound value using double-quotes in certain conditions. Within
|
81
90
|
# a double-quoted string, literal " and \ characters are themselves
|
@@ -13,9 +13,9 @@ module ActiveRecord
|
|
13
13
|
return if value.blank?
|
14
14
|
|
15
15
|
time = super
|
16
|
-
return time if time.is_a?(ActiveSupport::TimeWithZone)
|
16
|
+
return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
|
17
17
|
|
18
|
-
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to
|
18
|
+
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to PostgreSQL in UTC.
|
19
19
|
# We prefer times always in UTC, so here we convert back.
|
20
20
|
if is_utc?
|
21
21
|
time.getutc
|
@@ -4,19 +4,48 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module YugabyteDB
|
6
6
|
module Quoting
|
7
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
8
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
|
+
|
10
|
+
class IntegerOutOf64BitRange < StandardError
|
11
|
+
def initialize(msg)
|
12
|
+
super(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
# Escapes binary strings for bytea input to the database.
|
8
17
|
def escape_bytea(value)
|
9
|
-
|
18
|
+
valid_raw_connection.escape_bytea(value) if value
|
10
19
|
end
|
11
20
|
|
12
21
|
# Unescapes bytea output from a database to the binary string it represents.
|
13
22
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
14
23
|
# on escaped binary output from database drive.
|
15
24
|
def unescape_bytea(value)
|
16
|
-
|
25
|
+
valid_raw_connection.unescape_bytea(value) if value
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_int_in_range(value)
|
29
|
+
if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
|
30
|
+
exception = <<~ERROR
|
31
|
+
Provided value outside of the range of a signed 64bit integer.
|
32
|
+
|
33
|
+
PostgreSQL will treat the column type in question as a numeric.
|
34
|
+
This may result in a slow sequential scan due to a comparison
|
35
|
+
being performed between an integer or bigint value and a numeric value.
|
36
|
+
|
37
|
+
To allow for this potentially unwanted behavior, set
|
38
|
+
ActiveRecord.raise_int_wider_than_64bit to false.
|
39
|
+
ERROR
|
40
|
+
raise IntegerOutOf64BitRange.new exception
|
41
|
+
end
|
17
42
|
end
|
18
43
|
|
19
44
|
def quote(value) # :nodoc:
|
45
|
+
if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
|
46
|
+
check_int_in_range(value)
|
47
|
+
end
|
48
|
+
|
20
49
|
case value
|
21
50
|
when OID::Xml::Data
|
22
51
|
"xml '#{quote_string(value.to_s)}'"
|
@@ -43,7 +72,9 @@ module ActiveRecord
|
|
43
72
|
|
44
73
|
# Quotes strings for use in SQL input.
|
45
74
|
def quote_string(s) # :nodoc:
|
46
|
-
|
75
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
76
|
+
connection.escape(s)
|
77
|
+
end
|
47
78
|
end
|
48
79
|
|
49
80
|
# Checks the following cases:
|
@@ -55,12 +86,12 @@ module ActiveRecord
|
|
55
86
|
# - "schema.name".table_name
|
56
87
|
# - "schema.name"."table.name"
|
57
88
|
def quote_table_name(name) # :nodoc:
|
58
|
-
|
89
|
+
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
59
90
|
end
|
60
91
|
|
61
92
|
# Quotes schema names for use in SQL queries.
|
62
93
|
def quote_schema_name(name)
|
63
|
-
|
94
|
+
YSQL::Connection.quote_ident(name)
|
64
95
|
end
|
65
96
|
|
66
97
|
def quote_table_name_for_assignment(table, attr)
|
@@ -69,7 +100,7 @@ module ActiveRecord
|
|
69
100
|
|
70
101
|
# Quotes column names for use in SQL queries.
|
71
102
|
def quote_column_name(name) # :nodoc:
|
72
|
-
|
103
|
+
QUOTED_COLUMN_NAMES[name] ||= YSQL::Connection.quote_ident(super).freeze
|
73
104
|
end
|
74
105
|
|
75
106
|
# Quote date/time values for use in SQL input.
|
@@ -89,7 +120,7 @@ module ActiveRecord
|
|
89
120
|
def quote_default_expression(value, column) # :nodoc:
|
90
121
|
if value.is_a?(Proc)
|
91
122
|
value.call
|
92
|
-
elsif column.type == :uuid && value.is_a?(String) &&
|
123
|
+
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
93
124
|
value # Does not quote function default values for UUID columns
|
94
125
|
elsif column.respond_to?(:array?)
|
95
126
|
type = lookup_cast_type_from_column(column)
|
@@ -118,6 +149,7 @@ module ActiveRecord
|
|
118
149
|
end
|
119
150
|
|
120
151
|
def lookup_cast_type_from_column(column) # :nodoc:
|
152
|
+
verify! if type_map.nil?
|
121
153
|
type_map.lookup(column.oid, column.fmod, column.sql_type)
|
122
154
|
end
|
123
155
|
|
@@ -134,7 +166,7 @@ module ActiveRecord
|
|
134
166
|
(
|
135
167
|
(?:
|
136
168
|
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
137
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?
|
169
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
138
170
|
)
|
139
171
|
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
140
172
|
)
|
@@ -147,8 +179,9 @@ module ActiveRecord
|
|
147
179
|
(
|
148
180
|
(?:
|
149
181
|
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
150
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?
|
182
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
151
183
|
)
|
184
|
+
(?:\s+COLLATE\s+"\w+")?
|
152
185
|
(?:\s+ASC|\s+DESC)?
|
153
186
|
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
154
187
|
)
|
@@ -38,7 +38,7 @@ Rails needs superuser privileges to disable referential integrity.
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
42
42
|
sql = <<~SQL
|
43
43
|
do $$
|
44
44
|
declare r record;
|
@@ -61,14 +61,8 @@ Rails needs superuser privileges to disable referential integrity.
|
|
61
61
|
$$;
|
62
62
|
SQL
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
execute(sql)
|
67
|
-
end
|
68
|
-
|
69
|
-
true
|
70
|
-
rescue ActiveRecord::StatementInvalid
|
71
|
-
false
|
64
|
+
transaction(requires_new: true) do
|
65
|
+
execute(sql)
|
72
66
|
end
|
73
67
|
end
|
74
68
|
end
|