datamapper-dm-core 0.9.11 → 0.10.0
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/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -1,91 +1,161 @@
|
|
|
1
|
-
gem 'data_objects', '~>0.9.12'
|
|
2
1
|
require 'data_objects'
|
|
3
2
|
|
|
4
3
|
module DataMapper
|
|
5
4
|
module Adapters
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
8
|
#
|
|
9
|
-
#
|
|
9
|
+
# By inheriting from DataObjectsAdapter, you get a copy of all the
|
|
10
10
|
# standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
|
|
11
11
|
# You can extend and overwrite these copies without affecting the originals.
|
|
12
12
|
class DataObjectsAdapter < AbstractAdapter
|
|
13
|
+
extend Chainable
|
|
14
|
+
|
|
15
|
+
# For each model instance in resources, issues an SQL INSERT
|
|
16
|
+
# (or equivalent) statement to create a new record in the data store for
|
|
17
|
+
# the instance
|
|
18
|
+
#
|
|
19
|
+
# Note that this method does not update identity map. A plugin needs to use
|
|
20
|
+
# adapter directly, it is up to plugin developer to keep identity map
|
|
21
|
+
# up to date.
|
|
22
|
+
#
|
|
23
|
+
# @param [Enumerable(Resource)] resources
|
|
24
|
+
# The list of resources (model instances) to create
|
|
25
|
+
#
|
|
26
|
+
# @return [Integer]
|
|
27
|
+
# The number of records that were actually saved into the database
|
|
28
|
+
#
|
|
29
|
+
# @api semipublic
|
|
13
30
|
def create(resources)
|
|
14
|
-
created = 0
|
|
15
31
|
resources.each do |resource|
|
|
16
|
-
repository = resource.repository
|
|
17
32
|
model = resource.model
|
|
33
|
+
serial = model.serial(name)
|
|
18
34
|
attributes = resource.dirty_attributes
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
properties = []
|
|
37
|
+
bind_values = []
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
# make the order of the properties consistent
|
|
40
|
+
model.properties(name).each do |property|
|
|
41
|
+
next unless attributes.key?(property)
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
bind_value = attributes[property]
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
if
|
|
30
|
-
|
|
45
|
+
# skip insering NULL for columns that are serial or without a default
|
|
46
|
+
next if bind_value.nil? && (property.serial? || !property.default?)
|
|
47
|
+
|
|
48
|
+
# if serial is being set explicitly, do not set it again
|
|
49
|
+
if property.equal?(serial)
|
|
50
|
+
serial = nil
|
|
31
51
|
end
|
|
32
|
-
created += 1
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
created
|
|
36
|
-
end
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
command = connection.create_command(read_statement(query))
|
|
42
|
-
command.set_types(query.fields.map { |p| p.primitive })
|
|
53
|
+
properties << property
|
|
54
|
+
bind_values << bind_value
|
|
55
|
+
end
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
v == [] ? [nil] : v
|
|
47
|
-
end
|
|
48
|
-
reader = command.execute_reader(*bind_values)
|
|
57
|
+
statement = insert_statement(model, properties, serial)
|
|
58
|
+
result = execute(statement, *bind_values)
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
53
|
-
ensure
|
|
54
|
-
reader.close if reader
|
|
55
|
-
end
|
|
60
|
+
if result.to_i == 1 && serial
|
|
61
|
+
serial.set!(resource, result.insert_id)
|
|
56
62
|
end
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
# Constructs and executes SELECT query, then instantiates
|
|
67
|
+
# one or many object from result set.
|
|
68
|
+
#
|
|
69
|
+
# @param [Query] query
|
|
70
|
+
# composition of the query to perform
|
|
71
|
+
#
|
|
72
|
+
# @return [Array]
|
|
73
|
+
# result set of the query
|
|
74
|
+
#
|
|
75
|
+
# @api semipublic
|
|
76
|
+
def read(query)
|
|
77
|
+
fields = query.fields
|
|
78
|
+
types = fields.map { |property| property.primitive }
|
|
79
|
+
|
|
80
|
+
statement, bind_values = select_statement(query)
|
|
81
|
+
|
|
82
|
+
records = []
|
|
83
|
+
|
|
61
84
|
with_connection do |connection|
|
|
62
|
-
command = connection.create_command(
|
|
63
|
-
command.set_types(
|
|
85
|
+
command = connection.create_command(statement)
|
|
86
|
+
command.set_types(types)
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
reader = command.execute_reader(*query.bind_values)
|
|
88
|
+
reader = command.execute_reader(*bind_values)
|
|
67
89
|
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
begin
|
|
91
|
+
while reader.next!
|
|
92
|
+
records << fields.zip(reader.values).to_hash
|
|
70
93
|
end
|
|
71
94
|
ensure
|
|
72
|
-
reader.close
|
|
95
|
+
reader.close
|
|
73
96
|
end
|
|
74
97
|
end
|
|
98
|
+
|
|
99
|
+
records
|
|
75
100
|
end
|
|
76
101
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
# Constructs and executes UPDATE statement for given
|
|
103
|
+
# attributes and a query
|
|
104
|
+
#
|
|
105
|
+
# @param [Hash(Property => Object)] attributes
|
|
106
|
+
# hash of attribute values to set, keyed by Property
|
|
107
|
+
# @param [Collection] collection
|
|
108
|
+
# collection of records to be updated
|
|
109
|
+
#
|
|
110
|
+
# @return [Integer]
|
|
111
|
+
# the number of records updated
|
|
112
|
+
#
|
|
113
|
+
# @api semipublic
|
|
114
|
+
def update(attributes, collection)
|
|
115
|
+
query = collection.query
|
|
116
|
+
|
|
117
|
+
# TODO: if the query contains any links, a limit or an offset
|
|
118
|
+
# use a subselect to get the rows to be updated
|
|
119
|
+
|
|
120
|
+
properties = []
|
|
121
|
+
bind_values = []
|
|
122
|
+
|
|
123
|
+
# make the order of the properties consistent
|
|
124
|
+
query.model.properties(name).each do |property|
|
|
125
|
+
next unless attributes.key?(property)
|
|
126
|
+
properties << property
|
|
127
|
+
bind_values << attributes[property]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
statement, conditions_bind_values = update_statement(properties, query)
|
|
131
|
+
|
|
132
|
+
bind_values.concat(conditions_bind_values)
|
|
133
|
+
|
|
80
134
|
execute(statement, *bind_values).to_i
|
|
81
135
|
end
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
137
|
+
# Constructs and executes DELETE statement for given query
|
|
138
|
+
#
|
|
139
|
+
# @param [Collection] collection
|
|
140
|
+
# collection of records to be deleted
|
|
141
|
+
#
|
|
142
|
+
# @return [Integer]
|
|
143
|
+
# the number of records deleted
|
|
144
|
+
#
|
|
145
|
+
# @api semipublic
|
|
146
|
+
def delete(collection)
|
|
147
|
+
query = collection.query
|
|
148
|
+
|
|
149
|
+
# TODO: if the query contains any links, a limit or an offset
|
|
150
|
+
# use a subselect to get the rows to be deleted
|
|
151
|
+
|
|
152
|
+
statement, bind_values = delete_statement(query)
|
|
153
|
+
execute(statement, *bind_values).to_i
|
|
86
154
|
end
|
|
87
155
|
|
|
88
156
|
# Database-specific method
|
|
157
|
+
# TODO: document
|
|
158
|
+
# @api public
|
|
89
159
|
def execute(statement, *bind_values)
|
|
90
160
|
with_connection do |connection|
|
|
91
161
|
command = connection.create_command(statement)
|
|
@@ -93,21 +163,30 @@ module DataMapper
|
|
|
93
163
|
end
|
|
94
164
|
end
|
|
95
165
|
|
|
166
|
+
# TODO: document
|
|
167
|
+
# @api public
|
|
96
168
|
def query(statement, *bind_values)
|
|
97
|
-
|
|
169
|
+
with_connection do |connection|
|
|
170
|
+
reader = connection.create_command(statement).execute_reader(*bind_values)
|
|
171
|
+
fields = reader.fields
|
|
172
|
+
|
|
98
173
|
results = []
|
|
99
174
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
175
|
+
begin
|
|
176
|
+
if fields.size > 1
|
|
177
|
+
fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
|
|
178
|
+
struct = Struct.new(*fields)
|
|
103
179
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
180
|
+
while reader.next!
|
|
181
|
+
results << struct.new(*reader.values)
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
while reader.next!
|
|
185
|
+
results << reader.values.at(0)
|
|
186
|
+
end
|
|
110
187
|
end
|
|
188
|
+
ensure
|
|
189
|
+
reader.close
|
|
111
190
|
end
|
|
112
191
|
|
|
113
192
|
results
|
|
@@ -116,601 +195,470 @@ module DataMapper
|
|
|
116
195
|
|
|
117
196
|
protected
|
|
118
197
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
:query => query
|
|
139
|
-
))
|
|
198
|
+
# TODO: document
|
|
199
|
+
# @api private
|
|
200
|
+
def normalized_uri
|
|
201
|
+
@normalized_uri ||=
|
|
202
|
+
begin
|
|
203
|
+
query = @options.except(:adapter, :user, :password, :host, :port, :path, :fragment, :scheme, :query, :username, :database)
|
|
204
|
+
query = nil if query.empty?
|
|
205
|
+
|
|
206
|
+
DataObjects::URI.new(
|
|
207
|
+
@options[:adapter],
|
|
208
|
+
@options[:user] || @options[:username],
|
|
209
|
+
@options[:password],
|
|
210
|
+
@options[:host],
|
|
211
|
+
@options[:port],
|
|
212
|
+
@options[:path] || @options[:database],
|
|
213
|
+
query,
|
|
214
|
+
@options[:fragment]
|
|
215
|
+
).freeze
|
|
216
|
+
end
|
|
140
217
|
end
|
|
141
218
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
219
|
+
chainable do
|
|
220
|
+
protected
|
|
221
|
+
|
|
222
|
+
# Instantiates new connection object
|
|
223
|
+
#
|
|
224
|
+
# @api semipublic
|
|
225
|
+
def open_connection
|
|
147
226
|
# DataObjects::Connection.new(uri) will give you back the right
|
|
148
|
-
# driver based on the
|
|
149
|
-
DataObjects::Connection.new(
|
|
227
|
+
# driver based on the DataObjects::URI#scheme
|
|
228
|
+
connection = connection_stack.last || DataObjects::Connection.new(normalized_uri)
|
|
229
|
+
connection_stack << connection
|
|
230
|
+
connection
|
|
150
231
|
end
|
|
151
|
-
end
|
|
152
232
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
233
|
+
# Takes connection and closes it
|
|
234
|
+
#
|
|
235
|
+
# @api semipublic
|
|
236
|
+
def close_connection(connection)
|
|
237
|
+
connection_stack.pop
|
|
238
|
+
connection.close if connection_stack.empty?
|
|
239
|
+
end
|
|
156
240
|
end
|
|
157
241
|
|
|
158
242
|
private
|
|
159
243
|
|
|
244
|
+
# TODO: document
|
|
245
|
+
# @api public
|
|
160
246
|
def initialize(name, uri_or_options)
|
|
161
247
|
super
|
|
162
248
|
|
|
163
|
-
# Default the driver-
|
|
164
|
-
if driver_module = DataObjects.const_get(
|
|
249
|
+
# Default the driver-specific logger to DataMapper's logger
|
|
250
|
+
if driver_module = DataObjects.const_get(normalized_uri.scheme.capitalize)
|
|
165
251
|
driver_module.logger = DataMapper.logger if driver_module.respond_to?(:logger=)
|
|
166
252
|
end
|
|
167
253
|
end
|
|
168
254
|
|
|
255
|
+
# TODO: document
|
|
256
|
+
# @api private
|
|
257
|
+
def connection_stack
|
|
258
|
+
connection_stack_for = Thread.current[:dm_do_connection_stack] ||= {}
|
|
259
|
+
connection_stack_for[self] ||= []
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# TODO: document
|
|
263
|
+
# @api private
|
|
169
264
|
def with_connection
|
|
170
|
-
connection = nil
|
|
171
265
|
begin
|
|
172
|
-
connection =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
raise e
|
|
266
|
+
yield connection = open_connection
|
|
267
|
+
rescue Exception => exception
|
|
268
|
+
DataMapper.logger.error(exception.to_s)
|
|
269
|
+
raise exception
|
|
177
270
|
ensure
|
|
178
271
|
close_connection(connection) if connection
|
|
179
272
|
end
|
|
180
273
|
end
|
|
181
274
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
275
|
+
# This module is just for organization. The methods are included into the
|
|
276
|
+
# Adapter below.
|
|
277
|
+
module SQL #:nodoc:
|
|
278
|
+
IDENTIFIER_MAX_LENGTH = 128
|
|
279
|
+
|
|
280
|
+
# TODO: document
|
|
281
|
+
# @api semipublic
|
|
282
|
+
def property_to_column_name(property, qualify)
|
|
283
|
+
if qualify
|
|
284
|
+
table_name = property.model.storage_name(name)
|
|
285
|
+
"#{quote_name(table_name)}.#{quote_name(property.field)}"
|
|
286
|
+
else
|
|
287
|
+
quote_name(property.field)
|
|
190
288
|
end
|
|
191
289
|
end
|
|
192
|
-
end
|
|
193
290
|
|
|
194
|
-
# This model is just for organization. The methods are included into the
|
|
195
|
-
# Adapter below.
|
|
196
|
-
module SQL
|
|
197
291
|
private
|
|
198
292
|
|
|
199
293
|
# Adapters requiring a RETURNING syntax for INSERT statements
|
|
200
294
|
# should overwrite this to return true.
|
|
295
|
+
#
|
|
296
|
+
# @api private
|
|
201
297
|
def supports_returning?
|
|
202
298
|
false
|
|
203
299
|
end
|
|
204
300
|
|
|
205
301
|
# Adapters that do not support the DEFAULT VALUES syntax for
|
|
206
302
|
# INSERT statements should overwrite this to return false.
|
|
303
|
+
#
|
|
304
|
+
# @api private
|
|
207
305
|
def supports_default_values?
|
|
208
306
|
true
|
|
209
307
|
end
|
|
210
308
|
|
|
211
|
-
|
|
212
|
-
|
|
309
|
+
# Constructs SELECT statement for given query,
|
|
310
|
+
#
|
|
311
|
+
# @return [String] SELECT statement as a string
|
|
312
|
+
#
|
|
313
|
+
# @api private
|
|
314
|
+
def select_statement(query)
|
|
315
|
+
model = query.model
|
|
316
|
+
fields = query.fields
|
|
317
|
+
conditions = query.conditions
|
|
318
|
+
limit = query.limit
|
|
319
|
+
offset = query.offset
|
|
320
|
+
order_by = query.order
|
|
321
|
+
group_by = nil
|
|
322
|
+
|
|
323
|
+
# FIXME: using a boolean for qualify does not work in some cases,
|
|
324
|
+
# such as when you have a self-referrential many to many association.
|
|
325
|
+
# if you don't qualfiy the columns with a unique alias, then the
|
|
326
|
+
# SQL query will fail. This may mean though, that it might not
|
|
327
|
+
# be enough to pass in a Property, but we may need to know the
|
|
328
|
+
# table and the alias we should use for the column.
|
|
213
329
|
|
|
214
|
-
|
|
215
|
-
statement << 'DEFAULT VALUES'
|
|
216
|
-
else
|
|
217
|
-
statement << <<-EOS.compress_lines
|
|
218
|
-
(#{properties.map { |p| quote_column_name(p.field(repository.name)) } * ', '})
|
|
219
|
-
VALUES
|
|
220
|
-
(#{(['?'] * properties.size) * ', '})
|
|
221
|
-
EOS
|
|
222
|
-
end
|
|
330
|
+
qualify = query.links.any?
|
|
223
331
|
|
|
224
|
-
if
|
|
225
|
-
|
|
332
|
+
if qualify || query.unique?
|
|
333
|
+
group_by = fields.select { |property| property.kind_of?(Property) }
|
|
226
334
|
end
|
|
227
335
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def read_statement(query)
|
|
232
|
-
statement = "SELECT #{fields_statement(query)}"
|
|
233
|
-
statement << " FROM #{quote_table_name(query.model.storage_name(query.repository.name))}"
|
|
234
|
-
statement << links_statement(query) if query.links.any?
|
|
235
|
-
statement << " WHERE #{conditions_statement(query)}" if query.conditions.any?
|
|
236
|
-
statement << " GROUP BY #{group_by_statement(query)}" if query.unique? && query.fields.any? { |p| p.kind_of?(Property) }
|
|
237
|
-
statement << " ORDER BY #{order_statement(query)}" if query.order.any?
|
|
238
|
-
statement << " LIMIT #{quote_column_value(query.limit)}" if query.limit
|
|
239
|
-
statement << " OFFSET #{quote_column_value(query.offset)}" if query.offset && query.offset > 0
|
|
240
|
-
statement
|
|
241
|
-
rescue => e
|
|
242
|
-
DataMapper.logger.error("QUERY INVALID: #{query.inspect} (#{e})")
|
|
243
|
-
raise e
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def update_statement(properties, query)
|
|
247
|
-
statement = "UPDATE #{quote_table_name(query.model.storage_name(query.repository.name))}"
|
|
248
|
-
statement << " SET #{set_statement(query.repository, properties)}"
|
|
249
|
-
statement << " WHERE #{conditions_statement(query)}" if query.conditions.any?
|
|
250
|
-
statement
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def set_statement(repository, properties)
|
|
254
|
-
properties.map { |p| "#{quote_column_name(p.field(repository.name))} = ?" } * ', '
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def delete_statement(query)
|
|
258
|
-
statement = "DELETE FROM #{quote_table_name(query.model.storage_name(query.repository.name))}"
|
|
259
|
-
statement << " WHERE #{conditions_statement(query)}" if query.conditions.any?
|
|
260
|
-
statement
|
|
261
|
-
end
|
|
336
|
+
unless (limit && limit > 1) || offset > 0 || qualify
|
|
337
|
+
# TODO: move this method to Query, so that it walks the conditions
|
|
338
|
+
# and finds an OR operator
|
|
262
339
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
query.fields.map { |p| property_to_column_name(query.repository, p, qualify) } * ', '
|
|
266
|
-
end
|
|
340
|
+
# TODO: handle cases where two or more properties need to be
|
|
341
|
+
# used together to be unique
|
|
267
342
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
join_table_name = if table_list.include?(parent_table_name)
|
|
277
|
-
child_table_name
|
|
278
|
-
elsif table_list.include?(child_table_name)
|
|
279
|
-
parent_table_name
|
|
280
|
-
else
|
|
281
|
-
raise ArgumentError, 'you\'re trying to join a table with no connection to this query'
|
|
343
|
+
# if a unique property is used, and there is no OR operator, then an ORDER
|
|
344
|
+
# and LIMIT are unecessary because it should only return a single row
|
|
345
|
+
if conditions.kind_of?(Query::Conditions::AndOperation) &&
|
|
346
|
+
conditions.any? { |operand| operand.kind_of?(Query::Conditions::EqualToComparison) && operand.subject.respond_to?(:unique?) && operand.subject.unique? } &&
|
|
347
|
+
!conditions.any? { |operand| operand.kind_of?(Query::Conditions::OrOperation) }
|
|
348
|
+
order_by = nil
|
|
349
|
+
limit = nil
|
|
282
350
|
end
|
|
283
|
-
table_list << join_table_name
|
|
284
|
-
|
|
285
|
-
# We only do INNER JOIN for now
|
|
286
|
-
statement << " INNER JOIN #{quote_table_name(join_table_name)} ON "
|
|
287
|
-
|
|
288
|
-
statement << relationship.parent_key.zip(relationship.child_key).map do |parent_property,child_property|
|
|
289
|
-
condition_statement(query, :eql, parent_property, child_property)
|
|
290
|
-
end * ' AND '
|
|
291
351
|
end
|
|
292
352
|
|
|
293
|
-
|
|
294
|
-
end
|
|
353
|
+
conditions_statement, bind_values = conditions_statement(conditions, qualify)
|
|
295
354
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
355
|
+
statement = "SELECT #{columns_statement(fields, qualify)}"
|
|
356
|
+
statement << " FROM #{quote_name(model.storage_name(name))}"
|
|
357
|
+
statement << join_statement(query, qualify) if qualify
|
|
358
|
+
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
|
359
|
+
statement << " GROUP BY #{columns_statement(group_by, qualify)}" if group_by && group_by.any?
|
|
360
|
+
statement << " ORDER BY #{order_statement(order_by, qualify)}" if order_by && order_by.any?
|
|
299
361
|
|
|
300
|
-
|
|
301
|
-
repository = query.repository
|
|
302
|
-
qualify = query.links.any?
|
|
303
|
-
query.fields.select { |p| p.kind_of?(Property) }.map { |p| property_to_column_name(repository, p, qualify) } * ', '
|
|
304
|
-
end
|
|
362
|
+
add_limit_offset!(statement, limit, offset, bind_values)
|
|
305
363
|
|
|
306
|
-
|
|
307
|
-
repository = query.repository
|
|
308
|
-
qualify = query.links.any?
|
|
309
|
-
query.order.map { |i| order_column(repository, i, qualify) } * ', '
|
|
364
|
+
return statement, bind_values
|
|
310
365
|
end
|
|
311
366
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
when Query::Direction
|
|
319
|
-
property = item.property
|
|
320
|
-
descending = true if item.direction == :desc
|
|
367
|
+
# default construction of LIMIT and OFFSET
|
|
368
|
+
# overriden in Oracle adapter
|
|
369
|
+
def add_limit_offset!(statement, limit, offset, bind_values)
|
|
370
|
+
if limit
|
|
371
|
+
statement << ' LIMIT ?'
|
|
372
|
+
bind_values << limit
|
|
321
373
|
end
|
|
322
374
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
375
|
+
if limit && offset > 0
|
|
376
|
+
statement << ' OFFSET ?'
|
|
377
|
+
bind_values << offset
|
|
378
|
+
end
|
|
326
379
|
end
|
|
327
380
|
|
|
328
|
-
|
|
329
|
-
|
|
381
|
+
# Constructs INSERT statement for given query,
|
|
382
|
+
#
|
|
383
|
+
# @return [String] INSERT statement as a string
|
|
384
|
+
#
|
|
385
|
+
# @api private
|
|
386
|
+
def insert_statement(model, properties, serial)
|
|
387
|
+
statement = "INSERT INTO #{quote_name(model.storage_name(name))} "
|
|
330
388
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"(#{read_statement(condition)})"
|
|
340
|
-
|
|
341
|
-
# [].all? is always true
|
|
342
|
-
elsif condition.kind_of?(Array) && condition.any? && condition.all? { |p| p.kind_of?(Property) }
|
|
343
|
-
property_values = condition.map { |p| property_to_column_name(query.repository, p, qualify) }
|
|
344
|
-
"(#{property_values * ', '})"
|
|
345
|
-
else
|
|
346
|
-
'?'
|
|
347
|
-
end
|
|
389
|
+
if supports_default_values? && properties.empty?
|
|
390
|
+
statement << default_values_clause
|
|
391
|
+
else
|
|
392
|
+
statement << <<-SQL.compress_lines
|
|
393
|
+
(#{properties.map { |property| quote_name(property.field) }.join(', ')})
|
|
394
|
+
VALUES
|
|
395
|
+
(#{(['?'] * properties.size).join(', ')})
|
|
396
|
+
SQL
|
|
348
397
|
end
|
|
349
398
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
when :not then inequality_operator(right_condition)
|
|
353
|
-
when :like then 'LIKE'
|
|
354
|
-
when :gt then '>'
|
|
355
|
-
when :gte then '>='
|
|
356
|
-
when :lt then '<'
|
|
357
|
-
when :lte then '<='
|
|
358
|
-
else raise "Invalid query operator: #{operator.inspect}"
|
|
399
|
+
if supports_returning? && serial
|
|
400
|
+
statement << returning_clause(serial)
|
|
359
401
|
end
|
|
360
402
|
|
|
361
|
-
|
|
403
|
+
statement
|
|
362
404
|
end
|
|
363
405
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
when NilClass then 'IS'
|
|
369
|
-
else '='
|
|
370
|
-
end
|
|
406
|
+
# by default PostgreSQL syntax
|
|
407
|
+
# overrided in Oracle adapter
|
|
408
|
+
def default_values_clause
|
|
409
|
+
'DEFAULT VALUES'
|
|
371
410
|
end
|
|
372
411
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
when NilClass then 'IS NOT'
|
|
378
|
-
else '<>'
|
|
379
|
-
end
|
|
412
|
+
# by default PostgreSQL syntax
|
|
413
|
+
# overrided in Oracle adapter
|
|
414
|
+
def returning_clause(serial)
|
|
415
|
+
" RETURNING #{quote_name(serial.field)}"
|
|
380
416
|
end
|
|
381
417
|
|
|
382
|
-
|
|
383
|
-
|
|
418
|
+
# Constructs UPDATE statement for given query,
|
|
419
|
+
#
|
|
420
|
+
# @return [String] UPDATE statement as a string
|
|
421
|
+
#
|
|
422
|
+
# @api private
|
|
423
|
+
def update_statement(properties, query)
|
|
424
|
+
conditions_statement, bind_values = conditions_statement(query.conditions)
|
|
384
425
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
quote_column_name(property.field(repository.name))
|
|
389
|
-
end
|
|
390
|
-
end
|
|
426
|
+
statement = "UPDATE #{quote_name(query.model.storage_name(name))}"
|
|
427
|
+
statement << " SET #{properties.map { |property| "#{quote_name(property.field)} = ?" }.join(', ')}"
|
|
428
|
+
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
|
391
429
|
|
|
392
|
-
|
|
393
|
-
# this method delegate to them instead
|
|
394
|
-
def quote_table_name(table_name)
|
|
395
|
-
table_name.gsub('"', '""').split('.').map { |part| "\"#{part}\"" } * '.'
|
|
430
|
+
return statement, bind_values
|
|
396
431
|
end
|
|
397
432
|
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
433
|
+
# Constructs DELETE statement for given query,
|
|
434
|
+
#
|
|
435
|
+
# @return [String] DELETE statement as a string
|
|
436
|
+
#
|
|
437
|
+
# @api private
|
|
438
|
+
def delete_statement(query)
|
|
439
|
+
conditions_statement, bind_values = conditions_statement(query.conditions)
|
|
403
440
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
def quote_column_value(column_value)
|
|
407
|
-
return 'NULL' if column_value.nil?
|
|
441
|
+
statement = "DELETE FROM #{quote_name(query.model.storage_name(name))}"
|
|
442
|
+
statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
|
408
443
|
|
|
409
|
-
|
|
410
|
-
when String
|
|
411
|
-
if (integer = column_value.to_i).to_s == column_value
|
|
412
|
-
quote_column_value(integer)
|
|
413
|
-
elsif (float = column_value.to_f).to_s == column_value
|
|
414
|
-
quote_column_value(integer)
|
|
415
|
-
else
|
|
416
|
-
"'#{column_value.gsub("'", "''")}'"
|
|
417
|
-
end
|
|
418
|
-
when DateTime
|
|
419
|
-
quote_column_value(column_value.strftime('%Y-%m-%d %H:%M:%S'))
|
|
420
|
-
when Date
|
|
421
|
-
quote_column_value(column_value.strftime('%Y-%m-%d'))
|
|
422
|
-
when Time
|
|
423
|
-
quote_column_value(column_value.strftime('%Y-%m-%d %H:%M:%S') + ((column_value.usec > 0 ? ".#{column_value.usec.to_s.rjust(6, '0')}" : '')))
|
|
424
|
-
when Integer, Float
|
|
425
|
-
column_value.to_s
|
|
426
|
-
when BigDecimal
|
|
427
|
-
column_value.to_s('F')
|
|
428
|
-
else
|
|
429
|
-
column_value.to_s
|
|
430
|
-
end
|
|
444
|
+
return statement, bind_values
|
|
431
445
|
end
|
|
432
|
-
end #module SQL
|
|
433
446
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
#
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return model.properties(repository.name)
|
|
444
|
-
end
|
|
447
|
+
# Constructs comma separated list of fields
|
|
448
|
+
#
|
|
449
|
+
# @return [String]
|
|
450
|
+
# list of fields as a string
|
|
451
|
+
#
|
|
452
|
+
# @api private
|
|
453
|
+
def columns_statement(properties, qualify)
|
|
454
|
+
properties.map { |property| property_to_column_name(property, qualify) }.join(', ')
|
|
455
|
+
end
|
|
445
456
|
|
|
446
|
-
|
|
457
|
+
# Constructs joins clause
|
|
458
|
+
#
|
|
459
|
+
# @return [String]
|
|
460
|
+
# joins clause
|
|
461
|
+
#
|
|
462
|
+
# @api private
|
|
463
|
+
def join_statement(query, qualify)
|
|
464
|
+
statement = ''
|
|
447
465
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
properties << property
|
|
466
|
+
query.links.reverse_each do |relationship|
|
|
467
|
+
statement << " INNER JOIN #{quote_name(relationship.source_model.storage_name(name))} ON "
|
|
468
|
+
statement << relationship.target_key.zip(relationship.source_key).map do |target_property, source_property|
|
|
469
|
+
"#{property_to_column_name(target_property, qualify)} = #{property_to_column_name(source_property, qualify)}"
|
|
470
|
+
end.join(' AND ')
|
|
454
471
|
end
|
|
455
472
|
|
|
456
|
-
|
|
473
|
+
statement
|
|
457
474
|
end
|
|
458
475
|
|
|
459
|
-
#
|
|
460
|
-
|
|
461
|
-
|
|
476
|
+
# Constructs where clause
|
|
477
|
+
#
|
|
478
|
+
# @return [String]
|
|
479
|
+
# where clause
|
|
480
|
+
#
|
|
481
|
+
# @api private
|
|
482
|
+
def conditions_statement(conditions, qualify = false)
|
|
483
|
+
case conditions
|
|
484
|
+
when Query::Conditions::NotOperation
|
|
485
|
+
negate_operation(conditions, qualify)
|
|
486
|
+
|
|
487
|
+
when Query::Conditions::AbstractOperation
|
|
488
|
+
# TODO: remove this once conditions can be compressed
|
|
489
|
+
if conditions.operands.size == 1
|
|
490
|
+
# factor out operations with a single operand
|
|
491
|
+
conditions_statement(conditions.operands.first, qualify)
|
|
492
|
+
else
|
|
493
|
+
operation_statement(conditions, qualify)
|
|
494
|
+
end
|
|
462
495
|
|
|
463
|
-
|
|
496
|
+
when Query::Conditions::AbstractComparison
|
|
497
|
+
comparison_statement(conditions, qualify)
|
|
464
498
|
|
|
465
|
-
|
|
466
|
-
|
|
499
|
+
when Array
|
|
500
|
+
statement, bind_values = conditions # handle raw conditions
|
|
501
|
+
[ "(#{statement})", bind_values ]
|
|
467
502
|
end
|
|
468
|
-
|
|
469
|
-
true
|
|
470
503
|
end
|
|
471
504
|
|
|
472
|
-
#
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
505
|
+
# Constructs order clause
|
|
506
|
+
#
|
|
507
|
+
# @return [String]
|
|
508
|
+
# order clause
|
|
509
|
+
#
|
|
510
|
+
# @api private
|
|
511
|
+
def order_statement(order, qualify)
|
|
512
|
+
statements = order.map do |direction|
|
|
513
|
+
statement = property_to_column_name(direction.target, qualify)
|
|
514
|
+
statement << ' DESC' if direction.operator == :desc
|
|
515
|
+
statement
|
|
516
|
+
end
|
|
477
517
|
|
|
478
|
-
|
|
479
|
-
def transaction_primitive
|
|
480
|
-
DataObjects::Transaction.create_for_uri(@uri)
|
|
518
|
+
statements.join(', ')
|
|
481
519
|
end
|
|
482
520
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
false
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# TODO: move to dm-more/dm-migrations
|
|
495
|
-
def alter_table_add_column_statement(table_name, schema_hash)
|
|
496
|
-
"ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{property_schema_statement(schema_hash)}"
|
|
521
|
+
# TODO: document
|
|
522
|
+
# @api private
|
|
523
|
+
def negate_operation(operation, qualify)
|
|
524
|
+
@negated = !@negated
|
|
525
|
+
begin
|
|
526
|
+
conditions_statement(operation.operands.first, qualify)
|
|
527
|
+
ensure
|
|
528
|
+
@negated = !@negated
|
|
497
529
|
end
|
|
530
|
+
end
|
|
498
531
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
532
|
+
# TODO: document
|
|
533
|
+
# @api private
|
|
534
|
+
def operation_statement(operation, qualify)
|
|
535
|
+
statements = []
|
|
536
|
+
bind_values = []
|
|
502
537
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
(#{model.properties_with_subclasses(repository_name).map { |p| property_schema_statement(property_schema_hash(repository, p)) } * ', '}
|
|
506
|
-
EOS
|
|
538
|
+
operation.each do |operand|
|
|
539
|
+
statement, values = conditions_statement(operand, qualify)
|
|
507
540
|
|
|
508
|
-
if (
|
|
509
|
-
statement
|
|
541
|
+
if operand.respond_to?(:operands) && operand.operands.size > 1
|
|
542
|
+
statement = "(#{statement})"
|
|
510
543
|
end
|
|
511
544
|
|
|
512
|
-
|
|
513
|
-
|
|
545
|
+
statements << statement
|
|
546
|
+
bind_values.concat(values)
|
|
514
547
|
end
|
|
515
548
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
"DROP TABLE IF EXISTS #{quote_table_name(model.storage_name(repository.name))}"
|
|
519
|
-
end
|
|
549
|
+
join_with = operation.kind_of?(@negated ? Query::Conditions::OrOperation : Query::Conditions::AndOperation) ? 'AND' : 'OR'
|
|
550
|
+
statement = statements.join(" #{join_with} ")
|
|
520
551
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
table_name = model.storage_name(repository.name)
|
|
524
|
-
model.properties(repository.name).indexes.map do |index_name, fields|
|
|
525
|
-
<<-EOS.compress_lines
|
|
526
|
-
CREATE INDEX #{quote_column_name("index_#{table_name}_#{index_name}")} ON
|
|
527
|
-
#{quote_table_name(table_name)} (#{fields.map { |f| quote_column_name(f) } * ', '})
|
|
528
|
-
EOS
|
|
529
|
-
end
|
|
530
|
-
end
|
|
552
|
+
return statement, bind_values
|
|
553
|
+
end
|
|
531
554
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
end
|
|
541
|
-
end
|
|
555
|
+
# Constructs comparison clause
|
|
556
|
+
#
|
|
557
|
+
# @return [String]
|
|
558
|
+
# comparison clause
|
|
559
|
+
#
|
|
560
|
+
# @api private
|
|
561
|
+
def comparison_statement(comparison, qualify)
|
|
562
|
+
value = comparison.value
|
|
542
563
|
|
|
543
|
-
# TODO: move
|
|
544
|
-
|
|
545
|
-
schema = self.class.type_map[property.type].merge(:name => property.field(repository.name))
|
|
546
|
-
# TODO: figure out a way to specify the size not be included, even if
|
|
547
|
-
# a default is defined in the typemap
|
|
548
|
-
# - use this to make it so all TEXT primitive fields do not have size
|
|
549
|
-
if property.primitive == String && schema[:primitive] != 'TEXT'
|
|
550
|
-
schema[:size] = property.length
|
|
551
|
-
elsif property.primitive == BigDecimal || property.primitive == Float
|
|
552
|
-
schema[:precision] = property.precision
|
|
553
|
-
schema[:scale] = property.scale
|
|
554
|
-
end
|
|
564
|
+
# TODO: move exclusive Range handling into another method, and
|
|
565
|
+
# update conditions_statement to use it
|
|
555
566
|
|
|
556
|
-
|
|
557
|
-
|
|
567
|
+
# break exclusive Range queries up into two comparisons ANDed together
|
|
568
|
+
if value.kind_of?(Range) && value.exclude_end?
|
|
569
|
+
operation = Query::Conditions::Operation.new(:and,
|
|
570
|
+
Query::Conditions::Comparison.new(:gte, comparison.subject, value.first),
|
|
571
|
+
Query::Conditions::Comparison.new(:lt, comparison.subject, value.last)
|
|
572
|
+
)
|
|
558
573
|
|
|
559
|
-
|
|
560
|
-
# remove the default if the property is not nullable
|
|
561
|
-
schema.delete(:default) unless property.nullable?
|
|
562
|
-
else
|
|
563
|
-
if property.type.respond_to?(:dump)
|
|
564
|
-
schema[:default] = property.type.dump(property.default, property)
|
|
565
|
-
else
|
|
566
|
-
schema[:default] = property.default
|
|
567
|
-
end
|
|
568
|
-
end
|
|
574
|
+
statement, bind_values = conditions_statement(operation, qualify)
|
|
569
575
|
|
|
570
|
-
|
|
576
|
+
return "(#{statement})", bind_values
|
|
577
|
+
elsif comparison.relationship?
|
|
578
|
+
return conditions_statement(comparison.foreign_key_mapping, qualify)
|
|
571
579
|
end
|
|
572
580
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
581
|
+
operator = case comparison
|
|
582
|
+
when Query::Conditions::EqualToComparison then @negated ? inequality_operator(comparison.subject, value) : equality_operator(comparison.subject, value)
|
|
583
|
+
when Query::Conditions::InclusionComparison then @negated ? exclude_operator(comparison.subject, value) : include_operator(comparison.subject, value)
|
|
584
|
+
when Query::Conditions::RegexpComparison then @negated ? not_regexp_operator(value) : regexp_operator(value)
|
|
585
|
+
when Query::Conditions::LikeComparison then @negated ? unlike_operator(value) : like_operator(value)
|
|
586
|
+
when Query::Conditions::GreaterThanComparison then @negated ? '<=' : '>'
|
|
587
|
+
when Query::Conditions::LessThanComparison then @negated ? '>=' : '<'
|
|
588
|
+
when Query::Conditions::GreaterThanOrEqualToComparison then @negated ? '<' : '>='
|
|
589
|
+
when Query::Conditions::LessThanOrEqualToComparison then @negated ? '>' : '<='
|
|
590
|
+
end
|
|
583
591
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
592
|
+
# if operator return value contains ? then it means that it is function call
|
|
593
|
+
# and it contains placeholder (%s) for property name as well (used in Oracle adapter for regexp operator)
|
|
594
|
+
if operator.include?('?')
|
|
595
|
+
return operator % property_to_column_name(comparison.subject, qualify), [ value ]
|
|
596
|
+
else
|
|
597
|
+
return "#{property_to_column_name(comparison.subject, qualify)} #{operator} #{value.nil? ? 'NULL' : '?'}", [ value ].compact
|
|
587
598
|
end
|
|
599
|
+
end
|
|
588
600
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
601
|
+
# TODO: document
|
|
602
|
+
# @api private
|
|
603
|
+
def equality_operator(property, operand)
|
|
604
|
+
operand.nil? ? 'IS' : '='
|
|
605
|
+
end
|
|
592
606
|
|
|
593
|
-
|
|
594
|
-
|
|
607
|
+
# TODO: document
|
|
608
|
+
# @api private
|
|
609
|
+
def inequality_operator(property, operand)
|
|
610
|
+
operand.nil? ? 'IS NOT' : '<>'
|
|
611
|
+
end
|
|
595
612
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
include SQL
|
|
603
|
-
|
|
604
|
-
module ClassMethods
|
|
605
|
-
# Default TypeMap for all data object based adapters.
|
|
606
|
-
#
|
|
607
|
-
# @return <DataMapper::TypeMap> default TypeMap for data objects adapters.
|
|
608
|
-
#
|
|
609
|
-
# TODO: move to dm-more/dm-migrations
|
|
610
|
-
def type_map
|
|
611
|
-
@type_map ||= TypeMap.new(super) do |tm|
|
|
612
|
-
tm.map(Integer).to('INT')
|
|
613
|
-
tm.map(String).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
|
|
614
|
-
tm.map(Class).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
|
|
615
|
-
tm.map(DM::Discriminator).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
|
|
616
|
-
tm.map(BigDecimal).to('DECIMAL').with(:precision => Property::DEFAULT_PRECISION, :scale => Property::DEFAULT_SCALE_BIGDECIMAL)
|
|
617
|
-
tm.map(Float).to('FLOAT').with(:precision => Property::DEFAULT_PRECISION)
|
|
618
|
-
tm.map(DateTime).to('DATETIME')
|
|
619
|
-
tm.map(Date).to('DATE')
|
|
620
|
-
tm.map(Time).to('TIMESTAMP')
|
|
621
|
-
tm.map(TrueClass).to('BOOLEAN')
|
|
622
|
-
tm.map(DM::Object).to('TEXT')
|
|
623
|
-
tm.map(DM::Text).to('TEXT')
|
|
624
|
-
end
|
|
613
|
+
# TODO: document
|
|
614
|
+
# @api private
|
|
615
|
+
def include_operator(property, operand)
|
|
616
|
+
case operand
|
|
617
|
+
when Array then 'IN'
|
|
618
|
+
when Range then 'BETWEEN'
|
|
625
619
|
end
|
|
626
|
-
end
|
|
627
|
-
end # module Migration
|
|
628
|
-
|
|
629
|
-
include Migration
|
|
630
|
-
extend Migration::ClassMethods
|
|
631
|
-
end # class DataObjectsAdapter
|
|
632
|
-
end # module Adapters
|
|
620
|
+
end
|
|
633
621
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
#
|
|
639
|
-
# @param sql<String> an SQL query to execute
|
|
640
|
-
# @param <Array> an Array containing a String (being the SQL query to
|
|
641
|
-
# execute) and the parameters to the query.
|
|
642
|
-
# example: ["SELECT name FROM users WHERE id = ?", id]
|
|
643
|
-
# @param query<DataMapper::Query> a prepared Query to execute.
|
|
644
|
-
# @param opts<Hash> an options hash.
|
|
645
|
-
# :repository<Symbol> the name of the repository to execute the query
|
|
646
|
-
# in. Defaults to self.default_repository_name.
|
|
647
|
-
# :reload<Boolean> whether to reload any instances found that already
|
|
648
|
-
# exist in the identity map. Defaults to false.
|
|
649
|
-
# :properties<Array> the Properties of the instance that the query
|
|
650
|
-
# loads. Must contain DataMapper::Properties.
|
|
651
|
-
# Defaults to self.properties.
|
|
652
|
-
#
|
|
653
|
-
# @note
|
|
654
|
-
# A String, Array or Query is required.
|
|
655
|
-
# @return <Collection> the instance matched by the query.
|
|
656
|
-
#
|
|
657
|
-
# @example
|
|
658
|
-
# MyClass.find_by_sql(["SELECT id FROM my_classes WHERE county = ?",
|
|
659
|
-
# selected_county], :properties => MyClass.property[:id],
|
|
660
|
-
# :repository => :county_repo)
|
|
661
|
-
#
|
|
662
|
-
# -
|
|
663
|
-
# @api public
|
|
664
|
-
def find_by_sql(*args)
|
|
665
|
-
sql = nil
|
|
666
|
-
query = nil
|
|
667
|
-
bind_values = []
|
|
668
|
-
properties = nil
|
|
669
|
-
do_reload = false
|
|
670
|
-
repository_name = default_repository_name
|
|
671
|
-
args.each do |arg|
|
|
672
|
-
if arg.is_a?(String)
|
|
673
|
-
sql = arg
|
|
674
|
-
elsif arg.is_a?(Array)
|
|
675
|
-
sql = arg.first
|
|
676
|
-
bind_values = arg[1..-1]
|
|
677
|
-
elsif arg.is_a?(DataMapper::Query)
|
|
678
|
-
query = arg
|
|
679
|
-
elsif arg.is_a?(Hash)
|
|
680
|
-
repository_name = arg.delete(:repository) if arg.include?(:repository)
|
|
681
|
-
properties = Array(arg.delete(:properties)) if arg.include?(:properties)
|
|
682
|
-
do_reload = arg.delete(:reload) if arg.include?(:reload)
|
|
683
|
-
raise "unknown options to #find_by_sql: #{arg.inspect}" unless arg.empty?
|
|
622
|
+
# TODO: document
|
|
623
|
+
# @api private
|
|
624
|
+
def exclude_operator(property, operand)
|
|
625
|
+
"NOT #{include_operator(property, operand)}"
|
|
684
626
|
end
|
|
685
|
-
end
|
|
686
627
|
|
|
687
|
-
|
|
688
|
-
|
|
628
|
+
# TODO: document
|
|
629
|
+
# @api private
|
|
630
|
+
def regexp_operator(operand)
|
|
631
|
+
'~'
|
|
632
|
+
end
|
|
689
633
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
634
|
+
# TODO: document
|
|
635
|
+
# @api private
|
|
636
|
+
def not_regexp_operator(operand)
|
|
637
|
+
'!~'
|
|
638
|
+
end
|
|
694
639
|
|
|
695
|
-
|
|
640
|
+
# TODO: document
|
|
641
|
+
# @api private
|
|
642
|
+
def like_operator(operand)
|
|
643
|
+
'LIKE'
|
|
644
|
+
end
|
|
696
645
|
|
|
697
|
-
|
|
646
|
+
# TODO: document
|
|
647
|
+
# @api private
|
|
648
|
+
def unlike_operator(operand)
|
|
649
|
+
'NOT LIKE'
|
|
650
|
+
end
|
|
698
651
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
652
|
+
# TODO: document
|
|
653
|
+
# @api private
|
|
654
|
+
def quote_name(name)
|
|
655
|
+
"\"#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '""')}\""
|
|
656
|
+
end
|
|
657
|
+
end #module SQL
|
|
702
658
|
|
|
703
|
-
|
|
704
|
-
|
|
659
|
+
include SQL
|
|
660
|
+
end # class DataObjectsAdapter
|
|
705
661
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
end
|
|
709
|
-
ensure
|
|
710
|
-
reader.close if reader
|
|
711
|
-
end
|
|
712
|
-
end
|
|
713
|
-
end
|
|
714
|
-
end
|
|
715
|
-
end # module Model
|
|
662
|
+
const_added(:DataObjectsAdapter)
|
|
663
|
+
end # module Adapters
|
|
716
664
|
end # module DataMapper
|