activerecord-yugabytedb-adapter 7.0.4.1 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|