dm-core 0.10.2 → 1.0.0.rc1
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.
- data/.gitignore +10 -1
- data/Gemfile +143 -0
- data/Rakefile +9 -5
- data/VERSION +1 -1
- data/dm-core.gemspec +160 -57
- data/lib/dm-core.rb +131 -56
- data/lib/dm-core/adapters.rb +98 -14
- data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
- data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
- data/lib/dm-core/associations/many_to_many.rb +19 -30
- data/lib/dm-core/associations/many_to_one.rb +58 -42
- data/lib/dm-core/associations/one_to_many.rb +33 -23
- data/lib/dm-core/associations/one_to_one.rb +27 -11
- data/lib/dm-core/associations/relationship.rb +4 -4
- data/lib/dm-core/collection.rb +23 -16
- data/lib/dm-core/core_ext/array.rb +36 -0
- data/lib/dm-core/core_ext/hash.rb +30 -0
- data/lib/dm-core/core_ext/module.rb +46 -0
- data/lib/dm-core/core_ext/object.rb +31 -0
- data/lib/dm-core/core_ext/pathname.rb +20 -0
- data/lib/dm-core/core_ext/string.rb +22 -0
- data/lib/dm-core/core_ext/try_dup.rb +44 -0
- data/lib/dm-core/model.rb +88 -27
- data/lib/dm-core/model/hook.rb +75 -18
- data/lib/dm-core/model/property.rb +50 -9
- data/lib/dm-core/model/relationship.rb +31 -31
- data/lib/dm-core/model/scope.rb +3 -3
- data/lib/dm-core/property.rb +196 -516
- data/lib/dm-core/property/binary.rb +7 -0
- data/lib/dm-core/property/boolean.rb +35 -0
- data/lib/dm-core/property/class.rb +24 -0
- data/lib/dm-core/property/date.rb +47 -0
- data/lib/dm-core/property/date_time.rb +48 -0
- data/lib/dm-core/property/decimal.rb +43 -0
- data/lib/dm-core/property/discriminator.rb +48 -0
- data/lib/dm-core/property/float.rb +24 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/numeric.rb +43 -0
- data/lib/dm-core/property/object.rb +32 -0
- data/lib/dm-core/property/serial.rb +8 -0
- data/lib/dm-core/property/string.rb +49 -0
- data/lib/dm-core/property/text.rb +12 -0
- data/lib/dm-core/property/time.rb +48 -0
- data/lib/dm-core/property/typecast/numeric.rb +32 -0
- data/lib/dm-core/property/typecast/time.rb +28 -0
- data/lib/dm-core/property_set.rb +10 -4
- data/lib/dm-core/query.rb +14 -37
- data/lib/dm-core/query/conditions/comparison.rb +8 -6
- data/lib/dm-core/query/conditions/operation.rb +33 -2
- data/lib/dm-core/query/operator.rb +2 -5
- data/lib/dm-core/query/path.rb +4 -6
- data/lib/dm-core/repository.rb +21 -6
- data/lib/dm-core/resource.rb +316 -133
- data/lib/dm-core/resource/state.rb +79 -0
- data/lib/dm-core/resource/state/clean.rb +40 -0
- data/lib/dm-core/resource/state/deleted.rb +30 -0
- data/lib/dm-core/resource/state/dirty.rb +86 -0
- data/lib/dm-core/resource/state/immutable.rb +34 -0
- data/lib/dm-core/resource/state/persisted.rb +29 -0
- data/lib/dm-core/resource/state/transient.rb +70 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
- data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
- data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
- data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
- data/lib/dm-core/spec/setup.rb +165 -0
- data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
- data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
- data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/equalizer.rb +1 -0
- data/lib/dm-core/support/hook.rb +420 -0
- data/lib/dm-core/support/lazy_array.rb +453 -0
- data/lib/dm-core/support/local_object_space.rb +12 -0
- data/lib/dm-core/support/logger.rb +193 -6
- data/lib/dm-core/support/naming_conventions.rb +8 -8
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/type.rb +4 -0
- data/lib/dm-core/types/boolean.rb +2 -0
- data/lib/dm-core/types/decimal.rb +9 -0
- data/lib/dm-core/types/discriminator.rb +2 -0
- data/lib/dm-core/types/object.rb +3 -0
- data/lib/dm-core/types/serial.rb +2 -0
- data/lib/dm-core/types/text.rb +2 -0
- data/lib/dm-core/version.rb +1 -1
- data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
- data/spec/public/model/hook_spec.rb +209 -0
- data/spec/public/model/property_spec.rb +35 -0
- data/spec/public/model/relationship_spec.rb +33 -20
- data/spec/public/model_spec.rb +142 -10
- data/spec/public/property/binary_spec.rb +14 -0
- data/spec/public/property/boolean_spec.rb +14 -0
- data/spec/public/property/class_spec.rb +20 -0
- data/spec/public/property/date_spec.rb +14 -0
- data/spec/public/property/date_time_spec.rb +14 -0
- data/spec/public/property/decimal_spec.rb +14 -0
- data/spec/public/{types → property}/discriminator_spec.rb +2 -12
- data/spec/public/property/float_spec.rb +14 -0
- data/spec/public/property/integer_spec.rb +14 -0
- data/spec/public/property/object_spec.rb +9 -17
- data/spec/public/property/serial_spec.rb +14 -0
- data/spec/public/property/string_spec.rb +14 -0
- data/spec/public/property/text_spec.rb +52 -0
- data/spec/public/property/time_spec.rb +14 -0
- data/spec/public/property_spec.rb +28 -87
- data/spec/public/resource_spec.rb +101 -0
- data/spec/public/sel_spec.rb +5 -15
- data/spec/public/shared/collection_shared_spec.rb +16 -30
- data/spec/public/shared/finder_shared_spec.rb +2 -4
- data/spec/public/shared/property_shared_spec.rb +176 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
- data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
- data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
- data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
- data/spec/semipublic/associations/relationship_spec.rb +3 -3
- data/spec/semipublic/associations_spec.rb +1 -1
- data/spec/semipublic/property/binary_spec.rb +13 -0
- data/spec/semipublic/property/boolean_spec.rb +65 -0
- data/spec/semipublic/property/class_spec.rb +33 -0
- data/spec/semipublic/property/date_spec.rb +43 -0
- data/spec/semipublic/property/date_time_spec.rb +46 -0
- data/spec/semipublic/property/decimal_spec.rb +82 -0
- data/spec/semipublic/property/discriminator_spec.rb +19 -0
- data/spec/semipublic/property/float_spec.rb +82 -0
- data/spec/semipublic/property/integer_spec.rb +82 -0
- data/spec/semipublic/property/serial_spec.rb +13 -0
- data/spec/semipublic/property/string_spec.rb +13 -0
- data/spec/semipublic/property/text_spec.rb +31 -0
- data/spec/semipublic/property/time_spec.rb +50 -0
- data/spec/semipublic/property_spec.rb +2 -532
- data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
- data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
- data/spec/semipublic/query/path_spec.rb +17 -17
- data/spec/semipublic/query_spec.rb +47 -78
- data/spec/semipublic/resource/state/clean_spec.rb +88 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
- data/spec/semipublic/resource/state/transient_spec.rb +128 -0
- data/spec/semipublic/resource/state_spec.rb +226 -0
- data/spec/semipublic/shared/property_shared_spec.rb +143 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
- data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec_helper.rb +21 -97
- data/spec/support/types/huge_integer.rb +17 -0
- data/spec/unit/array_spec.rb +48 -0
- data/spec/unit/hash_spec.rb +35 -0
- data/spec/unit/hook_spec.rb +1234 -0
- data/spec/unit/lazy_array_spec.rb +1959 -0
- data/spec/unit/module_spec.rb +70 -0
- data/spec/unit/object_spec.rb +37 -0
- data/spec/unit/try_dup_spec.rb +45 -0
- data/tasks/local_gemfile.rake +18 -0
- data/tasks/spec.rake +0 -3
- metadata +197 -71
- data/deps.rip +0 -2
- data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
- data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
- data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
- data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
- data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
- data/lib/dm-core/core_ext/enumerable.rb +0 -28
- data/lib/dm-core/migrations.rb +0 -1427
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
- data/lib/dm-core/transaction.rb +0 -508
- data/lib/dm-core/types/paranoid_boolean.rb +0 -42
- data/lib/dm-core/types/paranoid_datetime.rb +0 -41
- data/spec/lib/adapter_helpers.rb +0 -105
- data/spec/lib/collection_helpers.rb +0 -18
- data/spec/lib/pending_helpers.rb +0 -46
- data/spec/public/migrations_spec.rb +0 -503
- data/spec/public/transaction_spec.rb +0 -153
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
data/deps.rip
DELETED
@@ -1,712 +0,0 @@
|
|
1
|
-
require 'data_objects'
|
2
|
-
|
3
|
-
module DataMapper
|
4
|
-
module Adapters
|
5
|
-
# DataObjectsAdapter is the base class for all adapers for relational
|
6
|
-
# databases. If you want to add support for a new RDBMS, it makes
|
7
|
-
# sense to make your adapter class inherit from this class.
|
8
|
-
#
|
9
|
-
# By inheriting from DataObjectsAdapter, you get a copy of all the
|
10
|
-
# standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
|
11
|
-
# You can extend and overwrite these copies without affecting the originals.
|
12
|
-
class DataObjectsAdapter < AbstractAdapter
|
13
|
-
extend Chainable
|
14
|
-
extend Deprecate
|
15
|
-
|
16
|
-
deprecate :query, :select
|
17
|
-
|
18
|
-
# Retrieve results using an SQL SELECT statement
|
19
|
-
#
|
20
|
-
# @param [String] statement
|
21
|
-
# the SQL SELECT statement
|
22
|
-
# @param [Array] *bind_values
|
23
|
-
# optional bind values to merge into the statement
|
24
|
-
#
|
25
|
-
# @return [Array]
|
26
|
-
# if fields > 1, return an Array of Struct objects
|
27
|
-
# if fields == 1, return an Array of objects
|
28
|
-
#
|
29
|
-
# @api public
|
30
|
-
def select(statement, *bind_values)
|
31
|
-
with_connection do |connection|
|
32
|
-
reader = connection.create_command(statement).execute_reader(*bind_values)
|
33
|
-
fields = reader.fields
|
34
|
-
|
35
|
-
begin
|
36
|
-
if fields.size > 1
|
37
|
-
select_fields(reader, fields)
|
38
|
-
else
|
39
|
-
select_field(reader)
|
40
|
-
end
|
41
|
-
ensure
|
42
|
-
reader.close
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Execute non-SELECT SQL query
|
48
|
-
#
|
49
|
-
# @param [String] statement
|
50
|
-
# the SQL statement
|
51
|
-
# @param [Array] *bind_values
|
52
|
-
# optional bind values to merge into the statement
|
53
|
-
#
|
54
|
-
# @return [DataObjects::Result]
|
55
|
-
# result with number of affected rows, and insert id if any
|
56
|
-
#
|
57
|
-
# @api public
|
58
|
-
def execute(statement, *bind_values)
|
59
|
-
with_connection do |connection|
|
60
|
-
command = connection.create_command(statement)
|
61
|
-
command.execute_non_query(*bind_values)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# For each model instance in resources, issues an SQL INSERT
|
66
|
-
# (or equivalent) statement to create a new record in the data store for
|
67
|
-
# the instance
|
68
|
-
#
|
69
|
-
# Note that this method does not update identity map. A plugin needs to use
|
70
|
-
# adapter directly, it is up to plugin developer to keep identity map
|
71
|
-
# up to date.
|
72
|
-
#
|
73
|
-
# @param [Enumerable(Resource)] resources
|
74
|
-
# The list of resources (model instances) to create
|
75
|
-
#
|
76
|
-
# @return [Integer]
|
77
|
-
# The number of records that were actually saved into the database
|
78
|
-
#
|
79
|
-
# @api semipublic
|
80
|
-
def create(resources)
|
81
|
-
name = self.name
|
82
|
-
|
83
|
-
resources.each do |resource|
|
84
|
-
model = resource.model
|
85
|
-
serial = model.serial(name)
|
86
|
-
attributes = resource.dirty_attributes
|
87
|
-
|
88
|
-
properties = []
|
89
|
-
bind_values = []
|
90
|
-
|
91
|
-
# make the order of the properties consistent
|
92
|
-
model.properties(name).each do |property|
|
93
|
-
next unless attributes.key?(property)
|
94
|
-
|
95
|
-
bind_value = attributes[property]
|
96
|
-
|
97
|
-
# skip insering NULL for columns that are serial or without a default
|
98
|
-
next if bind_value.nil? && (property.serial? || !property.default?)
|
99
|
-
|
100
|
-
# if serial is being set explicitly, do not set it again
|
101
|
-
if property.equal?(serial)
|
102
|
-
serial = nil
|
103
|
-
end
|
104
|
-
|
105
|
-
properties << property
|
106
|
-
bind_values << bind_value
|
107
|
-
end
|
108
|
-
|
109
|
-
statement = insert_statement(model, properties, serial)
|
110
|
-
result = execute(statement, *bind_values)
|
111
|
-
|
112
|
-
if result.affected_rows == 1 && serial
|
113
|
-
serial.set!(resource, result.insert_id)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Constructs and executes SELECT query, then instantiates
|
119
|
-
# one or many object from result set.
|
120
|
-
#
|
121
|
-
# @param [Query] query
|
122
|
-
# composition of the query to perform
|
123
|
-
#
|
124
|
-
# @return [Array]
|
125
|
-
# result set of the query
|
126
|
-
#
|
127
|
-
# @api semipublic
|
128
|
-
def read(query)
|
129
|
-
fields = query.fields
|
130
|
-
types = fields.map { |property| property.primitive }
|
131
|
-
|
132
|
-
statement, bind_values = select_statement(query)
|
133
|
-
|
134
|
-
records = []
|
135
|
-
|
136
|
-
with_connection do |connection|
|
137
|
-
command = connection.create_command(statement)
|
138
|
-
command.set_types(types)
|
139
|
-
|
140
|
-
reader = command.execute_reader(*bind_values)
|
141
|
-
|
142
|
-
begin
|
143
|
-
while reader.next!
|
144
|
-
records << fields.zip(reader.values).to_hash
|
145
|
-
end
|
146
|
-
ensure
|
147
|
-
reader.close
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
records
|
152
|
-
end
|
153
|
-
|
154
|
-
# Constructs and executes UPDATE statement for given
|
155
|
-
# attributes and a query
|
156
|
-
#
|
157
|
-
# @param [Hash(Property => Object)] attributes
|
158
|
-
# hash of attribute values to set, keyed by Property
|
159
|
-
# @param [Collection] collection
|
160
|
-
# collection of records to be updated
|
161
|
-
#
|
162
|
-
# @return [Integer]
|
163
|
-
# the number of records updated
|
164
|
-
#
|
165
|
-
# @api semipublic
|
166
|
-
def update(attributes, collection)
|
167
|
-
query = collection.query
|
168
|
-
|
169
|
-
properties = []
|
170
|
-
bind_values = []
|
171
|
-
|
172
|
-
# make the order of the properties consistent
|
173
|
-
query.model.properties(name).each do |property|
|
174
|
-
next unless attributes.key?(property)
|
175
|
-
properties << property
|
176
|
-
bind_values << attributes[property]
|
177
|
-
end
|
178
|
-
|
179
|
-
statement, conditions_bind_values = update_statement(properties, query)
|
180
|
-
|
181
|
-
bind_values.concat(conditions_bind_values)
|
182
|
-
|
183
|
-
execute(statement, *bind_values).affected_rows
|
184
|
-
end
|
185
|
-
|
186
|
-
# Constructs and executes DELETE statement for given query
|
187
|
-
#
|
188
|
-
# @param [Collection] collection
|
189
|
-
# collection of records to be deleted
|
190
|
-
#
|
191
|
-
# @return [Integer]
|
192
|
-
# the number of records deleted
|
193
|
-
#
|
194
|
-
# @api semipublic
|
195
|
-
def delete(collection)
|
196
|
-
query = collection.query
|
197
|
-
statement, bind_values = delete_statement(query)
|
198
|
-
execute(statement, *bind_values).affected_rows
|
199
|
-
end
|
200
|
-
|
201
|
-
protected
|
202
|
-
|
203
|
-
# @api private
|
204
|
-
def normalized_uri
|
205
|
-
@normalized_uri ||=
|
206
|
-
begin
|
207
|
-
query = @options.except(:adapter, :user, :password, :host, :port, :path, :fragment, :scheme, :query, :username, :database)
|
208
|
-
query = nil if query.empty?
|
209
|
-
|
210
|
-
DataObjects::URI.new(
|
211
|
-
@options[:adapter],
|
212
|
-
@options[:user] || @options[:username],
|
213
|
-
@options[:password],
|
214
|
-
@options[:host],
|
215
|
-
@options[:port],
|
216
|
-
@options[:path] || @options[:database],
|
217
|
-
query,
|
218
|
-
@options[:fragment]
|
219
|
-
).freeze
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
chainable do
|
224
|
-
protected
|
225
|
-
|
226
|
-
# Instantiates new connection object
|
227
|
-
#
|
228
|
-
# @api semipublic
|
229
|
-
def open_connection
|
230
|
-
# DataObjects::Connection.new(uri) will give you back the right
|
231
|
-
# driver based on the DataObjects::URI#scheme
|
232
|
-
connection_stack = self.connection_stack
|
233
|
-
connection = connection_stack.last || DataObjects::Connection.new(normalized_uri)
|
234
|
-
connection_stack << connection
|
235
|
-
connection
|
236
|
-
end
|
237
|
-
|
238
|
-
# Takes connection and closes it
|
239
|
-
#
|
240
|
-
# @api semipublic
|
241
|
-
def close_connection(connection)
|
242
|
-
connection_stack = self.connection_stack
|
243
|
-
connection_stack.pop
|
244
|
-
connection.close if connection_stack.empty?
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# @api private
|
249
|
-
def connection_stack
|
250
|
-
connection_stack_for = Thread.current[:dm_do_connection_stack] ||= {}
|
251
|
-
connection_stack_for[self] ||= []
|
252
|
-
end
|
253
|
-
|
254
|
-
private
|
255
|
-
|
256
|
-
# @api public
|
257
|
-
def initialize(name, uri_or_options)
|
258
|
-
super
|
259
|
-
|
260
|
-
# Default the driver-specific logger to DataMapper's logger
|
261
|
-
if driver_module = DataObjects.const_get(normalized_uri.scheme.capitalize)
|
262
|
-
driver_module.logger = DataMapper.logger if driver_module.respond_to?(:logger=)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# @api private
|
267
|
-
def with_connection
|
268
|
-
begin
|
269
|
-
yield connection = open_connection
|
270
|
-
rescue Exception => exception
|
271
|
-
DataMapper.logger.error(exception.to_s)
|
272
|
-
raise exception
|
273
|
-
ensure
|
274
|
-
close_connection(connection) if connection
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
# @api private
|
279
|
-
def select_fields(reader, fields)
|
280
|
-
fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
|
281
|
-
struct = Struct.new(*fields)
|
282
|
-
|
283
|
-
results = []
|
284
|
-
|
285
|
-
while reader.next!
|
286
|
-
results << struct.new(*reader.values)
|
287
|
-
end
|
288
|
-
|
289
|
-
results
|
290
|
-
end
|
291
|
-
|
292
|
-
# @api private
|
293
|
-
def select_field(reader)
|
294
|
-
results = []
|
295
|
-
|
296
|
-
while reader.next!
|
297
|
-
results << reader.values.at(0)
|
298
|
-
end
|
299
|
-
|
300
|
-
results
|
301
|
-
end
|
302
|
-
|
303
|
-
# This module is just for organization. The methods are included into the
|
304
|
-
# Adapter below.
|
305
|
-
module SQL #:nodoc:
|
306
|
-
IDENTIFIER_MAX_LENGTH = 128
|
307
|
-
|
308
|
-
# @api semipublic
|
309
|
-
def property_to_column_name(property, qualify, table_name = nil)
|
310
|
-
column_name = quote_name(property.field)
|
311
|
-
|
312
|
-
if qualify
|
313
|
-
table_name ||= quote_name(property.model.storage_name(name))
|
314
|
-
"#{table_name}.#{column_name}"
|
315
|
-
else
|
316
|
-
column_name
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
private
|
321
|
-
|
322
|
-
# Adapters requiring a RETURNING syntax for INSERT statements
|
323
|
-
# should overwrite this to return true.
|
324
|
-
#
|
325
|
-
# @api private
|
326
|
-
def supports_returning?
|
327
|
-
false
|
328
|
-
end
|
329
|
-
|
330
|
-
# Adapters that do not support the DEFAULT VALUES syntax for
|
331
|
-
# INSERT statements should overwrite this to return false.
|
332
|
-
#
|
333
|
-
# @api private
|
334
|
-
def supports_default_values?
|
335
|
-
true
|
336
|
-
end
|
337
|
-
|
338
|
-
# Constructs SELECT statement for given query,
|
339
|
-
#
|
340
|
-
# @return [String] SELECT statement as a string
|
341
|
-
#
|
342
|
-
# @api private
|
343
|
-
def select_statement(query)
|
344
|
-
qualify = query.links.any?
|
345
|
-
fields = query.fields
|
346
|
-
order_by = query.order
|
347
|
-
group_by = if query.unique?
|
348
|
-
fields.select { |property| property.kind_of?(Property) }
|
349
|
-
end
|
350
|
-
|
351
|
-
conditions_statement, bind_values = conditions_statement(query.conditions, qualify)
|
352
|
-
|
353
|
-
statement = "SELECT #{columns_statement(fields, qualify)}"
|
354
|
-
statement << " FROM #{quote_name(query.model.storage_name(name))}"
|
355
|
-
statement << join_statement(query, qualify) if qualify
|
356
|
-
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
357
|
-
statement << " GROUP BY #{columns_statement(group_by, qualify)}" if group_by && group_by.any?
|
358
|
-
statement << " ORDER BY #{order_statement(order_by, qualify)}" if order_by && order_by.any?
|
359
|
-
|
360
|
-
add_limit_offset!(statement, query.limit, query.offset, bind_values)
|
361
|
-
|
362
|
-
return statement, bind_values
|
363
|
-
end
|
364
|
-
|
365
|
-
# default construction of LIMIT and OFFSET
|
366
|
-
# overriden by some adapters (currently Oracle and SQL Server)
|
367
|
-
def add_limit_offset!(statement, limit, offset, bind_values)
|
368
|
-
if limit
|
369
|
-
statement << ' LIMIT ?'
|
370
|
-
bind_values << limit
|
371
|
-
end
|
372
|
-
|
373
|
-
if limit && offset > 0
|
374
|
-
statement << ' OFFSET ?'
|
375
|
-
bind_values << offset
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
# Constructs INSERT statement for given query,
|
380
|
-
#
|
381
|
-
# @return [String] INSERT statement as a string
|
382
|
-
#
|
383
|
-
# @api private
|
384
|
-
def insert_statement(model, properties, serial)
|
385
|
-
statement = "INSERT INTO #{quote_name(model.storage_name(name))} "
|
386
|
-
|
387
|
-
if supports_default_values? && properties.empty?
|
388
|
-
statement << default_values_clause
|
389
|
-
else
|
390
|
-
statement << <<-SQL.compress_lines
|
391
|
-
(#{properties.map { |property| quote_name(property.field) }.join(', ')})
|
392
|
-
VALUES
|
393
|
-
(#{(['?'] * properties.size).join(', ')})
|
394
|
-
SQL
|
395
|
-
end
|
396
|
-
|
397
|
-
if supports_returning? && serial
|
398
|
-
statement << returning_clause(serial)
|
399
|
-
end
|
400
|
-
|
401
|
-
statement
|
402
|
-
end
|
403
|
-
|
404
|
-
# by default PostgreSQL syntax
|
405
|
-
# overrided in Oracle adapter
|
406
|
-
def default_values_clause
|
407
|
-
'DEFAULT VALUES'
|
408
|
-
end
|
409
|
-
|
410
|
-
# by default PostgreSQL syntax
|
411
|
-
# overrided in Oracle adapter
|
412
|
-
def returning_clause(serial)
|
413
|
-
" RETURNING #{quote_name(serial.field)}"
|
414
|
-
end
|
415
|
-
|
416
|
-
# Constructs UPDATE statement for given query,
|
417
|
-
#
|
418
|
-
# @return [String] UPDATE statement as a string
|
419
|
-
#
|
420
|
-
# @api private
|
421
|
-
def update_statement(properties, query)
|
422
|
-
model = query.model
|
423
|
-
name = self.name
|
424
|
-
|
425
|
-
# TODO: DRY this up with delete_statement
|
426
|
-
conditions_statement, bind_values = if query.limit || query.links.any?
|
427
|
-
subquery(query, model.key(name), false)
|
428
|
-
else
|
429
|
-
conditions_statement(query.conditions)
|
430
|
-
end
|
431
|
-
|
432
|
-
statement = "UPDATE #{quote_name(model.storage_name(name))}"
|
433
|
-
statement << " SET #{properties.map { |property| "#{quote_name(property.field)} = ?" }.join(', ')}"
|
434
|
-
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
435
|
-
|
436
|
-
return statement, bind_values
|
437
|
-
end
|
438
|
-
|
439
|
-
# Constructs DELETE statement for given query,
|
440
|
-
#
|
441
|
-
# @return [String] DELETE statement as a string
|
442
|
-
#
|
443
|
-
# @api private
|
444
|
-
def delete_statement(query)
|
445
|
-
model = query.model
|
446
|
-
name = self.name
|
447
|
-
|
448
|
-
# TODO: DRY this up with update_statement
|
449
|
-
conditions_statement, bind_values = if query.limit || query.links.any?
|
450
|
-
subquery(query, model.key(name), false)
|
451
|
-
else
|
452
|
-
conditions_statement(query.conditions)
|
453
|
-
end
|
454
|
-
|
455
|
-
statement = "DELETE FROM #{quote_name(model.storage_name(name))}"
|
456
|
-
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
457
|
-
|
458
|
-
return statement, bind_values
|
459
|
-
end
|
460
|
-
|
461
|
-
# Constructs comma separated list of fields
|
462
|
-
#
|
463
|
-
# @return [String]
|
464
|
-
# list of fields as a string
|
465
|
-
#
|
466
|
-
# @api private
|
467
|
-
def columns_statement(properties, qualify, qualifier = nil)
|
468
|
-
properties.map { |property| property_to_column_name(property, qualify, qualifier) }.join(', ')
|
469
|
-
end
|
470
|
-
|
471
|
-
# Constructs joins clause
|
472
|
-
#
|
473
|
-
# @return [String]
|
474
|
-
# joins clause
|
475
|
-
#
|
476
|
-
# @api private
|
477
|
-
def join_statement(query, qualify)
|
478
|
-
statement = ''
|
479
|
-
|
480
|
-
query.links.reverse_each do |relationship|
|
481
|
-
statement << " INNER JOIN #{quote_name(relationship.source_model.storage_name(name))} ON "
|
482
|
-
statement << relationship.target_key.zip(relationship.source_key).map do |target_property, source_property|
|
483
|
-
"#{property_to_column_name(target_property, qualify)} = #{property_to_column_name(source_property, qualify)}"
|
484
|
-
end.join(' AND ')
|
485
|
-
end
|
486
|
-
|
487
|
-
statement
|
488
|
-
end
|
489
|
-
|
490
|
-
# Constructs where clause
|
491
|
-
#
|
492
|
-
# @return [String]
|
493
|
-
# where clause
|
494
|
-
#
|
495
|
-
# @api private
|
496
|
-
def conditions_statement(conditions, qualify = false)
|
497
|
-
case conditions
|
498
|
-
when Query::Conditions::NotOperation then negate_operation(conditions.operand, qualify)
|
499
|
-
when Query::Conditions::AbstractOperation then operation_statement(conditions, qualify)
|
500
|
-
when Query::Conditions::AbstractComparison then comparison_statement(conditions, qualify)
|
501
|
-
when Array
|
502
|
-
statement, bind_values = conditions # handle raw conditions
|
503
|
-
[ "(#{statement})", bind_values ].compact
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
# @api private
|
508
|
-
def supports_subquery?(*)
|
509
|
-
true
|
510
|
-
end
|
511
|
-
|
512
|
-
# @api private
|
513
|
-
def subquery(query, subject, qualify)
|
514
|
-
source_key, target_key = subquery_keys(subject)
|
515
|
-
|
516
|
-
if query.repository.name == name && supports_subquery?(query, source_key, target_key, qualify)
|
517
|
-
subquery_statement(query, source_key, target_key, qualify)
|
518
|
-
else
|
519
|
-
subquery_execute(query, source_key, target_key, qualify)
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
# @api private
|
524
|
-
def subquery_statement(query, source_key, target_key, qualify)
|
525
|
-
query = subquery_query(query, source_key)
|
526
|
-
select_statement, bind_values = select_statement(query)
|
527
|
-
|
528
|
-
statement = if target_key.size == 1
|
529
|
-
property_to_column_name(target_key.first, qualify)
|
530
|
-
else
|
531
|
-
"(#{target_key.map { |property| property_to_column_name(property, qualify) }.join(', ')})"
|
532
|
-
end
|
533
|
-
|
534
|
-
statement << " IN (#{select_statement})"
|
535
|
-
|
536
|
-
return statement, bind_values
|
537
|
-
end
|
538
|
-
|
539
|
-
# @api private
|
540
|
-
def subquery_execute(query, source_key, target_key, qualify)
|
541
|
-
query = subquery_query(query, source_key)
|
542
|
-
sources = query.model.all(query)
|
543
|
-
conditions = Query.target_conditions(sources, source_key, target_key)
|
544
|
-
|
545
|
-
if conditions.valid?
|
546
|
-
conditions_statement(conditions, qualify)
|
547
|
-
else
|
548
|
-
[ '1 = 0', [] ]
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
# @api private
|
553
|
-
def subquery_keys(subject)
|
554
|
-
case subject
|
555
|
-
when Associations::Relationship
|
556
|
-
relationship = subject.inverse
|
557
|
-
[ relationship.source_key, relationship.target_key ]
|
558
|
-
when PropertySet
|
559
|
-
[ subject, subject ]
|
560
|
-
end
|
561
|
-
end
|
562
|
-
|
563
|
-
# @api private
|
564
|
-
def subquery_query(query, source_key)
|
565
|
-
# force unique to be false because PostgreSQL has a problem with
|
566
|
-
# subselects that contain a GROUP BY with different columns
|
567
|
-
# than the outer-most query
|
568
|
-
query = query.merge(:fields => source_key, :unique => false)
|
569
|
-
query.update(:order => nil) unless query.limit
|
570
|
-
query
|
571
|
-
end
|
572
|
-
|
573
|
-
# Constructs order clause
|
574
|
-
#
|
575
|
-
# @return [String]
|
576
|
-
# order clause
|
577
|
-
#
|
578
|
-
# @api private
|
579
|
-
def order_statement(order, qualify, qualifier = nil)
|
580
|
-
statements = order.map do |direction|
|
581
|
-
statement = property_to_column_name(direction.target, qualify, qualifier)
|
582
|
-
statement << ' DESC' if direction.operator == :desc
|
583
|
-
statement
|
584
|
-
end
|
585
|
-
|
586
|
-
statements.join(', ')
|
587
|
-
end
|
588
|
-
|
589
|
-
# @api private
|
590
|
-
def negate_operation(operand, qualify)
|
591
|
-
statement, bind_values = conditions_statement(operand, qualify)
|
592
|
-
statement = "NOT(#{statement})" unless statement.nil?
|
593
|
-
[ statement, bind_values ]
|
594
|
-
end
|
595
|
-
|
596
|
-
# @api private
|
597
|
-
def operation_statement(operation, qualify)
|
598
|
-
statements = []
|
599
|
-
bind_values = []
|
600
|
-
|
601
|
-
operation.each do |operand|
|
602
|
-
statement, values = conditions_statement(operand, qualify)
|
603
|
-
next unless statement && values
|
604
|
-
statements << statement
|
605
|
-
bind_values.concat(values)
|
606
|
-
end
|
607
|
-
|
608
|
-
statement = statements.join(" #{operation.slug.to_s.upcase} ")
|
609
|
-
|
610
|
-
if statements.size > 1
|
611
|
-
statement = "(#{statement})"
|
612
|
-
end
|
613
|
-
|
614
|
-
return statement, bind_values
|
615
|
-
end
|
616
|
-
|
617
|
-
# Constructs comparison clause
|
618
|
-
#
|
619
|
-
# @return [String]
|
620
|
-
# comparison clause
|
621
|
-
#
|
622
|
-
# @api private
|
623
|
-
def comparison_statement(comparison, qualify)
|
624
|
-
subject = comparison.subject
|
625
|
-
value = comparison.value
|
626
|
-
|
627
|
-
# TODO: move exclusive Range handling into another method, and
|
628
|
-
# update conditions_statement to use it
|
629
|
-
|
630
|
-
# break exclusive Range queries up into two comparisons ANDed together
|
631
|
-
if value.kind_of?(Range) && value.exclude_end?
|
632
|
-
operation = Query::Conditions::Operation.new(:and,
|
633
|
-
Query::Conditions::Comparison.new(:gte, subject, value.first),
|
634
|
-
Query::Conditions::Comparison.new(:lt, subject, value.last)
|
635
|
-
)
|
636
|
-
|
637
|
-
statement, bind_values = conditions_statement(operation, qualify)
|
638
|
-
|
639
|
-
return "(#{statement})", bind_values
|
640
|
-
elsif comparison.relationship?
|
641
|
-
if value.respond_to?(:query) && value.respond_to?(:loaded?) && !value.loaded?
|
642
|
-
return subquery(value.query, subject, qualify)
|
643
|
-
else
|
644
|
-
return conditions_statement(comparison.foreign_key_mapping, qualify)
|
645
|
-
end
|
646
|
-
elsif comparison.slug == :in && value.empty?
|
647
|
-
return [] # match everything
|
648
|
-
end
|
649
|
-
|
650
|
-
operator = comparison_operator(comparison)
|
651
|
-
column_name = property_to_column_name(subject, qualify)
|
652
|
-
|
653
|
-
# if operator return value contains ? then it means that it is function call
|
654
|
-
# and it contains placeholder (%s) for property name as well (used in Oracle adapter for regexp operator)
|
655
|
-
if operator.include?('?')
|
656
|
-
return operator % column_name, [ value ]
|
657
|
-
else
|
658
|
-
return "#{column_name} #{operator} #{value.nil? ? 'NULL' : '?'}", [ value ].compact
|
659
|
-
end
|
660
|
-
end
|
661
|
-
|
662
|
-
def comparison_operator(comparison)
|
663
|
-
subject = comparison.subject
|
664
|
-
value = comparison.value
|
665
|
-
|
666
|
-
case comparison.slug
|
667
|
-
when :eql then equality_operator(subject, value)
|
668
|
-
when :in then include_operator(subject, value)
|
669
|
-
when :regexp then regexp_operator(value)
|
670
|
-
when :like then like_operator(value)
|
671
|
-
when :gt then '>'
|
672
|
-
when :lt then '<'
|
673
|
-
when :gte then '>='
|
674
|
-
when :lte then '<='
|
675
|
-
end
|
676
|
-
end
|
677
|
-
|
678
|
-
# @api private
|
679
|
-
def equality_operator(property, operand)
|
680
|
-
operand.nil? ? 'IS' : '='
|
681
|
-
end
|
682
|
-
|
683
|
-
# @api private
|
684
|
-
def include_operator(property, operand)
|
685
|
-
case operand
|
686
|
-
when Array then 'IN'
|
687
|
-
when Range then 'BETWEEN'
|
688
|
-
end
|
689
|
-
end
|
690
|
-
|
691
|
-
# @api private
|
692
|
-
def regexp_operator(operand)
|
693
|
-
'~'
|
694
|
-
end
|
695
|
-
|
696
|
-
# @api private
|
697
|
-
def like_operator(operand)
|
698
|
-
'LIKE'
|
699
|
-
end
|
700
|
-
|
701
|
-
# @api private
|
702
|
-
def quote_name(name)
|
703
|
-
"\"#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '""')}\""
|
704
|
-
end
|
705
|
-
end #module SQL
|
706
|
-
|
707
|
-
include SQL
|
708
|
-
end # class DataObjectsAdapter
|
709
|
-
|
710
|
-
const_added(:DataObjectsAdapter)
|
711
|
-
end # module Adapters
|
712
|
-
end # module DataMapper
|