sunstone 6.0.0.4 → 6.1.0.2
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/.github/workflows/main.yml +141 -0
- data/README.md +39 -3
- data/ext/active_record/associations.rb +2 -2
- data/ext/active_record/attribute_methods.rb +2 -2
- data/ext/active_record/callbacks.rb +1 -1
- data/ext/active_record/finder_methods.rb +40 -35
- data/ext/active_record/persistence.rb +2 -0
- data/ext/active_record/relation/calculations.rb +2 -2
- data/ext/active_record/statement_cache.rb +9 -5
- data/ext/active_record/transactions.rb +8 -15
- data/ext/arel/attributes/empty_relation.rb +31 -31
- data/ext/arel/nodes/select_statement.rb +1 -1
- data/lib/active_record/connection_adapters/sunstone/column.rb +2 -2
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +5 -5
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +18 -8
- data/lib/active_record/connection_adapters/sunstone/type/binary.rb +34 -0
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +39 -26
- data/lib/arel/visitors/sunstone.rb +13 -37
- data/lib/sunstone.rb +16 -2
- data/lib/sunstone/connection.rb +1 -1
- data/lib/sunstone/version.rb +1 -1
- data/sunstone.gemspec +3 -2
- data/test/active_record/persistance_test.rb +31 -6
- data/test/active_record/query_test.rb +9 -1
- data/test/schema_mock.rb +30 -26
- data/test/sunstone/connection/column_definition_test.rb +30 -0
- data/test/test_helper.rb +1 -0
- metadata +28 -12
- data/.travis.yml +0 -49
- data/ext/arel/attributes/relation.rb +0 -31
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
|
-
def save!(
|
23
|
+
def save!(**) #:nodoc:
|
24
24
|
if instance_variable_defined?(:@no_save_transaction) && @no_save_transaction
|
25
25
|
super
|
26
26
|
else
|
@@ -35,29 +35,22 @@ module ActiveRecord
|
|
35
35
|
|
36
36
|
def with_transaction_returning_status
|
37
37
|
status = nil
|
38
|
+
connection = self.class.connection
|
38
39
|
|
39
|
-
if
|
40
|
+
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && instance_variable_defined?(:@updating) && @updating
|
40
41
|
status = yield
|
41
|
-
status
|
42
42
|
else
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
else
|
47
|
-
sync_with_transaction_state if @transaction_state&.finalized?
|
48
|
-
@transaction_state = self.class.connection.transaction_state
|
49
|
-
end
|
43
|
+
ensure_finalize = !connection.transaction_open?
|
44
|
+
connection.transaction do
|
45
|
+
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
50
46
|
remember_transaction_record_state
|
51
47
|
|
52
48
|
status = yield
|
53
49
|
raise ActiveRecord::Rollback unless status
|
54
50
|
end
|
55
|
-
status
|
56
|
-
end
|
57
|
-
ensure
|
58
|
-
if @transaction_state && @transaction_state.committed?
|
59
|
-
clear_transaction_record_state
|
60
51
|
end
|
52
|
+
|
53
|
+
status
|
61
54
|
end
|
62
55
|
|
63
56
|
|
@@ -1,31 +1,31 @@
|
|
1
|
-
module Arel
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
1
|
+
# module Arel
|
2
|
+
# module Attributes
|
3
|
+
# class EmptyRelation < Attribute
|
4
|
+
#
|
5
|
+
# attr_accessor :collection, :for_write
|
6
|
+
#
|
7
|
+
# def initialize(relation, name, collection = false, for_write=false)
|
8
|
+
# self[:relation] = relation
|
9
|
+
# self[:name] = name
|
10
|
+
# @collection = collection
|
11
|
+
# @for_write = for_write
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def able_to_type_cast?
|
15
|
+
# false
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def table_name
|
19
|
+
# nil
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def eql? other
|
23
|
+
# self.class == other.class &&
|
24
|
+
# self.relation == other.relation &&
|
25
|
+
# self.name == other.name &&
|
26
|
+
# self.collection == other.collection
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
@@ -2,8 +2,7 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
# Sunstone-specific extensions to column definitions in a table.
|
4
4
|
class SunstoneColumn < Column #:nodoc:
|
5
|
-
|
6
|
-
alias :array? :array
|
5
|
+
attr_reader :array
|
7
6
|
|
8
7
|
def initialize(name, sql_type_metadata, options={})
|
9
8
|
@name = name.freeze
|
@@ -14,6 +13,7 @@ module ActiveRecord
|
|
14
13
|
@collation = nil
|
15
14
|
@table_name = nil
|
16
15
|
@primary_key = (options['primary_key'] == true)
|
16
|
+
@array = options['array']
|
17
17
|
end
|
18
18
|
|
19
19
|
def primary_key?
|
@@ -47,15 +47,15 @@ module ActiveRecord
|
|
47
47
|
# can be used to query the database repeatedly.
|
48
48
|
def cacheable_query(klass, arel) # :nodoc:
|
49
49
|
if prepared_statements
|
50
|
-
sql, binds = visitor.
|
50
|
+
sql, binds = visitor.compile(arel.ast, collector)
|
51
51
|
query = klass.query(sql)
|
52
52
|
elsif self.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
53
53
|
collector = SunstonePartialQueryCollector.new(self.collector)
|
54
|
-
parts, binds = visitor.
|
54
|
+
parts, binds = visitor.compile(arel.ast, collector)
|
55
55
|
query = StatementCache::PartialQuery.new(parts, true)
|
56
56
|
else
|
57
|
-
collector =
|
58
|
-
parts, binds = visitor.
|
57
|
+
collector = klass.partial_query_collector
|
58
|
+
parts, binds = visitor.compile(arel.ast, collector)
|
59
59
|
query = klass.partial_query(parts)
|
60
60
|
end
|
61
61
|
[query, binds]
|
@@ -142,7 +142,7 @@ module ActiveRecord
|
|
142
142
|
|
143
143
|
if sars[0].instance_variable_defined?(:@sunstone_calculation) && sars[0].instance_variable_get(:@sunstone_calculation)
|
144
144
|
# this is a count, min, max.... yea i know..
|
145
|
-
ActiveRecord::Result.new(['all'], [result], {:all => type_map.lookup('integer')})
|
145
|
+
ActiveRecord::Result.new(['all'], [result], {:all => type_map.lookup('integer', {})})
|
146
146
|
elsif result.is_a?(Array)
|
147
147
|
ActiveRecord::Result.new(result[0] ? result[0].keys : [], result.map{|r| r.values})
|
148
148
|
else
|
@@ -31,7 +31,11 @@ module ActiveRecord
|
|
31
31
|
|
32
32
|
version = Gem::Version.create(response['StandardAPI-Version'] || '5.0.0.4')
|
33
33
|
|
34
|
-
@definitions[table_name] = if (version >= Gem::Version.create('
|
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'))
|
35
39
|
JSON.parse(response.body)
|
36
40
|
else
|
37
41
|
{ 'columns' => JSON.parse(response.body), 'limit' => nil }
|
@@ -46,7 +50,9 @@ module ActiveRecord
|
|
46
50
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
47
51
|
# - ::regclass is a function that gives the id for a table name
|
48
52
|
def column_definitions(table_name) # :nodoc:
|
49
|
-
|
53
|
+
# First check for attributes and then for the deprecated columns field
|
54
|
+
# TODO: Remove after 0.3
|
55
|
+
definition(table_name)['attributes'] || definition(table_name)['columns']
|
50
56
|
end
|
51
57
|
|
52
58
|
# Returns the limit definition of the table (the maximum limit that can
|
@@ -58,28 +64,32 @@ module ActiveRecord
|
|
58
64
|
def tables
|
59
65
|
JSON.parse(@connection.get('/tables').body)
|
60
66
|
end
|
61
|
-
|
67
|
+
|
62
68
|
def views
|
63
69
|
[]
|
64
70
|
end
|
65
|
-
|
71
|
+
|
66
72
|
def new_column(name, options)
|
67
73
|
sql_type_metadata = fetch_type_metadata(options)
|
68
74
|
SunstoneColumn.new(name, sql_type_metadata, options)
|
69
75
|
end
|
70
|
-
|
76
|
+
|
77
|
+
def lookup_cast_type(options)
|
78
|
+
type_map.lookup(options['type'], options.symbolize_keys)
|
79
|
+
end
|
80
|
+
|
71
81
|
def fetch_type_metadata(options)
|
72
|
-
cast_type = lookup_cast_type(options
|
82
|
+
cast_type = lookup_cast_type(options)
|
73
83
|
simple_type = SqlTypeMetadata.new(
|
74
84
|
sql_type: options['type'],
|
75
85
|
type: cast_type.type,
|
76
86
|
limit: cast_type.limit,
|
77
87
|
precision: cast_type.precision,
|
78
|
-
scale: cast_type.scale
|
88
|
+
scale: cast_type.scale
|
79
89
|
)
|
80
90
|
SunstoneSQLTypeMetadata.new(simple_type, options)
|
81
91
|
end
|
82
|
-
|
92
|
+
|
83
93
|
def column_name_for_operation(operation, node) # :nodoc:
|
84
94
|
visitor.accept(node, collector).first[operation.to_sym]
|
85
95
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Sunstone
|
6
|
+
module Type
|
7
|
+
class Binary < ActiveRecord::Type::Binary
|
8
|
+
|
9
|
+
# Converts a value from database input to the appropriate ruby type. The
|
10
|
+
# return value of this method will be returned from
|
11
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
12
|
+
# implementation just calls Value#cast.
|
13
|
+
#
|
14
|
+
# +value+ The raw input, as provided from the database.
|
15
|
+
def deserialize(value)
|
16
|
+
value.nil? ? nil : Base64.strict_decode64(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Casts a value from the ruby type to a type that the database knows how
|
20
|
+
# to understand. The returned value from this method should be a
|
21
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
22
|
+
# +nil+.
|
23
|
+
def serialize(value)
|
24
|
+
if limit && value.bytesize > limit
|
25
|
+
raise ActiveModel::RangeError, "value is out of range for #{self.class} with limit #{limit} bytes"
|
26
|
+
end
|
27
|
+
Base64.strict_encode64(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
2
|
|
3
|
-
|
3
|
+
require 'arel/nodes/relation'
|
4
|
+
require 'arel/visitors/to_sql_extensions'
|
4
5
|
|
5
6
|
require 'active_record/connection_adapters/sunstone/database_statements'
|
6
7
|
require 'active_record/connection_adapters/sunstone/schema_statements'
|
@@ -9,6 +10,7 @@ require 'active_record/connection_adapters/sunstone/column'
|
|
9
10
|
|
10
11
|
require 'active_record/connection_adapters/sunstone/type/date_time'
|
11
12
|
require 'active_record/connection_adapters/sunstone/type/array'
|
13
|
+
require 'active_record/connection_adapters/sunstone/type/binary'
|
12
14
|
require 'active_record/connection_adapters/sunstone/type/uuid'
|
13
15
|
require 'active_record/connection_adapters/sunstone/type/json'
|
14
16
|
|
@@ -30,7 +32,7 @@ module ActiveRecord
|
|
30
32
|
conn_params[:port] ||= uri.port
|
31
33
|
conn_params[:use_ssl] ||= (uri.scheme == 'https')
|
32
34
|
end
|
33
|
-
|
35
|
+
|
34
36
|
# Forward only valid config params to Sunstone::Connection
|
35
37
|
conn_params.slice!(*VALID_SUNSTONE_CONN_PARAMS)
|
36
38
|
|
@@ -78,9 +80,9 @@ module ActiveRecord
|
|
78
80
|
|
79
81
|
# Initializes and connects a SunstoneAPI adapter.
|
80
82
|
def initialize(connection, logger, connection_parameters, config)
|
81
|
-
super(connection, logger, config)
|
83
|
+
super(connection, logger, config.reverse_merge(prepared_statements: false))
|
82
84
|
|
83
|
-
@
|
85
|
+
@prepared_statement_status = Concurrent::ThreadLocalVar.new(false)
|
84
86
|
@connection_parameters = connection_parameters
|
85
87
|
|
86
88
|
# @type_map = Type::HashLookupTypeMap.new
|
@@ -95,12 +97,12 @@ module ActiveRecord
|
|
95
97
|
super
|
96
98
|
@connection.reconnect!
|
97
99
|
end
|
98
|
-
|
100
|
+
|
99
101
|
def disconnect!
|
100
102
|
super
|
101
103
|
@connection.disconnect!
|
102
104
|
end
|
103
|
-
|
105
|
+
|
104
106
|
# Executes the delete statement and returns the number of rows affected.
|
105
107
|
def delete(arel, name = nil, binds = [])
|
106
108
|
r = exec_delete(arel, name, binds)
|
@@ -122,27 +124,28 @@ module ActiveRecord
|
|
122
124
|
def update_table_definition(table_name, base) #:nodoc:
|
123
125
|
SunstoneAPI::Table.new(table_name, base)
|
124
126
|
end
|
125
|
-
|
127
|
+
|
126
128
|
def arel_visitor
|
127
129
|
Arel::Visitors::Sunstone.new
|
128
130
|
end
|
129
|
-
|
131
|
+
|
130
132
|
def collector
|
131
133
|
Arel::Collectors::Sunstone.new
|
132
134
|
end
|
133
|
-
|
135
|
+
|
134
136
|
def server_config
|
135
137
|
JSON.parse(@connection.get("/configuration").body)
|
136
138
|
end
|
137
|
-
|
139
|
+
|
138
140
|
def lookup_cast_type_from_column(column) # :nodoc:
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
141
|
+
cast_type = type_map.lookup(column.sql_type, {
|
142
|
+
limit: column.limit,
|
143
|
+
precision: column.precision,
|
144
|
+
scale: column.scale
|
145
|
+
})
|
146
|
+
column.array ? Sunstone::Type::Array.new(cast_type) : cast_type
|
144
147
|
end
|
145
|
-
|
148
|
+
|
146
149
|
def transaction(requires_new: nil, isolation: nil, joinable: true)
|
147
150
|
Thread.current[:sunstone_transaction_count] ||= 0
|
148
151
|
Thread.current[:sunstone_request_sent] = nil if Thread.current[:sunstone_transaction_count] == 0
|
@@ -159,7 +162,7 @@ module ActiveRecord
|
|
159
162
|
rescue ActiveRecord::Rollback
|
160
163
|
# rollbacks are silently swallowed
|
161
164
|
end
|
162
|
-
|
165
|
+
|
163
166
|
def supports_json?
|
164
167
|
true
|
165
168
|
end
|
@@ -176,33 +179,43 @@ module ActiveRecord
|
|
176
179
|
exec_insert(arel, name, binds, pk, sequence_name)
|
177
180
|
end
|
178
181
|
alias create insert
|
179
|
-
|
182
|
+
|
180
183
|
# Should be the defuat insert, but rails escapes if for SQL so we'll just
|
181
184
|
# catch the string "DEFATUL VALUES" in the visitor
|
182
185
|
# def empty_insert_statement_value
|
183
186
|
# {}
|
184
187
|
# end
|
185
|
-
|
188
|
+
|
186
189
|
private
|
187
190
|
|
188
191
|
def initialize_type_map(m) # :nodoc:
|
189
192
|
m.register_type 'boolean', Type::Boolean.new
|
190
|
-
m.register_type 'string',
|
191
|
-
|
192
|
-
|
193
|
+
m.register_type 'string' do |_, options|
|
194
|
+
Type::String.new(**options.slice(:limit))
|
195
|
+
end
|
196
|
+
m.register_type 'integer' do |_, options|
|
197
|
+
Type::Integer.new(**options.slice(:limit))
|
198
|
+
end
|
199
|
+
m.register_type 'decimal' do |_, options|
|
200
|
+
Type::Decimal.new(**options.slice(:precision, :scale))
|
201
|
+
end
|
202
|
+
m.register_type 'binary' do |_, options|
|
203
|
+
Sunstone::Type::Binary.new(**options.slice(:limit))
|
204
|
+
end
|
205
|
+
|
193
206
|
m.register_type 'datetime', Sunstone::Type::DateTime.new
|
194
207
|
m.register_type 'json', Sunstone::Type::Json.new
|
195
208
|
m.register_type 'uuid', Sunstone::Type::Uuid.new
|
196
|
-
|
209
|
+
|
197
210
|
if defined?(Sunstone::Type::EWKB)
|
198
211
|
m.register_type 'ewkb', Sunstone::Type::EWKB.new
|
199
212
|
end
|
200
213
|
end
|
201
214
|
|
202
|
-
def create_table_definition(name,
|
203
|
-
SunstoneAPI::TableDefinition.new
|
215
|
+
def create_table_definition(name, **options)
|
216
|
+
SunstoneAPI::TableDefinition.new(self, name, **options)
|
204
217
|
end
|
205
|
-
|
218
|
+
|
206
219
|
ActiveRecord::Type.add_modifier({ array: true }, Sunstone::Type::Array, adapter: :sunstone)
|
207
220
|
# ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
|
208
221
|
end
|
@@ -1,19 +1,13 @@
|
|
1
1
|
require 'arel/visitors/visitor'
|
2
|
-
class Arel::Visitors::Dot
|
3
|
-
def visit_Arel_Nodes_Casted o
|
4
|
-
# collector << quoted(o.val, o.attribute).to_s
|
5
|
-
visit_String o.val
|
6
|
-
end
|
7
|
-
end
|
8
2
|
|
9
3
|
module Arel
|
10
4
|
module Visitors
|
11
5
|
class Sunstone < Arel::Visitors::Visitor
|
12
6
|
|
13
|
-
def compile
|
14
|
-
accept(node,
|
7
|
+
def compile(node, collector = Arel::Collectors::Sunstone.new)
|
8
|
+
accept(node, collector).value
|
15
9
|
end
|
16
|
-
|
10
|
+
|
17
11
|
def preparable
|
18
12
|
false
|
19
13
|
end
|
@@ -67,28 +61,6 @@ module Arel
|
|
67
61
|
collector
|
68
62
|
end
|
69
63
|
|
70
|
-
def visit_Arel_Nodes_Overlaps o, collector
|
71
|
-
key = visit(o.left, collector)
|
72
|
-
value = { overlaps: visit(o.right, collector) }
|
73
|
-
if key.is_a?(Hash)
|
74
|
-
add_to_bottom_of_hash_or_array(key, value)
|
75
|
-
key
|
76
|
-
else
|
77
|
-
{key => value}
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def visit_Arel_Nodes_NotOverlaps o, collector
|
82
|
-
key = visit(o.left, collector)
|
83
|
-
value = { not_overlaps: visit(o.right, collector) }
|
84
|
-
if key.is_a?(Hash)
|
85
|
-
add_to_bottom_of_hash_or_array(key, value)
|
86
|
-
key
|
87
|
-
else
|
88
|
-
{key => value}
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
64
|
def visit_Arel_Nodes_InsertStatement o, collector
|
93
65
|
collector.request_type = Net::HTTP::Post
|
94
66
|
collector.table = o.relation.name
|
@@ -255,7 +227,7 @@ module Arel
|
|
255
227
|
#
|
256
228
|
def visit_Arel_Nodes_Casted o, collector
|
257
229
|
# collector << quoted(o.val, o.attribute).to_s
|
258
|
-
o.
|
230
|
+
o.value
|
259
231
|
end
|
260
232
|
|
261
233
|
def visit_Arel_Nodes_Quoted o, collector
|
@@ -731,6 +703,7 @@ module Arel
|
|
731
703
|
{key => value}
|
732
704
|
end
|
733
705
|
end
|
706
|
+
alias_method :visit_Arel_Nodes_HomogeneousIn, :visit_Arel_Nodes_In
|
734
707
|
|
735
708
|
def visit_Arel_Nodes_NotIn o, collector
|
736
709
|
key = visit(o.left, collector)
|
@@ -947,9 +920,12 @@ module Arel
|
|
947
920
|
value = if o.relation.is_a?(Arel::Attributes::Relation)
|
948
921
|
{ o.name => visit_Arel_Attributes_Relation(o.relation, collector, false) }
|
949
922
|
else
|
950
|
-
|
923
|
+
if o.relation.is_a?(Arel::Attributes::Attribute)
|
924
|
+
{ o.name => o.relation.name }
|
925
|
+
else
|
926
|
+
visit(o.relation, collector)
|
927
|
+
end
|
951
928
|
end
|
952
|
-
# value = value.to_s.split('.').last if !value.is_a?(Hash)
|
953
929
|
|
954
930
|
if o.collection
|
955
931
|
ary = []
|
@@ -972,9 +948,9 @@ module Arel
|
|
972
948
|
end
|
973
949
|
end
|
974
950
|
|
975
|
-
def visit_Arel_Attributes_EmptyRelation o, collector, top=true
|
976
|
-
|
977
|
-
end
|
951
|
+
# def visit_Arel_Attributes_EmptyRelation o, collector, top=true
|
952
|
+
# o.for_write ? "#{o.name}_attributes" : o.name
|
953
|
+
# end
|
978
954
|
|
979
955
|
def visit_Arel_Attributes_Attribute o, collector
|
980
956
|
join_name = o.relation.table_alias || o.relation.name
|