mack-data_mapper 0.8.1 → 0.8.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.
Files changed (166) hide show
  1. data/lib/gems/addressable-2.0.0/lib/addressable/idna.rb +4867 -0
  2. data/lib/gems/addressable-2.0.0/lib/addressable/uri.rb +2469 -0
  3. data/lib/gems/addressable-2.0.0/lib/addressable/version.rb +35 -0
  4. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/adapters/data_objects_adapter.rb +85 -0
  5. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/aggregate_functions.rb +201 -0
  6. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/collection.rb +11 -0
  7. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/model.rb +11 -0
  8. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/repository.rb +7 -0
  9. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/support/symbol.rb +21 -0
  10. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/version.rb +7 -0
  11. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates.rb +15 -0
  12. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  13. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/data_objects_adapter.rb +709 -0
  14. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  15. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  16. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  17. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  18. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters.rb +22 -0
  19. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_many.rb +147 -0
  20. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_one.rb +107 -0
  21. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_many.rb +318 -0
  22. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_one.rb +61 -0
  23. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship.rb +223 -0
  24. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship_chain.rb +81 -0
  25. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations.rb +200 -0
  26. data/lib/gems/dm-core-0.9.7/lib/dm-core/auto_migrations.rb +105 -0
  27. data/lib/gems/dm-core-0.9.7/lib/dm-core/collection.rb +642 -0
  28. data/lib/gems/dm-core-0.9.7/lib/dm-core/dependency_queue.rb +32 -0
  29. data/lib/gems/dm-core-0.9.7/lib/dm-core/hook.rb +11 -0
  30. data/lib/gems/dm-core-0.9.7/lib/dm-core/identity_map.rb +42 -0
  31. data/lib/gems/dm-core-0.9.7/lib/dm-core/is.rb +16 -0
  32. data/lib/gems/dm-core-0.9.7/lib/dm-core/logger.rb +232 -0
  33. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrator.rb +29 -0
  35. data/lib/gems/dm-core-0.9.7/lib/dm-core/model.rb +488 -0
  36. data/lib/gems/dm-core-0.9.7/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/gems/dm-core-0.9.7/lib/dm-core/property.rb +663 -0
  38. data/lib/gems/dm-core-0.9.7/lib/dm-core/property_set.rb +169 -0
  39. data/lib/gems/dm-core-0.9.7/lib/dm-core/query.rb +628 -0
  40. data/lib/gems/dm-core-0.9.7/lib/dm-core/repository.rb +159 -0
  41. data/lib/gems/dm-core-0.9.7/lib/dm-core/resource.rb +637 -0
  42. data/lib/gems/dm-core-0.9.7/lib/dm-core/scope.rb +58 -0
  43. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/array.rb +13 -0
  44. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/assertions.rb +8 -0
  45. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/errors.rb +23 -0
  46. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/kernel.rb +11 -0
  47. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/symbol.rb +41 -0
  48. data/lib/gems/dm-core-0.9.7/lib/dm-core/support.rb +7 -0
  49. data/lib/gems/dm-core-0.9.7/lib/dm-core/transaction.rb +267 -0
  50. data/lib/gems/dm-core-0.9.7/lib/dm-core/type.rb +160 -0
  51. data/lib/gems/dm-core-0.9.7/lib/dm-core/type_map.rb +80 -0
  52. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/boolean.rb +7 -0
  53. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/discriminator.rb +34 -0
  54. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/object.rb +24 -0
  55. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_boolean.rb +34 -0
  56. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_datetime.rb +33 -0
  57. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/serial.rb +9 -0
  58. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/text.rb +10 -0
  59. data/lib/gems/dm-core-0.9.7/lib/dm-core/types.rb +19 -0
  60. data/lib/gems/dm-core-0.9.7/lib/dm-core/version.rb +3 -0
  61. data/lib/gems/dm-core-0.9.7/lib/dm-core.rb +217 -0
  62. data/lib/gems/dm-core-0.9.7/script/all +5 -0
  63. data/lib/gems/dm-core-0.9.7/script/performance.rb +284 -0
  64. data/lib/gems/dm-core-0.9.7/script/profile.rb +87 -0
  65. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations/version.rb +5 -0
  66. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations.rb +1 -0
  67. data/lib/gems/dm-migrations-0.9.7/lib/migration.rb +215 -0
  68. data/lib/gems/dm-migrations-0.9.7/lib/migration_runner.rb +88 -0
  69. data/lib/gems/dm-migrations-0.9.7/lib/spec/example/migration_example_group.rb +73 -0
  70. data/lib/gems/dm-migrations-0.9.7/lib/spec/matchers/migration_matchers.rb +107 -0
  71. data/lib/gems/dm-migrations-0.9.7/lib/sql/column.rb +9 -0
  72. data/lib/gems/dm-migrations-0.9.7/lib/sql/mysql.rb +52 -0
  73. data/lib/gems/dm-migrations-0.9.7/lib/sql/postgresql.rb +78 -0
  74. data/lib/gems/dm-migrations-0.9.7/lib/sql/sqlite3.rb +43 -0
  75. data/lib/gems/dm-migrations-0.9.7/lib/sql/table.rb +19 -0
  76. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_creator.rb +81 -0
  77. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_modifier.rb +53 -0
  78. data/lib/gems/dm-migrations-0.9.7/lib/sql.rb +10 -0
  79. data/lib/gems/dm-observer-0.9.7/lib/dm-observer/version.rb +5 -0
  80. data/lib/gems/dm-observer-0.9.7/lib/dm-observer.rb +91 -0
  81. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer/version.rb +5 -0
  82. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer.rb +183 -0
  83. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps/version.rb +5 -0
  84. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps.rb +57 -0
  85. data/lib/gems/dm-types-0.9.7/lib/dm-types/bcrypt_hash.rb +31 -0
  86. data/lib/gems/dm-types-0.9.7/lib/dm-types/csv.rb +28 -0
  87. data/lib/gems/dm-types-0.9.7/lib/dm-types/enum.rb +70 -0
  88. data/lib/gems/dm-types-0.9.7/lib/dm-types/epoch_time.rb +27 -0
  89. data/lib/gems/dm-types-0.9.7/lib/dm-types/file_path.rb +27 -0
  90. data/lib/gems/dm-types-0.9.7/lib/dm-types/flag.rb +61 -0
  91. data/lib/gems/dm-types-0.9.7/lib/dm-types/ip_address.rb +30 -0
  92. data/lib/gems/dm-types-0.9.7/lib/dm-types/json.rb +40 -0
  93. data/lib/gems/dm-types-0.9.7/lib/dm-types/regexp.rb +20 -0
  94. data/lib/gems/dm-types-0.9.7/lib/dm-types/serial.rb +8 -0
  95. data/lib/gems/dm-types-0.9.7/lib/dm-types/slug.rb +37 -0
  96. data/lib/gems/dm-types-0.9.7/lib/dm-types/uri.rb +29 -0
  97. data/lib/gems/dm-types-0.9.7/lib/dm-types/uuid.rb +64 -0
  98. data/lib/gems/dm-types-0.9.7/lib/dm-types/version.rb +5 -0
  99. data/lib/gems/dm-types-0.9.7/lib/dm-types/yaml.rb +36 -0
  100. data/lib/gems/dm-types-0.9.7/lib/dm-types.rb +28 -0
  101. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/absent_field_validator.rb +60 -0
  102. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/acceptance_validator.rb +76 -0
  103. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/auto_validate.rb +153 -0
  104. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/block_validator.rb +60 -0
  105. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/confirmation_validator.rb +80 -0
  106. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/contextual_validators.rb +56 -0
  107. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/custom_validator.rb +72 -0
  108. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/format_validator.rb +97 -0
  109. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/email.rb +40 -0
  110. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/url.rb +20 -0
  111. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/generic_validator.rb +100 -0
  112. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/length_validator.rb +113 -0
  113. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/method_validator.rb +68 -0
  114. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/numeric_validator.rb +83 -0
  115. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/primitive_validator.rb +60 -0
  116. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/required_field_validator.rb +88 -0
  117. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/support/object.rb +5 -0
  118. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/uniqueness_validator.rb +64 -0
  119. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/validation_errors.rb +63 -0
  120. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/version.rb +5 -0
  121. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/within_validator.rb +53 -0
  122. data/lib/gems/dm-validations-0.9.7/lib/dm-validations.rb +234 -0
  123. data/lib/gems/json_pure-1.1.3/GPL +340 -0
  124. data/lib/gems/json_pure-1.1.3/VERSION +1 -0
  125. data/lib/gems/json_pure-1.1.3/bin/edit_json.rb +10 -0
  126. data/lib/gems/json_pure-1.1.3/bin/prettify_json.rb +76 -0
  127. data/lib/gems/json_pure-1.1.3/lib/json/Array.xpm +21 -0
  128. data/lib/gems/json_pure-1.1.3/lib/json/FalseClass.xpm +21 -0
  129. data/lib/gems/json_pure-1.1.3/lib/json/Hash.xpm +21 -0
  130. data/lib/gems/json_pure-1.1.3/lib/json/Key.xpm +73 -0
  131. data/lib/gems/json_pure-1.1.3/lib/json/NilClass.xpm +21 -0
  132. data/lib/gems/json_pure-1.1.3/lib/json/Numeric.xpm +28 -0
  133. data/lib/gems/json_pure-1.1.3/lib/json/String.xpm +96 -0
  134. data/lib/gems/json_pure-1.1.3/lib/json/TrueClass.xpm +21 -0
  135. data/lib/gems/json_pure-1.1.3/lib/json/add/core.rb +135 -0
  136. data/lib/gems/json_pure-1.1.3/lib/json/add/rails.rb +58 -0
  137. data/lib/gems/json_pure-1.1.3/lib/json/common.rb +354 -0
  138. data/lib/gems/json_pure-1.1.3/lib/json/editor.rb +1362 -0
  139. data/lib/gems/json_pure-1.1.3/lib/json/ext.rb +13 -0
  140. data/lib/gems/json_pure-1.1.3/lib/json/json.xpm +1499 -0
  141. data/lib/gems/json_pure-1.1.3/lib/json/pure/generator.rb +394 -0
  142. data/lib/gems/json_pure-1.1.3/lib/json/pure/parser.rb +259 -0
  143. data/lib/gems/json_pure-1.1.3/lib/json/pure.rb +75 -0
  144. data/lib/gems/json_pure-1.1.3/lib/json/version.rb +9 -0
  145. data/lib/gems/json_pure-1.1.3/lib/json.rb +235 -0
  146. data/lib/gems/launchy-0.3.2/bin/launchy +12 -0
  147. data/lib/gems/launchy-0.3.2/lib/launchy/application.rb +163 -0
  148. data/lib/gems/launchy-0.3.2/lib/launchy/browser.rb +85 -0
  149. data/lib/gems/launchy-0.3.2/lib/launchy/command_line.rb +48 -0
  150. data/lib/gems/launchy-0.3.2/lib/launchy/gemspec.rb +53 -0
  151. data/lib/gems/launchy-0.3.2/lib/launchy/specification.rb +133 -0
  152. data/lib/gems/launchy-0.3.2/lib/launchy/version.rb +18 -0
  153. data/lib/gems/launchy-0.3.2/lib/launchy.rb +58 -0
  154. data/lib/gems/uuidtools-1.0.3/lib/uuidtools/version.rb +32 -0
  155. data/lib/gems/uuidtools-1.0.3/lib/uuidtools.rb +648 -0
  156. data/lib/gems.rb +13 -0
  157. data/lib/mack-data_mapper/migration_generator/migration_generator.rb +5 -0
  158. data/lib/mack-data_mapper/migration_generator/templates/db/migrations/%=@migration_name%.rb.template +1 -1
  159. data/lib/mack-data_mapper/model_generator/manifest.yml +3 -3
  160. data/lib/mack-data_mapper/model_generator/model_generator.rb +8 -1
  161. data/lib/mack-data_mapper/model_generator/templates/model.rb.template +1 -1
  162. data/lib/mack-data_mapper/model_generator/templates/rspec.rb.template +1 -1
  163. data/lib/mack-data_mapper/model_generator/templates/test_case.rb.template +1 -1
  164. data/lib/mack-data_mapper.rb +3 -2
  165. data/lib/mack-data_mapper_tasks.rb +7 -0
  166. metadata +235 -86
@@ -0,0 +1,709 @@
1
+ gem 'data_objects', '~>0.9.7'
2
+ require 'data_objects'
3
+
4
+ module DataMapper
5
+ module Adapters
6
+ # You must inherit from the DoAdapter, and implement the
7
+ # required methods to adapt a database library for use with the DataMapper.
8
+ #
9
+ # NOTE: 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
+ def create(resources)
14
+ created = 0
15
+ resources.each do |resource|
16
+ repository = resource.repository
17
+ model = resource.model
18
+ attributes = resource.dirty_attributes
19
+
20
+ # TODO: make a model.identity_field method
21
+ identity_field = model.key(repository.name).detect { |p| p.serial? }
22
+
23
+ statement = create_statement(repository, model, attributes.keys, identity_field)
24
+ bind_values = attributes.values
25
+
26
+ result = execute(statement, *bind_values)
27
+
28
+ if result.to_i == 1
29
+ if identity_field
30
+ identity_field.set!(resource, result.insert_id)
31
+ end
32
+ created += 1
33
+ end
34
+ end
35
+ created
36
+ end
37
+
38
+ def read_many(query)
39
+ Collection.new(query) do |collection|
40
+ with_connection do |connection|
41
+ command = connection.create_command(read_statement(query))
42
+ command.set_types(query.fields.map { |p| p.primitive })
43
+
44
+ begin
45
+ bind_values = query.bind_values.map do |v|
46
+ v == [] ? [nil] : v
47
+ end
48
+ reader = command.execute_reader(*bind_values)
49
+
50
+ while(reader.next!)
51
+ collection.load(reader.values)
52
+ end
53
+ ensure
54
+ reader.close if reader
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def read_one(query)
61
+ with_connection do |connection|
62
+ command = connection.create_command(read_statement(query))
63
+ command.set_types(query.fields.map { |p| p.primitive })
64
+
65
+ begin
66
+ reader = command.execute_reader(*query.bind_values)
67
+
68
+ if reader.next!
69
+ query.model.load(reader.values, query)
70
+ end
71
+ ensure
72
+ reader.close if reader
73
+ end
74
+ end
75
+ end
76
+
77
+ def update(attributes, query)
78
+ statement = update_statement(attributes.keys, query)
79
+ bind_values = attributes.values + query.bind_values
80
+ execute(statement, *bind_values).to_i
81
+ end
82
+
83
+ def delete(query)
84
+ statement = delete_statement(query)
85
+ execute(statement, *query.bind_values).to_i
86
+ end
87
+
88
+ # Database-specific method
89
+ def execute(statement, *bind_values)
90
+ with_connection do |connection|
91
+ command = connection.create_command(statement)
92
+ command.execute_non_query(*bind_values)
93
+ end
94
+ end
95
+
96
+ def query(statement, *bind_values)
97
+ with_reader(statement, bind_values) do |reader|
98
+ results = []
99
+
100
+ if (fields = reader.fields).size > 1
101
+ fields = fields.map { |field| Extlib::Inflection.underscore(field).to_sym }
102
+ struct = Struct.new(*fields)
103
+
104
+ while(reader.next!) do
105
+ results << struct.new(*reader.values)
106
+ end
107
+ else
108
+ while(reader.next!) do
109
+ results << reader.values.at(0)
110
+ end
111
+ end
112
+
113
+ results
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+ def normalize_uri(uri_or_options)
120
+ if uri_or_options.kind_of?(String) || uri_or_options.kind_of?(Addressable::URI)
121
+ uri_or_options = DataObjects::URI.parse(uri_or_options)
122
+ end
123
+
124
+ if uri_or_options.kind_of?(DataObjects::URI)
125
+ return uri_or_options
126
+ end
127
+
128
+ query = uri_or_options.except(:adapter, :username, :password, :host, :port, :database).map { |pair| pair.join('=') }.join('&')
129
+ query = nil if query.blank?
130
+
131
+ return DataObjects::URI.parse(Addressable::URI.new(
132
+ :scheme => uri_or_options[:adapter].to_s,
133
+ :user => uri_or_options[:username],
134
+ :password => uri_or_options[:password],
135
+ :host => uri_or_options[:host],
136
+ :port => uri_or_options[:port],
137
+ :path => uri_or_options[:database],
138
+ :query => query
139
+ ))
140
+ end
141
+
142
+ # TODO: clean up once transaction related methods move to dm-more/dm-transactions
143
+ def create_connection
144
+ if within_transaction?
145
+ current_transaction.primitive_for(self).connection
146
+ else
147
+ # DataObjects::Connection.new(uri) will give you back the right
148
+ # driver based on the Uri#scheme.
149
+ DataObjects::Connection.new(@uri)
150
+ end
151
+ end
152
+
153
+ # TODO: clean up once transaction related methods move to dm-more/dm-transactions
154
+ def close_connection(connection)
155
+ connection.close unless within_transaction? && current_transaction.primitive_for(self).connection == connection
156
+ end
157
+
158
+ private
159
+
160
+ def initialize(name, uri_or_options)
161
+ super
162
+
163
+ # Default the driver-specifc logger to DataMapper's logger
164
+ if driver_module = DataObjects.const_get(@uri.scheme.capitalize) rescue nil
165
+ driver_module.logger = DataMapper.logger if driver_module.respond_to?(:logger=)
166
+ end
167
+ end
168
+
169
+ def with_connection
170
+ connection = nil
171
+ begin
172
+ connection = create_connection
173
+ return yield(connection)
174
+ rescue => e
175
+ DataMapper.logger.error(e)
176
+ raise e
177
+ ensure
178
+ close_connection(connection) if connection
179
+ end
180
+ end
181
+
182
+ def with_reader(statement, bind_values = [])
183
+ with_connection do |connection|
184
+ reader = nil
185
+ begin
186
+ reader = connection.create_command(statement).execute_reader(*bind_values)
187
+ return yield(reader)
188
+ ensure
189
+ reader.close if reader
190
+ end
191
+ end
192
+ end
193
+
194
+ # This model is just for organization. The methods are included into the
195
+ # Adapter below.
196
+ module SQL
197
+ private
198
+
199
+ # Adapters requiring a RETURNING syntax for INSERT statements
200
+ # should overwrite this to return true.
201
+ def supports_returning?
202
+ false
203
+ end
204
+
205
+ # Adapters that do not support the DEFAULT VALUES syntax for
206
+ # INSERT statements should overwrite this to return false.
207
+ def supports_default_values?
208
+ true
209
+ end
210
+
211
+ def create_statement(repository, model, properties, identity_field)
212
+ statement = "INSERT INTO #{quote_table_name(model.storage_name(repository.name))} "
213
+
214
+ if supports_default_values? && properties.empty?
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
223
+
224
+ if supports_returning? && identity_field
225
+ statement << " RETURNING #{quote_column_name(identity_field.field(repository.name))}"
226
+ end
227
+
228
+ statement
229
+ end
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
262
+
263
+ def fields_statement(query)
264
+ qualify = query.links.any?
265
+ query.fields.map { |p| property_to_column_name(query.repository, p, qualify) } * ', '
266
+ end
267
+
268
+ def links_statement(query)
269
+ table_name = query.model.storage_name(query.repository.name)
270
+
271
+ statement = ''
272
+ query.links.each do |relationship|
273
+ parent_table_name = relationship.parent_model.storage_name(query.repository.name)
274
+ child_table_name = relationship.child_model.storage_name(query.repository.name)
275
+
276
+ join_table_name = table_name == parent_table_name ? child_table_name : parent_table_name
277
+
278
+ # We only do INNER JOIN for now
279
+ statement << " INNER JOIN #{quote_table_name(join_table_name)} ON "
280
+
281
+ statement << relationship.parent_key.zip(relationship.child_key).map do |parent_property,child_property|
282
+ condition_statement(query, :eql, parent_property, child_property)
283
+ end * ' AND '
284
+ end
285
+
286
+ statement
287
+ end
288
+
289
+ def conditions_statement(query)
290
+ query.conditions.map { |o,p,b| condition_statement(query, o, p, b) } * ' AND '
291
+ end
292
+
293
+ def group_by_statement(query)
294
+ repository = query.repository
295
+ qualify = query.links.any?
296
+ query.fields.select { |p| p.kind_of?(Property) }.map { |p| property_to_column_name(repository, p, qualify) } * ', '
297
+ end
298
+
299
+ def order_statement(query)
300
+ repository = query.repository
301
+ qualify = query.links.any?
302
+ query.order.map { |i| order_column(repository, i, qualify) } * ', '
303
+ end
304
+
305
+ def order_column(repository, item, qualify)
306
+ property, descending = nil, false
307
+
308
+ case item
309
+ when Property
310
+ property = item
311
+ when Query::Direction
312
+ property = item.property
313
+ descending = true if item.direction == :desc
314
+ end
315
+
316
+ order_column = property_to_column_name(repository, property, qualify)
317
+ order_column << ' DESC' if descending
318
+ order_column
319
+ end
320
+
321
+ def condition_statement(query, operator, left_condition, right_condition)
322
+ return left_condition if operator == :raw
323
+
324
+ qualify = query.links.any?
325
+
326
+ conditions = [ left_condition, right_condition ].map do |condition|
327
+ if condition.kind_of?(Property) || condition.kind_of?(Query::Path)
328
+ property_to_column_name(query.repository, condition, qualify)
329
+ elsif condition.kind_of?(Query)
330
+ opposite = condition == left_condition ? right_condition : left_condition
331
+ query.merge_subquery(operator, opposite, condition)
332
+ "(#{read_statement(condition)})"
333
+
334
+ # [].all? is always true
335
+ elsif condition.kind_of?(Array) && condition.any? && condition.all? { |p| p.kind_of?(Property) }
336
+ property_values = condition.map { |p| property_to_column_name(query.repository, p, qualify) }
337
+ "(#{property_values * ', '})"
338
+ else
339
+ '?'
340
+ end
341
+ end
342
+
343
+ comparison = case operator
344
+ when :eql, :in then equality_operator(right_condition)
345
+ when :not then inequality_operator(right_condition)
346
+ when :like then 'LIKE'
347
+ when :gt then '>'
348
+ when :gte then '>='
349
+ when :lt then '<'
350
+ when :lte then '<='
351
+ else raise "Invalid query operator: #{operator.inspect}"
352
+ end
353
+
354
+ "(" + (conditions * " #{comparison} ") + ")"
355
+ end
356
+
357
+ def equality_operator(operand)
358
+ case operand
359
+ when Array, Query then 'IN'
360
+ when Range then 'BETWEEN'
361
+ when NilClass then 'IS'
362
+ else '='
363
+ end
364
+ end
365
+
366
+ def inequality_operator(operand)
367
+ case operand
368
+ when Array, Query then 'NOT IN'
369
+ when Range then 'NOT BETWEEN'
370
+ when NilClass then 'IS NOT'
371
+ else '<>'
372
+ end
373
+ end
374
+
375
+ def property_to_column_name(repository, property, qualify)
376
+ table_name = property.model.storage_name(repository.name) if property && property.respond_to?(:model)
377
+
378
+ if table_name && qualify
379
+ "#{quote_table_name(table_name)}.#{quote_column_name(property.field(repository.name))}"
380
+ else
381
+ quote_column_name(property.field(repository.name))
382
+ end
383
+ end
384
+
385
+ # TODO: once the driver's quoting methods become public, have
386
+ # this method delegate to them instead
387
+ def quote_table_name(table_name)
388
+ table_name.gsub('"', '""').split('.').map { |part| "\"#{part}\"" } * '.'
389
+ end
390
+
391
+ # TODO: once the driver's quoting methods become public, have
392
+ # this method delegate to them instead
393
+ def quote_column_name(column_name)
394
+ "\"#{column_name.gsub('"', '""')}\""
395
+ end
396
+
397
+ # TODO: once the driver's quoting methods become public, have
398
+ # this method delegate to them instead
399
+ def quote_column_value(column_value)
400
+ return 'NULL' if column_value.nil?
401
+
402
+ case column_value
403
+ when String
404
+ if (integer = column_value.to_i).to_s == column_value
405
+ quote_column_value(integer)
406
+ elsif (float = column_value.to_f).to_s == column_value
407
+ quote_column_value(integer)
408
+ else
409
+ "'#{column_value.gsub("'", "''")}'"
410
+ end
411
+ when DateTime
412
+ quote_column_value(column_value.strftime('%Y-%m-%d %H:%M:%S'))
413
+ when Date
414
+ quote_column_value(column_value.strftime('%Y-%m-%d'))
415
+ when Time
416
+ quote_column_value(column_value.strftime('%Y-%m-%d %H:%M:%S') + ((column_value.usec > 0 ? ".#{column_value.usec.to_s.rjust(6, '0')}" : '')))
417
+ when Integer, Float
418
+ column_value.to_s
419
+ when BigDecimal
420
+ column_value.to_s('F')
421
+ else
422
+ column_value.to_s
423
+ end
424
+ end
425
+ end #module SQL
426
+
427
+ include SQL
428
+
429
+ # TODO: move to dm-more/dm-migrations
430
+ module Migration
431
+ # TODO: move to dm-more/dm-migrations
432
+ def upgrade_model_storage(repository, model)
433
+ table_name = model.storage_name(repository.name)
434
+
435
+ if success = create_model_storage(repository, model)
436
+ return model.properties(repository.name)
437
+ end
438
+
439
+ properties = []
440
+
441
+ model.properties(repository.name).each do |property|
442
+ schema_hash = property_schema_hash(repository, property)
443
+ next if field_exists?(table_name, schema_hash[:name])
444
+ statement = alter_table_add_column_statement(table_name, schema_hash)
445
+ execute(statement)
446
+ properties << property
447
+ end
448
+
449
+ properties
450
+ end
451
+
452
+ # TODO: move to dm-more/dm-migrations
453
+ def create_model_storage(repository, model)
454
+ return false if storage_exists?(model.storage_name(repository.name))
455
+
456
+ execute(create_table_statement(repository, model))
457
+
458
+ (create_index_statements(repository, model) + create_unique_index_statements(repository, model)).each do |sql|
459
+ execute(sql)
460
+ end
461
+
462
+ true
463
+ end
464
+
465
+ # TODO: move to dm-more/dm-migrations
466
+ def destroy_model_storage(repository, model)
467
+ execute(drop_table_statement(repository, model))
468
+ true
469
+ end
470
+
471
+ # TODO: move to dm-more/dm-transactions
472
+ def transaction_primitive
473
+ DataObjects::Transaction.create_for_uri(@uri)
474
+ end
475
+
476
+ module SQL
477
+ private
478
+
479
+ # Adapters that support AUTO INCREMENT fields for CREATE TABLE
480
+ # statements should overwrite this to return true
481
+ #
482
+ # TODO: move to dm-more/dm-migrations
483
+ def supports_serial?
484
+ false
485
+ end
486
+
487
+ # TODO: move to dm-more/dm-migrations
488
+ def alter_table_add_column_statement(table_name, schema_hash)
489
+ "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{property_schema_statement(schema_hash)}"
490
+ end
491
+
492
+ # TODO: move to dm-more/dm-migrations
493
+ def create_table_statement(repository, model)
494
+ repository_name = repository.name
495
+
496
+ statement = <<-EOS.compress_lines
497
+ CREATE TABLE #{quote_table_name(model.storage_name(repository_name))}
498
+ (#{model.properties_with_subclasses(repository_name).map { |p| property_schema_statement(property_schema_hash(repository, p)) } * ', '}
499
+ EOS
500
+
501
+ if (key = model.key(repository_name)).any?
502
+ statement << ", PRIMARY KEY(#{ key.map { |p| quote_column_name(p.field(repository_name)) } * ', '})"
503
+ end
504
+
505
+ statement << ')'
506
+ statement
507
+ end
508
+
509
+ # TODO: move to dm-more/dm-migrations
510
+ def drop_table_statement(repository, model)
511
+ "DROP TABLE IF EXISTS #{quote_table_name(model.storage_name(repository.name))}"
512
+ end
513
+
514
+ # TODO: move to dm-more/dm-migrations
515
+ def create_index_statements(repository, model)
516
+ table_name = model.storage_name(repository.name)
517
+ model.properties(repository.name).indexes.map do |index_name, fields|
518
+ <<-EOS.compress_lines
519
+ CREATE INDEX #{quote_column_name("index_#{table_name}_#{index_name}")} ON
520
+ #{quote_table_name(table_name)} (#{fields.map { |f| quote_column_name(f) } * ', '})
521
+ EOS
522
+ end
523
+ end
524
+
525
+ # TODO: move to dm-more/dm-migrations
526
+ def create_unique_index_statements(repository, model)
527
+ table_name = model.storage_name(repository.name)
528
+ model.properties(repository.name).unique_indexes.map do |index_name, fields|
529
+ <<-EOS.compress_lines
530
+ CREATE UNIQUE INDEX #{quote_column_name("unique_index_#{table_name}_#{index_name}")} ON
531
+ #{quote_table_name(table_name)} (#{fields.map { |f| quote_column_name(f) } * ', '})
532
+ EOS
533
+ end
534
+ end
535
+
536
+ # TODO: move to dm-more/dm-migrations
537
+ def property_schema_hash(repository, property)
538
+ schema = self.class.type_map[property.type].merge(:name => property.field(repository.name))
539
+ # TODO: figure out a way to specify the size not be included, even if
540
+ # a default is defined in the typemap
541
+ # - use this to make it so all TEXT primitive fields do not have size
542
+ if property.primitive == String && schema[:primitive] != 'TEXT'
543
+ schema[:size] = property.length
544
+ elsif property.primitive == BigDecimal || property.primitive == Float
545
+ schema[:precision] = property.precision
546
+ schema[:scale] = property.scale
547
+ end
548
+
549
+ schema[:nullable?] = property.nullable?
550
+ schema[:serial?] = property.serial?
551
+
552
+ if property.default.nil? || property.default.respond_to?(:call)
553
+ # remove the default if the property is not nullable
554
+ schema.delete(:default) unless property.nullable?
555
+ else
556
+ if property.type.respond_to?(:dump)
557
+ schema[:default] = property.type.dump(property.default, property)
558
+ else
559
+ schema[:default] = property.default
560
+ end
561
+ end
562
+
563
+ schema
564
+ end
565
+
566
+ # TODO: move to dm-more/dm-migrations
567
+ def property_schema_statement(schema)
568
+ statement = quote_column_name(schema[:name])
569
+ statement << " #{schema[:primitive]}"
570
+
571
+ if schema[:precision] && schema[:scale]
572
+ statement << "(#{[ :precision, :scale ].map { |k| quote_column_value(schema[k]) } * ','})"
573
+ elsif schema[:size]
574
+ statement << "(#{quote_column_value(schema[:size])})"
575
+ end
576
+
577
+ statement << ' NOT NULL' unless schema[:nullable?]
578
+ statement << " DEFAULT #{quote_column_value(schema[:default])}" if schema.has_key?(:default)
579
+ statement
580
+ end
581
+
582
+ # TODO: move to dm-more/dm-migrations
583
+ def relationship_schema_hash(relationship)
584
+ identifier, relationship = relationship
585
+
586
+ self.class.type_map[Integer].merge(:name => "#{identifier}_id") if identifier == relationship.name
587
+ end
588
+
589
+ # TODO: move to dm-more/dm-migrations
590
+ def relationship_schema_statement(hash)
591
+ property_schema_statement(hash) unless hash.nil?
592
+ end
593
+ end # module SQL
594
+
595
+ include SQL
596
+
597
+ module ClassMethods
598
+ # Default TypeMap for all data object based adapters.
599
+ #
600
+ # @return <DataMapper::TypeMap> default TypeMap for data objects adapters.
601
+ #
602
+ # TODO: move to dm-more/dm-migrations
603
+ def type_map
604
+ @type_map ||= TypeMap.new(super) do |tm|
605
+ tm.map(Integer).to('INT')
606
+ tm.map(String).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
607
+ tm.map(Class).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
608
+ tm.map(DM::Discriminator).to('VARCHAR').with(:size => Property::DEFAULT_LENGTH)
609
+ tm.map(BigDecimal).to('DECIMAL').with(:precision => Property::DEFAULT_PRECISION, :scale => Property::DEFAULT_SCALE_BIGDECIMAL)
610
+ tm.map(Float).to('FLOAT').with(:precision => Property::DEFAULT_PRECISION)
611
+ tm.map(DateTime).to('DATETIME')
612
+ tm.map(Date).to('DATE')
613
+ tm.map(Time).to('TIMESTAMP')
614
+ tm.map(TrueClass).to('BOOLEAN')
615
+ tm.map(DM::Object).to('TEXT')
616
+ tm.map(DM::Text).to('TEXT')
617
+ end
618
+ end
619
+ end # module ClassMethods
620
+ end # module Migration
621
+
622
+ include Migration
623
+ extend Migration::ClassMethods
624
+ end # class DataObjectsAdapter
625
+ end # module Adapters
626
+
627
+ # TODO: move to dm-ar-finders
628
+ module Model
629
+ #
630
+ # Find instances by manually providing SQL
631
+ #
632
+ # @param sql<String> an SQL query to execute
633
+ # @param <Array> an Array containing a String (being the SQL query to
634
+ # execute) and the parameters to the query.
635
+ # example: ["SELECT name FROM users WHERE id = ?", id]
636
+ # @param query<DataMapper::Query> a prepared Query to execute.
637
+ # @param opts<Hash> an options hash.
638
+ # :repository<Symbol> the name of the repository to execute the query
639
+ # in. Defaults to self.default_repository_name.
640
+ # :reload<Boolean> whether to reload any instances found that already
641
+ # exist in the identity map. Defaults to false.
642
+ # :properties<Array> the Properties of the instance that the query
643
+ # loads. Must contain DataMapper::Properties.
644
+ # Defaults to self.properties.
645
+ #
646
+ # @note
647
+ # A String, Array or Query is required.
648
+ # @return <Collection> the instance matched by the query.
649
+ #
650
+ # @example
651
+ # MyClass.find_by_sql(["SELECT id FROM my_classes WHERE county = ?",
652
+ # selected_county], :properties => MyClass.property[:id],
653
+ # :repository => :county_repo)
654
+ #
655
+ # -
656
+ # @api public
657
+ def find_by_sql(*args)
658
+ sql = nil
659
+ query = nil
660
+ bind_values = []
661
+ properties = nil
662
+ do_reload = false
663
+ repository_name = default_repository_name
664
+ args.each do |arg|
665
+ if arg.is_a?(String)
666
+ sql = arg
667
+ elsif arg.is_a?(Array)
668
+ sql = arg.first
669
+ bind_values = arg[1..-1]
670
+ elsif arg.is_a?(DataMapper::Query)
671
+ query = arg
672
+ elsif arg.is_a?(Hash)
673
+ repository_name = arg.delete(:repository) if arg.include?(:repository)
674
+ properties = Array(arg.delete(:properties)) if arg.include?(:properties)
675
+ do_reload = arg.delete(:reload) if arg.include?(:reload)
676
+ raise "unknown options to #find_by_sql: #{arg.inspect}" unless arg.empty?
677
+ end
678
+ end
679
+
680
+ repository = repository(repository_name)
681
+ raise "#find_by_sql only available for Repositories served by a DataObjectsAdapter" unless repository.adapter.is_a?(DataMapper::Adapters::DataObjectsAdapter)
682
+
683
+ if query
684
+ sql = repository.adapter.send(:read_statement, query)
685
+ bind_values = query.bind_values
686
+ end
687
+
688
+ raise "#find_by_sql requires a query of some kind to work" unless sql
689
+
690
+ properties ||= self.properties(repository.name)
691
+
692
+ Collection.new(Query.new(repository, self)) do |collection|
693
+ repository.adapter.send(:with_connection) do |connection|
694
+ command = connection.create_command(sql)
695
+
696
+ begin
697
+ reader = command.execute_reader(*bind_values)
698
+
699
+ while(reader.next!)
700
+ collection.load(reader.values)
701
+ end
702
+ ensure
703
+ reader.close if reader
704
+ end
705
+ end
706
+ end
707
+ end
708
+ end # module Model
709
+ end # module DataMapper