activerecord-yugabytedb-adapter 7.0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +67 -0
- data/README.md +31 -0
- data/Rakefile +8 -0
- data/activerecord-yugabytedb-adapter.gemspec +39 -0
- data/lib/active_record/connection_adapters/yugabytedb/column.rb +69 -0
- data/lib/active_record/connection_adapters/yugabytedb/database_statements.rb +156 -0
- data/lib/active_record/connection_adapters/yugabytedb/database_tasks.rb +19 -0
- data/lib/active_record/connection_adapters/yugabytedb/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/point.rb +64 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/range.rb +115 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/type_map_initializer.rb +125 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/uuid.rb +35 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/yugabytedb/oid.rb +38 -0
- data/lib/active_record/connection_adapters/yugabytedb/quoting.rb +205 -0
- data/lib/active_record/connection_adapters/yugabytedb/referential_integrity.rb +77 -0
- data/lib/active_record/connection_adapters/yugabytedb/schema_creation.rb +100 -0
- data/lib/active_record/connection_adapters/yugabytedb/schema_definitions.rb +243 -0
- data/lib/active_record/connection_adapters/yugabytedb/schema_dumper.rb +74 -0
- data/lib/active_record/connection_adapters/yugabytedb/schema_statements.rb +812 -0
- data/lib/active_record/connection_adapters/yugabytedb/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/yugabytedb/utils.rb +80 -0
- data/lib/active_record/connection_adapters/yugabytedb_adapter.rb +1069 -0
- data/lib/activerecord-yugabytedb-adapter.rb +11 -0
- data/lib/arel/visitors/yugabytedb.rb +99 -0
- data/lib/version.rb +5 -0
- data/sig/activerecord-yugabytedb-adapter.rbs +4 -0
- metadata +124 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Range < Type::Value # :nodoc:
|
8
|
+
attr_reader :subtype, :type
|
9
|
+
delegate :user_input_in_time_zone, to: :subtype
|
10
|
+
|
11
|
+
def initialize(subtype, type = :range)
|
12
|
+
@subtype = subtype
|
13
|
+
@type = type
|
14
|
+
end
|
15
|
+
|
16
|
+
def type_cast_for_schema(value)
|
17
|
+
value.inspect.gsub("Infinity", "::Float::INFINITY")
|
18
|
+
end
|
19
|
+
|
20
|
+
def cast_value(value)
|
21
|
+
return if value == "empty"
|
22
|
+
return value unless value.is_a?(::String)
|
23
|
+
|
24
|
+
extracted = extract_bounds(value)
|
25
|
+
from = type_cast_single extracted[:from]
|
26
|
+
to = type_cast_single extracted[:to]
|
27
|
+
|
28
|
+
if !infinity?(from) && extracted[:exclude_start]
|
29
|
+
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
30
|
+
end
|
31
|
+
::Range.new(from, to, extracted[:exclude_end])
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialize(value)
|
35
|
+
if value.is_a?(::Range)
|
36
|
+
from = type_cast_single_for_database(value.begin)
|
37
|
+
to = type_cast_single_for_database(value.end)
|
38
|
+
::Range.new(from, to, value.exclude_end?)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
other.is_a?(Range) &&
|
46
|
+
other.subtype == subtype &&
|
47
|
+
other.type == type
|
48
|
+
end
|
49
|
+
|
50
|
+
def map(value) # :nodoc:
|
51
|
+
new_begin = yield(value.begin)
|
52
|
+
new_end = yield(value.end)
|
53
|
+
::Range.new(new_begin, new_end, value.exclude_end?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def force_equality?(value)
|
57
|
+
value.is_a?(::Range)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def type_cast_single(value)
|
62
|
+
infinity?(value) ? value : @subtype.deserialize(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def type_cast_single_for_database(value)
|
66
|
+
infinity?(value) ? value : @subtype.serialize(@subtype.cast(value))
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_bounds(value)
|
70
|
+
from, to = value[1..-2].split(",", 2)
|
71
|
+
{
|
72
|
+
from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
|
73
|
+
to: (to == "" || to == "infinity") ? infinity : unquote(to),
|
74
|
+
exclude_start: value.start_with?("("),
|
75
|
+
exclude_end: value.end_with?(")")
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# When formatting the bound values of range types, PostgreSQL quotes
|
80
|
+
# the bound value using double-quotes in certain conditions. Within
|
81
|
+
# a double-quoted string, literal " and \ characters are themselves
|
82
|
+
# escaped. In input, PostgreSQL accepts multiple escape styles for "
|
83
|
+
# (either \" or "") but in output always uses "".
|
84
|
+
# See:
|
85
|
+
# * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
|
86
|
+
# * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
|
87
|
+
def unquote(value)
|
88
|
+
if value.start_with?('"') && value.end_with?('"')
|
89
|
+
unquoted_value = value[1..-2]
|
90
|
+
unquoted_value.gsub!('""', '"')
|
91
|
+
unquoted_value.gsub!("\\\\", "\\")
|
92
|
+
unquoted_value
|
93
|
+
else
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def infinity(negative: false)
|
99
|
+
if subtype.respond_to?(:infinity)
|
100
|
+
subtype.infinity(negative: negative)
|
101
|
+
elsif negative
|
102
|
+
-::Float::INFINITY
|
103
|
+
else
|
104
|
+
::Float::INFINITY
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def infinity?(value)
|
109
|
+
value.respond_to?(:infinite?) && value.infinite?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class SpecializedString < Type::String # :nodoc:
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
def initialize(type, **options)
|
11
|
+
@type = type
|
12
|
+
super(**options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Timestamp < DateTime # :nodoc:
|
8
|
+
def type
|
9
|
+
real_type_unless_aliased(:timestamp)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class TimestampWithTimeZone < DateTime # :nodoc:
|
8
|
+
def type
|
9
|
+
real_type_unless_aliased(:timestamptz)
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast_value(value)
|
13
|
+
return if value.blank?
|
14
|
+
|
15
|
+
time = super
|
16
|
+
return time if time.is_a?(ActiveSupport::TimeWithZone)
|
17
|
+
|
18
|
+
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
|
19
|
+
# We prefer times always in UTC, so here we convert back.
|
20
|
+
if is_utc?
|
21
|
+
time.getutc
|
22
|
+
else
|
23
|
+
time.getlocal
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module YugabyteDB
|
8
|
+
module OID # :nodoc:
|
9
|
+
# This class uses the data from PostgreSQL pg_type table to build
|
10
|
+
# the OID -> Type mapping.
|
11
|
+
# - OID is an integer representing the type.
|
12
|
+
# - Type is an OID::Type object.
|
13
|
+
# This class has side effects on the +store+ passed during initialization.
|
14
|
+
class TypeMapInitializer # :nodoc:
|
15
|
+
def initialize(store)
|
16
|
+
@store = store
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(records)
|
20
|
+
nodes = records.reject { |row| @store.key? row["oid"].to_i }
|
21
|
+
mapped = nodes.extract! { |row| @store.key? row["typname"] }
|
22
|
+
ranges = nodes.extract! { |row| row["typtype"] == "r" }
|
23
|
+
enums = nodes.extract! { |row| row["typtype"] == "e" }
|
24
|
+
domains = nodes.extract! { |row| row["typtype"] == "d" }
|
25
|
+
arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
|
26
|
+
composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
|
27
|
+
|
28
|
+
mapped.each { |row| register_mapped_type(row) }
|
29
|
+
enums.each { |row| register_enum_type(row) }
|
30
|
+
domains.each { |row| register_domain_type(row) }
|
31
|
+
arrays.each { |row| register_array_type(row) }
|
32
|
+
ranges.each { |row| register_range_type(row) }
|
33
|
+
composites.each { |row| register_composite_type(row) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_conditions_for_known_type_names
|
37
|
+
known_type_names = @store.keys.map { |n| "'#{n}'" }
|
38
|
+
<<~SQL % known_type_names.join(", ")
|
39
|
+
WHERE
|
40
|
+
t.typname IN (%s)
|
41
|
+
SQL
|
42
|
+
end
|
43
|
+
|
44
|
+
def query_conditions_for_known_type_types
|
45
|
+
known_type_types = %w('r' 'e' 'd')
|
46
|
+
<<~SQL % known_type_types.join(", ")
|
47
|
+
WHERE
|
48
|
+
t.typtype IN (%s)
|
49
|
+
SQL
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_conditions_for_array_types
|
53
|
+
known_type_oids = @store.keys.reject { |k| k.is_a?(String) }
|
54
|
+
<<~SQL % [known_type_oids.join(", ")]
|
55
|
+
WHERE
|
56
|
+
t.typelem IN (%s)
|
57
|
+
SQL
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def register_mapped_type(row)
|
62
|
+
alias_type row["oid"], row["typname"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def register_enum_type(row)
|
66
|
+
register row["oid"], OID::Enum.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def register_array_type(row)
|
70
|
+
register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
|
71
|
+
OID::Array.new(subtype, row["typdelim"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def register_range_type(row)
|
76
|
+
register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype|
|
77
|
+
OID::Range.new(subtype, row["typname"].to_sym)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def register_domain_type(row)
|
82
|
+
if base_type = @store.lookup(row["typbasetype"].to_i)
|
83
|
+
register row["oid"], base_type
|
84
|
+
else
|
85
|
+
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def register_composite_type(row)
|
90
|
+
if subtype = @store.lookup(row["typelem"].to_i)
|
91
|
+
register row["oid"], OID::Vector.new(row["typdelim"], subtype)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def register(oid, oid_type = nil, &block)
|
96
|
+
oid = assert_valid_registration(oid, oid_type || block)
|
97
|
+
if block_given?
|
98
|
+
@store.register_type(oid, &block)
|
99
|
+
else
|
100
|
+
@store.register_type(oid, oid_type)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def alias_type(oid, target)
|
105
|
+
oid = assert_valid_registration(oid, target)
|
106
|
+
@store.alias_type(oid, target)
|
107
|
+
end
|
108
|
+
|
109
|
+
def register_with_subtype(oid, target_oid)
|
110
|
+
if @store.key?(target_oid)
|
111
|
+
register(oid) do |_, *args|
|
112
|
+
yield @store.lookup(target_oid, *args)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_valid_registration(oid, oid_type)
|
118
|
+
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
119
|
+
oid.to_i
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Uuid < Type::Value # :nodoc:
|
8
|
+
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
|
9
|
+
|
10
|
+
alias :serialize :deserialize
|
11
|
+
|
12
|
+
def type
|
13
|
+
:uuid
|
14
|
+
end
|
15
|
+
|
16
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
17
|
+
old_value.class != new_value.class ||
|
18
|
+
new_value && old_value.casecmp(new_value) != 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def changed_in_place?(raw_old_value, new_value)
|
22
|
+
raw_old_value.class != new_value.class ||
|
23
|
+
new_value && raw_old_value.casecmp(new_value) != 0
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def cast_value(value)
|
28
|
+
casted = value.to_s
|
29
|
+
casted if casted.match?(ACCEPTABLE_UUID)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Vector < Type::Value # :nodoc:
|
8
|
+
attr_reader :delim, :subtype
|
9
|
+
|
10
|
+
# +delim+ corresponds to the `typdelim` column in the pg_types
|
11
|
+
# table. +subtype+ is derived from the `typelem` column in the
|
12
|
+
# pg_types table.
|
13
|
+
def initialize(delim, subtype)
|
14
|
+
@delim = delim
|
15
|
+
@subtype = subtype
|
16
|
+
end
|
17
|
+
|
18
|
+
# FIXME: this should probably split on +delim+ and use +subtype+
|
19
|
+
# to cast the values. Unfortunately, the current Rails behavior
|
20
|
+
# is to just return the string.
|
21
|
+
def cast(value)
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Xml < Type::String # :nodoc:
|
8
|
+
def type
|
9
|
+
:xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
return unless value
|
14
|
+
Data.new(super)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Data # :nodoc:
|
18
|
+
def initialize(value)
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
@value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/connection_adapters/yugabytedb/oid/array"
|
4
|
+
require "active_record/connection_adapters/yugabytedb/oid/bit"
|
5
|
+
require "active_record/connection_adapters/yugabytedb/oid/bit_varying"
|
6
|
+
require "active_record/connection_adapters/yugabytedb/oid/bytea"
|
7
|
+
require "active_record/connection_adapters/yugabytedb/oid/cidr"
|
8
|
+
require "active_record/connection_adapters/yugabytedb/oid/date"
|
9
|
+
require "active_record/connection_adapters/yugabytedb/oid/date_time"
|
10
|
+
require "active_record/connection_adapters/yugabytedb/oid/decimal"
|
11
|
+
require "active_record/connection_adapters/yugabytedb/oid/enum"
|
12
|
+
require "active_record/connection_adapters/yugabytedb/oid/hstore"
|
13
|
+
require "active_record/connection_adapters/yugabytedb/oid/inet"
|
14
|
+
require "active_record/connection_adapters/yugabytedb/oid/interval"
|
15
|
+
require "active_record/connection_adapters/yugabytedb/oid/jsonb"
|
16
|
+
require "active_record/connection_adapters/yugabytedb/oid/macaddr"
|
17
|
+
require "active_record/connection_adapters/yugabytedb/oid/money"
|
18
|
+
require "active_record/connection_adapters/yugabytedb/oid/oid"
|
19
|
+
require "active_record/connection_adapters/yugabytedb/oid/point"
|
20
|
+
require "active_record/connection_adapters/yugabytedb/oid/legacy_point"
|
21
|
+
require "active_record/connection_adapters/yugabytedb/oid/range"
|
22
|
+
require "active_record/connection_adapters/yugabytedb/oid/specialized_string"
|
23
|
+
require "active_record/connection_adapters/yugabytedb/oid/timestamp"
|
24
|
+
require "active_record/connection_adapters/yugabytedb/oid/timestamp_with_time_zone"
|
25
|
+
require "active_record/connection_adapters/yugabytedb/oid/uuid"
|
26
|
+
require "active_record/connection_adapters/yugabytedb/oid/vector"
|
27
|
+
require "active_record/connection_adapters/yugabytedb/oid/xml"
|
28
|
+
|
29
|
+
require "active_record/connection_adapters/yugabytedb/oid/type_map_initializer"
|
30
|
+
|
31
|
+
module ActiveRecord
|
32
|
+
module ConnectionAdapters
|
33
|
+
module YugabyteDB
|
34
|
+
module OID # :nodoc:
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module YugabyteDB
|
6
|
+
module Quoting
|
7
|
+
# Escapes binary strings for bytea input to the database.
|
8
|
+
def escape_bytea(value)
|
9
|
+
@connection.escape_bytea(value) if value
|
10
|
+
end
|
11
|
+
|
12
|
+
# Unescapes bytea output from a database to the binary string it represents.
|
13
|
+
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
14
|
+
# on escaped binary output from database drive.
|
15
|
+
def unescape_bytea(value)
|
16
|
+
@connection.unescape_bytea(value) if value
|
17
|
+
end
|
18
|
+
|
19
|
+
def quote(value) # :nodoc:
|
20
|
+
case value
|
21
|
+
when OID::Xml::Data
|
22
|
+
"xml '#{quote_string(value.to_s)}'"
|
23
|
+
when OID::Bit::Data
|
24
|
+
if value.binary?
|
25
|
+
"B'#{value}'"
|
26
|
+
elsif value.hex?
|
27
|
+
"X'#{value}'"
|
28
|
+
end
|
29
|
+
when Numeric
|
30
|
+
if value.finite?
|
31
|
+
super
|
32
|
+
else
|
33
|
+
"'#{value}'"
|
34
|
+
end
|
35
|
+
when OID::Array::Data
|
36
|
+
quote(encode_array(value))
|
37
|
+
when Range
|
38
|
+
quote(encode_range(value))
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Quotes strings for use in SQL input.
|
45
|
+
def quote_string(s) # :nodoc:
|
46
|
+
@connection.escape(s)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Checks the following cases:
|
50
|
+
#
|
51
|
+
# - table_name
|
52
|
+
# - "table.name"
|
53
|
+
# - schema_name.table_name
|
54
|
+
# - schema_name."table.name"
|
55
|
+
# - "schema.name".table_name
|
56
|
+
# - "schema.name"."table.name"
|
57
|
+
def quote_table_name(name) # :nodoc:
|
58
|
+
self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
59
|
+
end
|
60
|
+
|
61
|
+
# Quotes schema names for use in SQL queries.
|
62
|
+
def quote_schema_name(name)
|
63
|
+
YugabyteYSQL::Connection.quote_ident(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def quote_table_name_for_assignment(table, attr)
|
67
|
+
quote_column_name(attr)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Quotes column names for use in SQL queries.
|
71
|
+
def quote_column_name(name) # :nodoc:
|
72
|
+
self.class.quoted_column_names[name] ||= YugabyteYSQL::Connection.quote_ident(super).freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
# Quote date/time values for use in SQL input.
|
76
|
+
def quoted_date(value) # :nodoc:
|
77
|
+
if value.year <= 0
|
78
|
+
bce_year = format("%04d", -value.year + 1)
|
79
|
+
super.sub(/^-?\d+/, bce_year) + " BC"
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def quoted_binary(value) # :nodoc:
|
86
|
+
"'#{escape_bytea(value.to_s)}'"
|
87
|
+
end
|
88
|
+
|
89
|
+
def quote_default_expression(value, column) # :nodoc:
|
90
|
+
if value.is_a?(Proc)
|
91
|
+
value.call
|
92
|
+
elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
|
93
|
+
value # Does not quote function default values for UUID columns
|
94
|
+
elsif column.respond_to?(:array?)
|
95
|
+
type = lookup_cast_type_from_column(column)
|
96
|
+
quote(type.serialize(value))
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def type_cast(value) # :nodoc:
|
103
|
+
case value
|
104
|
+
when Type::Binary::Data
|
105
|
+
# Return a bind param hash with format as binary.
|
106
|
+
# See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
|
107
|
+
# for more information
|
108
|
+
{ value: value.to_s, format: 1 }
|
109
|
+
when OID::Xml::Data, OID::Bit::Data
|
110
|
+
value.to_s
|
111
|
+
when OID::Array::Data
|
112
|
+
encode_array(value)
|
113
|
+
when Range
|
114
|
+
encode_range(value)
|
115
|
+
else
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def lookup_cast_type_from_column(column) # :nodoc:
|
121
|
+
type_map.lookup(column.oid, column.fmod, column.sql_type)
|
122
|
+
end
|
123
|
+
|
124
|
+
def column_name_matcher
|
125
|
+
COLUMN_NAME
|
126
|
+
end
|
127
|
+
|
128
|
+
def column_name_with_order_matcher
|
129
|
+
COLUMN_NAME_WITH_ORDER
|
130
|
+
end
|
131
|
+
|
132
|
+
COLUMN_NAME = /
|
133
|
+
\A
|
134
|
+
(
|
135
|
+
(?:
|
136
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
137
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
|
138
|
+
)
|
139
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
140
|
+
)
|
141
|
+
(?:\s*,\s*\g<1>)*
|
142
|
+
\z
|
143
|
+
/ix
|
144
|
+
|
145
|
+
COLUMN_NAME_WITH_ORDER = /
|
146
|
+
\A
|
147
|
+
(
|
148
|
+
(?:
|
149
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
150
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
|
151
|
+
)
|
152
|
+
(?:\s+ASC|\s+DESC)?
|
153
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
154
|
+
)
|
155
|
+
(?:\s*,\s*\g<1>)*
|
156
|
+
\z
|
157
|
+
/ix
|
158
|
+
|
159
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
160
|
+
|
161
|
+
private
|
162
|
+
def lookup_cast_type(sql_type)
|
163
|
+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
164
|
+
end
|
165
|
+
|
166
|
+
def encode_array(array_data)
|
167
|
+
encoder = array_data.encoder
|
168
|
+
values = type_cast_array(array_data.values)
|
169
|
+
|
170
|
+
result = encoder.encode(values)
|
171
|
+
if encoding = determine_encoding_of_strings_in_array(values)
|
172
|
+
result.force_encoding(encoding)
|
173
|
+
end
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
def encode_range(range)
|
178
|
+
"[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
|
179
|
+
end
|
180
|
+
|
181
|
+
def determine_encoding_of_strings_in_array(value)
|
182
|
+
case value
|
183
|
+
when ::Array then determine_encoding_of_strings_in_array(value.first)
|
184
|
+
when ::String then value.encoding
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def type_cast_array(values)
|
189
|
+
case values
|
190
|
+
when ::Array then values.map { |item| type_cast_array(item) }
|
191
|
+
else type_cast(values)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def type_cast_range_value(value)
|
196
|
+
infinity?(value) ? "" : type_cast(value)
|
197
|
+
end
|
198
|
+
|
199
|
+
def infinity?(value)
|
200
|
+
value.respond_to?(:infinite?) && value.infinite?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|