sunstone 6.1.3 → 7.1.0.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 +4 -4
- data/ext/active_record/associations/collection_association.rb +4 -26
- data/ext/active_record/associations.rb +5 -1
- data/ext/active_record/attribute_methods.rb +21 -14
- data/ext/active_record/callbacks.rb +4 -1
- data/ext/active_record/finder_methods.rb +26 -16
- data/ext/active_record/persistence.rb +58 -21
- data/ext/active_record/relation/calculations.rb +15 -5
- data/ext/active_record/relation/query_methods.rb +1 -1
- data/ext/active_record/statement_cache.rb +0 -1
- data/ext/active_record/transactions.rb +2 -2
- data/ext/arel/nodes/select_statement.rb +3 -3
- data/lib/active_record/connection_adapters/sunstone/column.rb +11 -1
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +54 -21
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +35 -17
- data/lib/active_record/connection_adapters/sunstone/type/binary.rb +3 -3
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +109 -60
- data/lib/arel/collectors/sunstone.rb +25 -4
- data/lib/arel/visitors/sunstone.rb +81 -75
- data/lib/sunstone/connection.rb +42 -15
- data/lib/sunstone/version.rb +1 -1
- metadata +11 -11
@@ -27,19 +27,8 @@ module ActiveRecord
|
|
27
27
|
return @definitions[table_name]
|
28
28
|
end
|
29
29
|
|
30
|
-
response =
|
31
|
-
|
32
|
-
version = Gem::Version.create(response['StandardAPI-Version'] || '5.0.0.4')
|
33
|
-
|
34
|
-
@definitions[table_name] = if (version >= Gem::Version.create('6.0.0.29'))
|
35
|
-
schema = JSON.parse(response.body)
|
36
|
-
schema['columns'] = schema.delete('attributes')
|
37
|
-
schema
|
38
|
-
elsif (version >= Gem::Version.create('5.0.0.5'))
|
39
|
-
JSON.parse(response.body)
|
40
|
-
else
|
41
|
-
{ 'columns' => JSON.parse(response.body), 'limit' => nil }
|
42
|
-
end
|
30
|
+
response = with_raw_connection { |conn| conn.get("/#{table_name}/schema") }
|
31
|
+
@definitions[table_name] = JSON.parse(response.body)
|
43
32
|
rescue ::Sunstone::Exception::NotFound
|
44
33
|
raise ActiveRecord::StatementInvalid, "Table \"#{table_name}\" does not exist"
|
45
34
|
end
|
@@ -50,8 +39,8 @@ module ActiveRecord
|
|
50
39
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
51
40
|
# - ::regclass is a function that gives the id for a table name
|
52
41
|
def column_definitions(table_name) # :nodoc:
|
53
|
-
#
|
54
|
-
#
|
42
|
+
# TODO: settle on schema, I think we've switched to attributes, so
|
43
|
+
# columns can be removed soon?
|
55
44
|
definition(table_name)['attributes'] || definition(table_name)['columns']
|
56
45
|
end
|
57
46
|
|
@@ -62,7 +51,7 @@ module ActiveRecord
|
|
62
51
|
end
|
63
52
|
|
64
53
|
def tables
|
65
|
-
JSON.parse(
|
54
|
+
JSON.parse(with_raw_connection { |conn| conn.get('/tables').body })
|
66
55
|
end
|
67
56
|
|
68
57
|
def views
|
@@ -75,7 +64,7 @@ module ActiveRecord
|
|
75
64
|
end
|
76
65
|
|
77
66
|
def lookup_cast_type(options)
|
78
|
-
type_map.lookup(options['type'], options.symbolize_keys)
|
67
|
+
@type_map.lookup(options['type'], options.symbolize_keys)
|
79
68
|
end
|
80
69
|
|
81
70
|
def fetch_type_metadata(options)
|
@@ -93,6 +82,35 @@ module ActiveRecord
|
|
93
82
|
def column_name_for_operation(operation, node) # :nodoc:
|
94
83
|
visitor.accept(node, collector).first[operation.to_sym]
|
95
84
|
end
|
85
|
+
|
86
|
+
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
|
87
|
+
# PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
|
88
|
+
# require the order columns appear in the SELECT.
|
89
|
+
#
|
90
|
+
# columns_for_distinct("posts.id", ["posts.created_at desc"])
|
91
|
+
#
|
92
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
93
|
+
columns
|
94
|
+
end
|
95
|
+
|
96
|
+
def distinct_relation_for_primary_key(relation) # :nodoc:
|
97
|
+
values = columns_for_distinct(
|
98
|
+
relation.table[relation.primary_key],
|
99
|
+
relation.order_values
|
100
|
+
)
|
101
|
+
|
102
|
+
limited = relation.reselect(values).distinct!
|
103
|
+
limited_ids = select_rows(limited.arel, "SQL").map(&:last)
|
104
|
+
|
105
|
+
if limited_ids.empty?
|
106
|
+
relation.none!
|
107
|
+
else
|
108
|
+
relation.where!(relation.primary_key => limited_ids)
|
109
|
+
end
|
110
|
+
|
111
|
+
relation.limit_value = relation.offset_value = nil
|
112
|
+
relation
|
113
|
+
end
|
96
114
|
|
97
115
|
# TODO: def encoding
|
98
116
|
|
@@ -13,11 +13,11 @@ module ActiveRecord
|
|
13
13
|
#
|
14
14
|
# +value+ The raw input, as provided from the database.
|
15
15
|
def deserialize(value)
|
16
|
-
value.nil? ? nil : Base64.strict_decode64(value)
|
16
|
+
value.nil? ? nil : Base64.strict_decode64(value)
|
17
17
|
end
|
18
18
|
|
19
|
-
# Casts a value from the ruby type to a type that the database knows
|
20
|
-
# to understand. The returned value from this method should be a
|
19
|
+
# Casts a value from the ruby type to a type that the database knows
|
20
|
+
# how to understand. The returned value from this method should be a
|
21
21
|
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
22
22
|
# +nil+.
|
23
23
|
def serialize(value)
|
@@ -17,27 +17,14 @@ require 'active_record/connection_adapters/sunstone/type/json'
|
|
17
17
|
module ActiveRecord
|
18
18
|
module ConnectionHandling # :nodoc:
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
def sunstone_adapter_class
|
21
|
+
ConnectionAdapters::SunstoneAPIAdapter
|
22
|
+
end
|
23
|
+
|
22
24
|
# Establishes a connection to the database that's used by all Active Record
|
23
25
|
# objects
|
24
26
|
def sunstone_connection(config)
|
25
|
-
|
26
|
-
conn_params.delete_if { |_, v| v.nil? }
|
27
|
-
|
28
|
-
if conn_params[:url]
|
29
|
-
uri = URI.parse(conn_params.delete(:url))
|
30
|
-
conn_params[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
|
31
|
-
conn_params[:host] ||= uri.host
|
32
|
-
conn_params[:port] ||= uri.port
|
33
|
-
conn_params[:use_ssl] ||= (uri.scheme == 'https')
|
34
|
-
end
|
35
|
-
|
36
|
-
# Forward only valid config params to Sunstone::Connection
|
37
|
-
conn_params.slice!(*VALID_SUNSTONE_CONN_PARAMS)
|
38
|
-
|
39
|
-
client = ::Sunstone::Connection.new(conn_params)
|
40
|
-
ConnectionAdapters::SunstoneAPIAdapter.new(client, logger, conn_params, config)
|
27
|
+
sunstone_adapter_class.new(config)
|
41
28
|
end
|
42
29
|
end
|
43
30
|
|
@@ -54,6 +41,7 @@ module ActiveRecord
|
|
54
41
|
# <encoding></tt> call on the connection.
|
55
42
|
class SunstoneAPIAdapter < AbstractAdapter
|
56
43
|
ADAPTER_NAME = 'Sunstone'.freeze
|
44
|
+
VALID_SUNSTONE_CONN_PARAMS = [:url, :host, :port, :api_key, :use_ssl, :user_agent, :ca_cert]
|
57
45
|
|
58
46
|
NATIVE_DATABASE_TYPES = {
|
59
47
|
string: { name: "string" },
|
@@ -61,6 +49,12 @@ module ActiveRecord
|
|
61
49
|
json: { name: "json" },
|
62
50
|
boolean: { name: "boolean" }
|
63
51
|
}
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def new_client(conn_params)
|
55
|
+
::Sunstone::Connection.new(conn_params)
|
56
|
+
end
|
57
|
+
end
|
64
58
|
|
65
59
|
# include PostgreSQL::Quoting
|
66
60
|
# include PostgreSQL::ReferentialIntegrity
|
@@ -72,35 +66,66 @@ module ActiveRecord
|
|
72
66
|
def supports_statement_cache?
|
73
67
|
false
|
74
68
|
end
|
69
|
+
|
70
|
+
def default_prepared_statements
|
71
|
+
false
|
72
|
+
end
|
75
73
|
|
76
|
-
def clear_cache!
|
74
|
+
def clear_cache!(new_connection: false)
|
77
75
|
# TODO move @definitions to using @schema_cache
|
78
76
|
@definitions = {}
|
79
77
|
end
|
80
78
|
|
81
79
|
# Initializes and connects a SunstoneAPI adapter.
|
82
|
-
def initialize(
|
83
|
-
super
|
80
|
+
def initialize(...)
|
81
|
+
super
|
84
82
|
|
85
|
-
|
86
|
-
|
83
|
+
conn_params = @config.compact
|
84
|
+
if conn_params[:url]
|
85
|
+
uri = URI.parse(conn_params.delete(:url))
|
86
|
+
conn_params[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
|
87
|
+
conn_params[:host] ||= uri.host
|
88
|
+
conn_params[:port] ||= uri.port
|
89
|
+
conn_params[:use_ssl] ||= (uri.scheme == 'https')
|
90
|
+
end
|
91
|
+
|
92
|
+
# Forward only valid config params to Sunstone::Connection
|
93
|
+
conn_params.slice!(*VALID_SUNSTONE_CONN_PARAMS)
|
94
|
+
|
95
|
+
@connection_parameters = conn_params
|
96
|
+
|
97
|
+
@max_identifier_length = nil
|
98
|
+
@type_map = nil
|
99
|
+
@raw_connection = nil
|
100
|
+
end
|
87
101
|
|
88
|
-
|
89
|
-
#
|
102
|
+
def url(path=nil)
|
103
|
+
"http#{@connection_parameters[:use_ssl] ? 's' : ''}://#{@connection_parameters[:host]}#{@connection_parameters[:port] != 80 ? (@connection_parameters[:port] == 443 && @connection_parameters[:use_ssl] ? '' : ":#{@connection_parameters[:port]}") : ''}#{path}"
|
90
104
|
end
|
91
105
|
|
92
106
|
def active?
|
93
|
-
@
|
107
|
+
@raw_connection&.active?
|
94
108
|
end
|
95
109
|
|
96
|
-
def
|
110
|
+
def load_type_map
|
111
|
+
@type_map = Type::HashLookupTypeMap.new
|
112
|
+
initialize_type_map(@type_map)
|
113
|
+
end
|
114
|
+
|
115
|
+
def reconnect
|
97
116
|
super
|
98
|
-
@
|
117
|
+
@raw_connection&.reconnect!
|
99
118
|
end
|
100
119
|
|
101
120
|
def disconnect!
|
102
121
|
super
|
103
|
-
@
|
122
|
+
@raw_connection&.disconnect!
|
123
|
+
@raw_connection = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def discard! # :nodoc:
|
127
|
+
super
|
128
|
+
@raw_connection = nil
|
104
129
|
end
|
105
130
|
|
106
131
|
# Executes the delete statement and returns the number of rows affected.
|
@@ -134,11 +159,17 @@ module ActiveRecord
|
|
134
159
|
end
|
135
160
|
|
136
161
|
def server_config
|
137
|
-
|
162
|
+
with_raw_connection do |conn|
|
163
|
+
JSON.parse(conn.get("/configuration").body)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def return_value_after_insert?(column) # :nodoc:
|
168
|
+
column.auto_populated?
|
138
169
|
end
|
139
170
|
|
140
171
|
def lookup_cast_type_from_column(column) # :nodoc:
|
141
|
-
cast_type = type_map.lookup(column.sql_type, {
|
172
|
+
cast_type = @type_map.lookup(column.sql_type, {
|
142
173
|
limit: column.limit,
|
143
174
|
precision: column.precision,
|
144
175
|
scale: column.scale
|
@@ -167,45 +198,63 @@ module ActiveRecord
|
|
167
198
|
true
|
168
199
|
end
|
169
200
|
|
170
|
-
# Executes an INSERT query and returns the
|
171
|
-
#
|
172
|
-
|
173
|
-
|
174
|
-
# id and return that value.
|
175
|
-
#
|
176
|
-
# If the next id was calculated in advance (as in Oracle), it should be
|
177
|
-
# passed in as +id_value+.
|
178
|
-
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
179
|
-
exec_insert(arel, name, binds, pk, sequence_name)
|
201
|
+
# Executes an INSERT query and returns a hash of the object and
|
202
|
+
# any updated relations. This is different from AR which returns an ID
|
203
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
|
204
|
+
exec_insert(arel, name, binds, pk, sequence_name, returning: returning)
|
180
205
|
end
|
181
206
|
alias create insert
|
182
207
|
|
183
|
-
#
|
184
|
-
#
|
185
|
-
|
186
|
-
|
187
|
-
|
208
|
+
# Connects to a StandardAPI server and sets up the adapter depending
|
209
|
+
# on the connected server's characteristics.
|
210
|
+
def connect
|
211
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
212
|
+
end
|
188
213
|
|
189
|
-
|
214
|
+
def reconnect
|
215
|
+
@raw_connection&.reconnect!
|
216
|
+
connect unless @raw_connection
|
217
|
+
end
|
190
218
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
219
|
+
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
220
|
+
# This is called by #connect and should not be called manually.
|
221
|
+
def configure_connection
|
222
|
+
reload_type_map
|
223
|
+
end
|
224
|
+
|
225
|
+
def reload_type_map
|
226
|
+
if @type_map
|
227
|
+
type_map.clear
|
228
|
+
else
|
229
|
+
@type_map = Type::HashLookupTypeMap.new
|
195
230
|
end
|
196
|
-
|
197
|
-
|
231
|
+
|
232
|
+
initialize_type_map
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def initialize_type_map(m = nil)
|
238
|
+
self.class.initialize_type_map(m || @type_map)
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.initialize_type_map(m) # :nodoc:
|
242
|
+
m.register_type 'boolean', Type::Boolean.new
|
243
|
+
m.register_type 'binary' do |_, options|
|
244
|
+
Sunstone::Type::Binary.new(**options.slice(:limit))
|
198
245
|
end
|
199
|
-
m.register_type
|
246
|
+
m.register_type 'datetime', Sunstone::Type::DateTime.new
|
247
|
+
m.register_type 'decimal' do |_, options|
|
200
248
|
Type::Decimal.new(**options.slice(:precision, :scale))
|
201
249
|
end
|
202
|
-
m.register_type
|
203
|
-
|
250
|
+
m.register_type 'integer' do |_, options|
|
251
|
+
Type::Integer.new(**options.slice(:limit))
|
204
252
|
end
|
205
|
-
|
206
|
-
m.register_type
|
207
|
-
|
208
|
-
|
253
|
+
m.register_type 'json', Sunstone::Type::Json.new
|
254
|
+
m.register_type 'string' do |_, options|
|
255
|
+
Type::String.new(**options.slice(:limit))
|
256
|
+
end
|
257
|
+
m.register_type 'uuid', Sunstone::Type::Uuid.new
|
209
258
|
|
210
259
|
if defined?(Sunstone::Type::EWKB)
|
211
260
|
m.register_type 'ewkb', Sunstone::Type::EWKB.new
|
@@ -24,7 +24,14 @@ module Arel
|
|
24
24
|
def substitute_binds hash, bvs
|
25
25
|
if hash.is_a?(Array)
|
26
26
|
hash.map do |v|
|
27
|
-
if v.is_a?(
|
27
|
+
if v.is_a?(ActiveRecord::Relation::QueryAttribute)
|
28
|
+
new_v = bvs.shift
|
29
|
+
if new_v.is_a?(ActiveRecord::Relation::QueryAttribute)
|
30
|
+
new_v.value_for_database
|
31
|
+
else
|
32
|
+
v.type.serialize(new_v)
|
33
|
+
end
|
34
|
+
elsif v.is_a?(Arel::Nodes::BindParam)
|
28
35
|
bvs.shift#.value_for_database
|
29
36
|
elsif v.is_a?(Hash) || v.is_a?(Array)
|
30
37
|
substitute_binds(v, bvs)
|
@@ -35,7 +42,14 @@ module Arel
|
|
35
42
|
elsif hash.is_a?(Hash)
|
36
43
|
newhash = {}
|
37
44
|
hash.each do |k, v|
|
38
|
-
if v.is_a?(
|
45
|
+
if v.is_a?(ActiveRecord::Relation::QueryAttribute)
|
46
|
+
new_v = bvs.shift
|
47
|
+
newhash[k] = if new_v.is_a?(ActiveRecord::Relation::QueryAttribute)
|
48
|
+
new_v.value_for_database
|
49
|
+
else
|
50
|
+
v.type.serialize(new_v)
|
51
|
+
end
|
52
|
+
elsif v.is_a?(Arel::Nodes::BindParam)
|
39
53
|
newhash[k] = bvs.shift || v.value.value_for_database
|
40
54
|
elsif v.is_a?(Hash)
|
41
55
|
newhash[k] = substitute_binds(v, bvs)
|
@@ -47,9 +61,16 @@ module Arel
|
|
47
61
|
end
|
48
62
|
newhash
|
49
63
|
elsif hash.is_a?(Arel::Nodes::BindParam)
|
50
|
-
bvs.shift
|
64
|
+
bvs.shift
|
65
|
+
elsif hash.is_a?(ActiveRecord::Relation::QueryAttribute)
|
66
|
+
new_v = bvs.shift
|
67
|
+
if new_v.is_a?(ActiveRecord::Relation::QueryAttribute)
|
68
|
+
new_v.value_for_database
|
69
|
+
else
|
70
|
+
v.type.serialize(new_v)
|
71
|
+
end
|
51
72
|
else
|
52
|
-
bvs.shift
|
73
|
+
bvs.shift
|
53
74
|
end
|
54
75
|
end
|
55
76
|
|