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,628 @@
1
+ module DataMapper
2
+ class Query
3
+ include Assertions
4
+
5
+ OPTIONS = [
6
+ :reload, :offset, :limit, :order, :add_reversed, :fields, :links, :includes, :conditions, :unique
7
+ ]
8
+
9
+ attr_reader :repository, :model, *OPTIONS - [ :reload, :unique ]
10
+ attr_writer :add_reversed
11
+ alias add_reversed? add_reversed
12
+
13
+ def reload?
14
+ @reload
15
+ end
16
+
17
+ def unique?
18
+ @unique
19
+ end
20
+
21
+ def reverse
22
+ dup.reverse!
23
+ end
24
+
25
+ def reverse!
26
+ # reverse the sort order
27
+ update(:order => self.order.map { |o| o.reverse })
28
+
29
+ self
30
+ end
31
+
32
+ def update(other)
33
+ assert_kind_of 'other', other, self.class, Hash
34
+
35
+ assert_valid_other(other)
36
+
37
+ if other.kind_of?(Hash)
38
+ return self if other.empty?
39
+ other = self.class.new(@repository, model, other)
40
+ end
41
+
42
+ return self if self == other
43
+
44
+ # TODO: update this so if "other" had a value explicitly set
45
+ # overwrite the attributes in self
46
+
47
+ # only overwrite the attributes with non-default values
48
+ @reload = other.reload? unless other.reload? == false
49
+ @unique = other.unique? unless other.unique? == false
50
+ @offset = other.offset if other.reload? || other.offset != 0
51
+ @limit = other.limit unless other.limit == nil
52
+ @order = other.order unless other.order == model.default_order
53
+ @add_reversed = other.add_reversed? unless other.add_reversed? == false
54
+ @links = other.links unless other.links == []
55
+ @includes = other.includes unless other.includes == []
56
+
57
+ @fields == @properties.defaults ? @fields = other.fields : @fields |= other.fields
58
+
59
+ update_conditions(other)
60
+
61
+ self
62
+ end
63
+
64
+ def merge(other)
65
+ dup.update(other)
66
+ end
67
+
68
+ def ==(other)
69
+ return true if super
70
+ return false unless other.kind_of?(self.class)
71
+
72
+ # TODO: add a #hash method, and then use it in the comparison, eg:
73
+ # return hash == other.hash
74
+ @model == other.model &&
75
+ @reload == other.reload? &&
76
+ @unique == other.unique? &&
77
+ @offset == other.offset &&
78
+ @limit == other.limit &&
79
+ @order == other.order && # order is significant, so do not sort this
80
+ @add_reversed == other.add_reversed? &&
81
+ @fields == other.fields && # TODO: sort this so even if the order is different, it is equal
82
+ @links == other.links && # TODO: sort this so even if the order is different, it is equal
83
+ @includes == other.includes && # TODO: sort this so even if the order is different, it is equal
84
+ @conditions.sort_by { |c| c.at(0).hash + c.at(1).hash + c.at(2).hash } == other.conditions.sort_by { |c| c.at(0).hash + c.at(1).hash + c.at(2).hash }
85
+ end
86
+
87
+ alias eql? ==
88
+
89
+ def bind_values
90
+ bind_values = []
91
+ conditions.each do |tuple|
92
+ next if tuple.size == 2
93
+ operator, property, bind_value = *tuple
94
+ if :raw == operator
95
+ bind_values.push(*bind_value)
96
+ else
97
+ bind_values << bind_value
98
+ end
99
+ end
100
+ bind_values
101
+ end
102
+
103
+ def inheritance_property
104
+ fields.detect { |property| property.type == DataMapper::Types::Discriminator }
105
+ end
106
+
107
+ def inheritance_property_index
108
+ fields.index(inheritance_property)
109
+ end
110
+
111
+ # TODO: spec this
112
+ def key_property_indexes(repository)
113
+ if (key_property_indexes = model.key(repository.name).map { |property| fields.index(property) }).all?
114
+ key_property_indexes
115
+ end
116
+ end
117
+
118
+ # find the point in self.conditions where the sub select tuple is
119
+ # located. Delete the tuple and add value.conditions. value must be a
120
+ # <DM::Query>
121
+ #
122
+ def merge_subquery(operator, property, value)
123
+ assert_kind_of 'value', value, self.class
124
+
125
+ new_conditions = []
126
+ conditions.each do |tuple|
127
+ if tuple.at(0).to_s == operator.to_s && tuple.at(1) == property && tuple.at(2) == value
128
+ value.conditions.each do |subquery_tuple|
129
+ new_conditions << subquery_tuple
130
+ end
131
+ else
132
+ new_conditions << tuple
133
+ end
134
+ end
135
+ @conditions = new_conditions
136
+ end
137
+
138
+ def inspect
139
+ attrs = [
140
+ [ :repository, repository.name ],
141
+ [ :model, model ],
142
+ [ :fields, fields ],
143
+ [ :links, links ],
144
+ [ :conditions, conditions ],
145
+ [ :order, order ],
146
+ [ :limit, limit ],
147
+ [ :offset, offset ],
148
+ [ :reload, reload? ],
149
+ [ :unique, unique? ],
150
+ ]
151
+
152
+ "#<#{self.class.name} #{attrs.map { |(k,v)| "@#{k}=#{v.inspect}" } * ' '}>"
153
+ end
154
+
155
+ private
156
+
157
+ def initialize(repository, model, options = {})
158
+ assert_kind_of 'repository', repository, Repository
159
+ assert_kind_of 'model', model, Model
160
+ assert_kind_of 'options', options, Hash
161
+
162
+ options.each_pair { |k,v| options[k] = v.call if v.is_a? Proc } if options.is_a? Hash
163
+
164
+ assert_valid_options(options)
165
+
166
+ @repository = repository
167
+ @properties = model.properties(@repository.name)
168
+
169
+ @model = model # must be Class that includes DM::Resource
170
+ @reload = options.fetch :reload, false # must be true or false
171
+ @unique = options.fetch :unique, false # must be true or false
172
+ @offset = options.fetch :offset, 0 # must be an Integer greater than or equal to 0
173
+ @limit = options.fetch :limit, nil # must be an Integer greater than or equal to 1
174
+ @order = options.fetch :order, model.default_order(@repository.name) # must be an Array of Symbol, DM::Query::Direction or DM::Property
175
+ @add_reversed = options.fetch :add_reversed, false # must be true or false
176
+ @fields = options.fetch :fields, @properties.defaults # must be an Array of Symbol, String or DM::Property
177
+ @links = options.fetch :links, [] # must be an Array of Tuples - Tuple [DM::Query,DM::Assoc::Relationship]
178
+ @includes = options.fetch :includes, [] # must be an Array of DM::Query::Path
179
+ @conditions = [] # must be an Array of triplets (or pairs when passing in raw String queries)
180
+
181
+ # normalize order and fields
182
+ @order = normalize_order(@order)
183
+ @fields = normalize_fields(@fields)
184
+
185
+ # XXX: should I validate that each property in @order corresponds
186
+ # to something in @fields? Many DB engines require they match,
187
+ # and I can think of no valid queries where a field would be so
188
+ # important that you sort on it, but not important enough to
189
+ # return.
190
+
191
+ # normalize links and includes.
192
+ # NOTE: this must be done after order and fields
193
+ @links = normalize_links(@links)
194
+ @includes = normalize_includes(@includes)
195
+
196
+ # treat all non-options as conditions
197
+ (options.keys - OPTIONS).each do |k|
198
+ append_condition(k, options[k])
199
+ end
200
+
201
+ # parse raw options[:conditions] differently
202
+ if conditions = options[:conditions]
203
+ if conditions.kind_of?(Hash)
204
+ conditions.each do |k,v|
205
+ append_condition(k, v)
206
+ end
207
+ elsif conditions.kind_of?(Array)
208
+ raw_query, *bind_values = conditions
209
+ @conditions << if bind_values.empty?
210
+ [ :raw, raw_query ]
211
+ else
212
+ [ :raw, raw_query, bind_values ]
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ def initialize_copy(original)
219
+ # deep-copy the condition tuples when copying the object
220
+ @conditions = original.conditions.map { |tuple| tuple.dup }
221
+ end
222
+
223
+ # validate the options
224
+ def assert_valid_options(options)
225
+ # [DB] This might look more ugly now, but it's 2x as fast as the old code
226
+ # [DB] This is one of the heavy spots for Query.new I found during profiling.
227
+ options.each_pair do |attribute, value|
228
+
229
+ # validate the reload option and unique option
230
+ if [:reload, :unique].include? attribute
231
+ if value != true && value != false
232
+ raise ArgumentError, "+options[:#{attribute}]+ must be true or false, but was #{value.inspect}", caller(2)
233
+ end
234
+
235
+ # validate the offset and limit options
236
+ elsif [:offset, :limit].include? attribute
237
+ assert_kind_of "options[:#{attribute}]", value, Integer
238
+ if attribute == :offset && value < 0
239
+ raise ArgumentError, "+options[:offset]+ must be greater than or equal to 0, but was #{value.inspect}", caller(2)
240
+ elsif attribute == :limit && value < 1
241
+ raise ArgumentError, "+options[:limit]+ must be greater than or equal to 1, but was #{options[:limit].inspect}", caller(2)
242
+ end
243
+
244
+ # validate the :order, :fields, :links and :includes options
245
+ elsif [ :order, :fields, :links, :includes ].include? attribute
246
+ assert_kind_of "options[:#{attribute}]", value, Array
247
+
248
+ if value.empty?
249
+ if attribute == :fields
250
+ if options[:unique] == false
251
+ raise ArgumentError, '+options[:fields]+ cannot be empty if +options[:unique] is false', caller(2)
252
+ end
253
+ elsif attribute == :order
254
+ if options[:fields] && options[:fields].any? { |p| !p.kind_of?(Operator) }
255
+ raise ArgumentError, '+options[:order]+ cannot be empty if +options[:fields] contains a non-operator', caller(2)
256
+ end
257
+ else
258
+ raise ArgumentError, "+options[:#{attribute}]+ cannot be empty", caller(2)
259
+ end
260
+ end
261
+
262
+ # validates the :conditions option
263
+ elsif :conditions == attribute
264
+ assert_kind_of 'options[:conditions]', value, Hash, Array
265
+
266
+ if value.empty?
267
+ raise ArgumentError, '+options[:conditions]+ cannot be empty', caller(2)
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ # validate other DM::Query or Hash object
274
+ def assert_valid_other(other)
275
+ return unless other.kind_of?(self.class)
276
+
277
+ unless other.repository == repository
278
+ raise ArgumentError, "+other+ #{self.class} must be for the #{repository.name} repository, not #{other.repository.name}", caller(2)
279
+ end
280
+
281
+ unless other.model == model
282
+ raise ArgumentError, "+other+ #{self.class} must be for the #{model.name} model, not #{other.model.name}", caller(2)
283
+ end
284
+ end
285
+
286
+ # normalize order elements to DM::Query::Direction
287
+ def normalize_order(order)
288
+ order.map do |order_by|
289
+ case order_by
290
+ when Direction
291
+ # NOTE: The property is available via order_by.property
292
+ # TODO: if the Property's model doesn't match
293
+ # self.model, append the property's model to @links
294
+ # eg:
295
+ #if property.model != self.model
296
+ # @links << discover_path_for_property(property)
297
+ #end
298
+
299
+ order_by
300
+ when Property
301
+ # TODO: if the Property's model doesn't match
302
+ # self.model, append the property's model to @links
303
+ # eg:
304
+ #if property.model != self.model
305
+ # @links << discover_path_for_property(property)
306
+ #end
307
+
308
+ Direction.new(order_by)
309
+ when Operator
310
+ property = @properties[order_by.target]
311
+ Direction.new(property, order_by.operator)
312
+ when Symbol, String
313
+ property = @properties[order_by]
314
+
315
+ if property.nil?
316
+ raise ArgumentError, "+options[:order]+ entry #{order_by} does not map to a DataMapper::Property", caller(2)
317
+ end
318
+
319
+ Direction.new(property)
320
+ else
321
+ raise ArgumentError, "+options[:order]+ entry #{order_by.inspect} not supported", caller(2)
322
+ end
323
+ end
324
+ end
325
+
326
+ # normalize fields to DM::Property
327
+ def normalize_fields(fields)
328
+ # TODO: return a PropertySet
329
+ # TODO: raise an exception if the property is not available in the repository
330
+ fields.map do |field|
331
+ case field
332
+ when Property, Operator
333
+ # TODO: if the Property's model doesn't match
334
+ # self.model, append the property's model to @links
335
+ # eg:
336
+ #if property.model != self.model
337
+ # @links << discover_path_for_property(property)
338
+ #end
339
+ field
340
+ when Symbol, String
341
+ property = @properties[field]
342
+
343
+ if property.nil?
344
+ raise ArgumentError, "+options[:fields]+ entry #{field} does not map to a DataMapper::Property", caller(2)
345
+ end
346
+
347
+ property
348
+ else
349
+ raise ArgumentError, "+options[:fields]+ entry #{field.inspect} not supported", caller(2)
350
+ end
351
+ end
352
+ end
353
+
354
+ # normalize links to DM::Query::Path
355
+ def normalize_links(links)
356
+ # XXX: this should normalize to DM::Query::Path, not DM::Association::Relationship
357
+ # because a link may be more than one-hop-away from the source. A DM::Query::Path
358
+ # should include an Array of Relationship objects that trace the "path" between
359
+ # the source and the target.
360
+ links.map do |link|
361
+ case link
362
+ when Associations::Relationship
363
+ link
364
+ when Symbol, String
365
+ link = link.to_sym if link.kind_of?(String)
366
+
367
+ unless model.relationships(@repository.name).has_key?(link)
368
+ raise ArgumentError, "+options[:links]+ entry #{link} does not map to a DataMapper::Associations::Relationship", caller(2)
369
+ end
370
+
371
+ model.relationships(@repository.name)[link]
372
+ else
373
+ raise ArgumentError, "+options[:links]+ entry #{link.inspect} not supported", caller(2)
374
+ end
375
+ end
376
+ end
377
+
378
+ # normalize includes to DM::Query::Path
379
+ def normalize_includes(includes)
380
+ # TODO: normalize Array of Symbol, String, DM::Property 1-jump-away or DM::Query::Path
381
+ # NOTE: :includes can only be and array of DM::Query::Path objects now. This method
382
+ # can go away after review of what has been done.
383
+ includes
384
+ end
385
+
386
+ # validate that all the links or includes are present for the given DM::Query::Path
387
+ #
388
+ def validate_query_path_links(path)
389
+ path.relationships.map do |relationship|
390
+ @links << relationship unless (@links.include?(relationship) || @includes.include?(relationship))
391
+ end
392
+ end
393
+
394
+ def append_condition(clause, bind_value)
395
+ operator = :eql
396
+ bind_value = bind_value.call if bind_value.is_a?(Proc)
397
+
398
+ property = case clause
399
+ when Property
400
+ clause
401
+ when Query::Path
402
+ validate_query_path_links(clause)
403
+ clause
404
+ when Operator
405
+ operator = clause.operator
406
+ return if operator == :not && bind_value == []
407
+ if clause.target.is_a?(Symbol)
408
+ @properties[clause.target]
409
+ elsif clause.target.is_a?(Query::Path)
410
+ validate_query_path_links(clause.target)
411
+ clause.target
412
+ end
413
+ when Symbol
414
+ @properties[clause]
415
+ when String
416
+ if clause =~ /\w\.\w/
417
+ query_path = @model
418
+ clause.split(".").each { |piece| query_path = query_path.send(piece) }
419
+ append_condition(query_path, bind_value)
420
+ return
421
+ else
422
+ @properties[clause]
423
+ end
424
+ else
425
+ raise ArgumentError, "Condition type #{clause.inspect} not supported", caller(2)
426
+ end
427
+
428
+ if property.nil?
429
+ raise ArgumentError, "Clause #{clause.inspect} does not map to a DataMapper::Property", caller(2)
430
+ end
431
+
432
+ bind_value = dump_custom_value(property, bind_value)
433
+
434
+ @conditions << [ operator, property, bind_value ]
435
+ end
436
+
437
+ def dump_custom_value(property_or_path, bind_value)
438
+ case property_or_path
439
+ when DataMapper::Query::Path
440
+ dump_custom_value(property_or_path.property, bind_value)
441
+ when Property
442
+ if property_or_path.custom?
443
+ property_or_path.type.dump(bind_value, property_or_path)
444
+ else
445
+ bind_value
446
+ end
447
+ else
448
+ bind_value
449
+ end
450
+ end
451
+
452
+ # TODO: check for other mutually exclusive operator + property
453
+ # combinations. For example if self's conditions were
454
+ # [ :gt, :amount, 5 ] and the other's condition is [ :lt, :amount, 2 ]
455
+ # there is a conflict. When in conflict the other's conditions
456
+ # overwrites self's conditions.
457
+
458
+ # TODO: Another condition is when the other condition operator is
459
+ # eql, this should over-write all the like,range and list operators
460
+ # for the same property, since we are now looking for an exact match.
461
+ # Vice versa, passing in eql should overwrite all of those operators.
462
+
463
+ def update_conditions(other)
464
+ @conditions = @conditions.dup
465
+
466
+ # build an index of conditions by the property and operator to
467
+ # avoid nested looping
468
+ conditions_index = {}
469
+ @conditions.each do |condition|
470
+ operator, property = *condition
471
+ next if :raw == operator
472
+ conditions_index[property] ||= {}
473
+ conditions_index[property][operator] = condition
474
+ end
475
+
476
+ # loop over each of the other's conditions, and overwrite the
477
+ # conditions when in conflict
478
+ other.conditions.each do |other_condition|
479
+ other_operator, other_property, other_bind_value = *other_condition
480
+
481
+ unless :raw == other_operator
482
+ conditions_index[other_property] ||= {}
483
+ if condition = conditions_index[other_property][other_operator]
484
+ operator, property, bind_value = *condition
485
+
486
+ next if bind_value == other_bind_value
487
+
488
+ # overwrite the bind value in the existing condition
489
+ condition[2] = case operator
490
+ when :eql, :like then other_bind_value
491
+ when :gt, :gte then [ bind_value, other_bind_value ].min
492
+ when :lt, :lte then [ bind_value, other_bind_value ].max
493
+ when :not, :in
494
+ if bind_value.kind_of?(Array)
495
+ bind_value |= other_bind_value
496
+ elsif other_bind_value.kind_of?(Array)
497
+ other_bind_value |= bind_value
498
+ else
499
+ other_bind_value
500
+ end
501
+ end
502
+
503
+ next # process the next other condition
504
+ end
505
+ end
506
+
507
+ # otherwise append the other condition
508
+ @conditions << other_condition.dup
509
+ end
510
+
511
+ @conditions
512
+ end
513
+
514
+ class Direction
515
+ include Assertions
516
+
517
+ attr_reader :property, :direction
518
+
519
+ def ==(other)
520
+ return true if super
521
+ hash == other.hash
522
+ end
523
+
524
+ alias eql? ==
525
+
526
+ def hash
527
+ @property.hash + @direction.hash
528
+ end
529
+
530
+ def reverse
531
+ self.class.new(@property, @direction == :asc ? :desc : :asc)
532
+ end
533
+
534
+ def inspect
535
+ "#<#{self.class.name} #{@property.inspect} #{@direction}>"
536
+ end
537
+
538
+ private
539
+
540
+ def initialize(property, direction = :asc)
541
+ assert_kind_of 'property', property, Property
542
+ assert_kind_of 'direction', direction, Symbol
543
+
544
+ @property = property
545
+ @direction = direction
546
+ end
547
+ end # class Direction
548
+
549
+ class Operator
550
+ include Assertions
551
+
552
+ attr_reader :target, :operator
553
+
554
+ def to_sym
555
+ @property_name
556
+ end
557
+
558
+ def ==(other)
559
+ return true if super
560
+ return false unless other.kind_of?(self.class)
561
+ @operator == other.operator && @target == other.target
562
+ end
563
+
564
+ private
565
+
566
+ def initialize(target, operator)
567
+ assert_kind_of 'operator', operator, Symbol
568
+
569
+ @target = target
570
+ @operator = operator
571
+ end
572
+ end # class Operator
573
+
574
+ class Path
575
+ include Assertions
576
+
577
+ %w[ id type ].each { |m| undef_method m }
578
+
579
+ attr_reader :relationships, :model, :property, :operator
580
+
581
+ [ :gt, :gte, :lt, :lte, :not, :eql, :like, :in ].each do |sym|
582
+ class_eval <<-EOS, __FILE__, __LINE__
583
+ def #{sym}
584
+ Operator.new(self, :#{sym})
585
+ end
586
+ EOS
587
+ end
588
+
589
+ # duck type the DM::Query::Path to act like a DM::Property
590
+ def field(*args)
591
+ @property ? @property.field(*args) : nil
592
+ end
593
+
594
+ # more duck typing
595
+ def to_sym
596
+ @property ? @property.name.to_sym : @model.storage_name(@repository).to_sym
597
+ end
598
+
599
+ private
600
+
601
+ def initialize(repository, relationships, model, property_name = nil)
602
+ assert_kind_of 'repository', repository, Repository
603
+ assert_kind_of 'relationships', relationships, Array
604
+ assert_kind_of 'model', model, Model
605
+ assert_kind_of 'property_name', property_name, Symbol unless property_name.nil?
606
+
607
+ @repository = repository
608
+ @relationships = relationships
609
+ @model = model
610
+ @property = @model.properties(@repository.name)[property_name] if property_name
611
+ end
612
+
613
+ def method_missing(method, *args)
614
+ if relationship = @model.relationships(@repository.name)[method]
615
+ klass = klass = model == relationship.child_model ? relationship.parent_model : relationship.child_model
616
+ return Query::Path.new(@repository, @relationships + [ relationship ], klass)
617
+ end
618
+
619
+ if @model.properties(@repository.name)[method]
620
+ @property = @model.properties(@repository.name)[method] unless @property
621
+ return self
622
+ end
623
+
624
+ raise NoMethodError, "undefined property or association `#{method}' on #{@model}"
625
+ end
626
+ end # class Path
627
+ end # class Query
628
+ end # module DataMapper