dm-core 0.10.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -1,42 +0,0 @@
1
- require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
-
3
- require 'do_mysql'
4
-
5
- module DataMapper
6
- module Adapters
7
- class MysqlAdapter < DataObjectsAdapter
8
- module SQL #:nodoc:
9
- IDENTIFIER_MAX_LENGTH = 64
10
-
11
- private
12
-
13
- # @api private
14
- def supports_default_values? #:nodoc:
15
- false
16
- end
17
-
18
- # @api private
19
- def supports_subquery?(query, source_key, target_key, qualify)
20
- # TODO: renable once query does not include target_model for deletes and updates
21
- # query.limit.nil?
22
-
23
- false
24
- end
25
-
26
- # @api private
27
- def regexp_operator(operand)
28
- 'REGEXP'
29
- end
30
-
31
- # @api private
32
- def quote_name(name)
33
- "`#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('`', '``')}`"
34
- end
35
- end #module SQL
36
-
37
- include SQL
38
- end # class MysqlAdapter
39
-
40
- const_added(:MysqlAdapter)
41
- end # module Adapters
42
- end # module DataMapper
@@ -1,229 +0,0 @@
1
- require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
-
3
- require 'do_oracle'
4
-
5
- module DataMapper
6
-
7
- class Property
8
- # for custom sequence names
9
- OPTIONS << :sequence
10
- end
11
-
12
- module Adapters
13
- class OracleAdapter < DataObjectsAdapter
14
- module SQL #:nodoc:
15
- IDENTIFIER_MAX_LENGTH = 30
16
-
17
- private
18
-
19
- # Constructs INSERT statement for given query,
20
- #
21
- # @return [String] INSERT statement as a string
22
- #
23
- # @api private
24
- def insert_statement(model, properties, serial)
25
- statement = "INSERT INTO #{quote_name(model.storage_name(name))} "
26
-
27
- no_properties = properties.empty?
28
- custom_sequence = serial && serial.options[:sequence]
29
- serial_field = serial && quote_name(serial.field)
30
-
31
- if supports_default_values? && no_properties && !custom_sequence
32
- statement << "(#{serial_field}) " if serial
33
- statement << default_values_clause
34
- else
35
- # do not use custom sequence if identity field was assigned a value
36
- if custom_sequence && properties.include?(serial)
37
- custom_sequence = nil
38
- end
39
- statement << "("
40
- if custom_sequence
41
- statement << "#{serial_field}"
42
- statement << ", " unless no_properties
43
- end
44
- statement << "#{properties.map { |property| quote_name(property.field) }.join(', ')}) "
45
- statement << "VALUES ("
46
- if custom_sequence
47
- statement << "#{quote_name(custom_sequence)}.NEXTVAL"
48
- statement << ", " unless no_properties
49
- end
50
- statement << "#{(['?'] * properties.size).join(', ')})"
51
- end
52
-
53
- if supports_returning? && serial
54
- statement << returning_clause(serial)
55
- end
56
-
57
- statement
58
- end
59
-
60
- # Oracle syntax for inserting default values
61
- def default_values_clause
62
- 'VALUES (DEFAULT)'
63
- end
64
-
65
- # @api private
66
- def supports_returning?
67
- true
68
- end
69
-
70
- # INTO :insert_id is recognized by Oracle DataObjects driver
71
- def returning_clause(serial)
72
- " RETURNING #{quote_name(serial.field)} INTO :insert_id"
73
- end
74
-
75
- # Constructs SELECT statement for given query,
76
- # Overrides DataObjects adapter implementation with using subquery instead of GROUP BY to get unique records
77
- #
78
- # @return [String] SELECT statement as a string
79
- #
80
- # @api private
81
- def select_statement(query)
82
- name = self.name
83
- model = query.model
84
- fields = query.fields
85
- conditions = query.conditions
86
- limit = query.limit
87
- offset = query.offset
88
- order = query.order
89
- group_by = nil
90
-
91
- # FIXME: using a boolean for qualify does not work in some cases,
92
- # such as when you have a self-referrential many to many association.
93
- # if you don't qualfiy the columns with a unique alias, then the
94
- # SQL query will fail. This may mean though, that it might not
95
- # be enough to pass in a Property, but we may need to know the
96
- # table and the alias we should use for the column.
97
-
98
- qualify = query.links.any?
99
-
100
- if query.unique?
101
- group_by = fields.select { |property| property.kind_of?(Property) }
102
- end
103
-
104
- # create subquery to find all valid keys and then use these keys to retrive all other columns
105
- use_subquery = qualify
106
- no_group_by = group_by.blank?
107
- no_order = order.blank?
108
-
109
- # when we can include ROWNUM condition in main WHERE clause
110
- use_simple_rownum_limit = limit && (offset||0 == 0) && no_group_by && no_order
111
-
112
- unless (limit && limit > 1) || offset > 0 || qualify
113
- # TODO: move this method to Query, so that it walks the conditions
114
- # and finds an OR operator
115
-
116
- # TODO: handle cases where two or more properties need to be
117
- # used together to be unique
118
-
119
- # if a unique property is used, and there is no OR operator, then an ORDER
120
- # and LIMIT are unecessary because it should only return a single row
121
- if conditions.respond_to?(:slug) && conditions.slug == :and &&
122
- conditions.any? { |operand| operand.respond_to?(:slug) && operand.slug == :eql && operand.subject.respond_to?(:unique?) && operand.subject.unique? } &&
123
- !conditions.any? { |operand| operand.respond_to?(:slug) && operand.slug == :or }
124
- order = nil
125
- no_order = true
126
- limit = nil
127
- end
128
- end
129
-
130
- conditions_statement, bind_values = conditions_statement(conditions, qualify)
131
-
132
- model_key_column = columns_statement(model.key(name), qualify)
133
- from_statement = " FROM #{quote_name(model.storage_name(name))}"
134
-
135
- statement = "SELECT #{columns_statement(fields, qualify)}"
136
- if use_subquery
137
- statement << from_statement
138
- statement << " WHERE (#{model_key_column}) IN"
139
- statement << " (SELECT DISTINCT #{model_key_column}"
140
- # do not need to do group by for uniqueness as just one row per primary key will be returned
141
- no_group_by = true
142
- end
143
- statement << from_statement
144
- statement << join_statement(query, qualify) if qualify
145
- statement << " WHERE (#{conditions_statement})" unless conditions_statement.blank?
146
- if use_subquery
147
- statement << ")"
148
- end
149
- if use_simple_rownum_limit
150
- statement << " AND rownum <= ?"
151
- bind_values << limit
152
- end
153
- statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless no_group_by
154
- statement << " ORDER BY #{order_statement(order, qualify)}" unless no_order
155
-
156
- add_limit_offset!(statement, limit, offset, bind_values) unless use_simple_rownum_limit
157
-
158
- return statement, bind_values
159
- end
160
-
161
- # Oracle does not support LIMIT and OFFSET
162
- # Functionality is mimiced through the use of nested selects.
163
- # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
164
- def add_limit_offset!(statement, limit, offset, bind_values)
165
- positive_offset = offset > 0
166
-
167
- if limit && positive_offset
168
- statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_ where rownum <= ?) where raw_rnum_ > ?"
169
- bind_values << offset + limit << offset
170
- elsif limit
171
- statement.replace "select raw_sql_.* from (#{statement}) raw_sql_ where rownum <= ?"
172
- bind_values << limit
173
- elsif positive_offset
174
- statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_) where raw_rnum_ > ?"
175
- bind_values << offset
176
- end
177
- end
178
-
179
- # @api private
180
- # Oracle does not allow " in table or column names therefore substitute them with underscore
181
- def quote_name(name)
182
- "\"#{oracle_upcase(name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_')}\""
183
- end
184
-
185
- # If table or column name contains just lowercase characters then do uppercase
186
- # as uppercase version will be used in Oracle data dictionary tables
187
- def oracle_upcase(name)
188
- name =~ /[A-Z]/ ? name : name.upcase
189
- end
190
-
191
- # CLOB value should be compared using DBMS_LOB.SUBSTR function
192
- # NOTE: just first 32767 bytes will be compared!
193
- # @api private
194
- def equality_operator(property, operand)
195
- if operand.nil?
196
- 'IS'
197
- elsif property.type == Types::Text
198
- 'DBMS_LOB.SUBSTR(%s) = ?'
199
- else
200
- '='
201
- end
202
- end
203
-
204
- # @api private
205
- def include_operator(property, operand)
206
- operator = case operand
207
- when Array then 'IN'
208
- when Range then 'BETWEEN'
209
- end
210
- if property.type == Types::Text
211
- "DBMS_LOB.SUBSTR(%s) #{operator} ?"
212
- else
213
- operator
214
- end
215
- end
216
-
217
- # @api private
218
- def regexp_operator(operand)
219
- 'REGEXP_LIKE(%s, ?)'
220
- end
221
-
222
- end #module SQL
223
-
224
- include SQL
225
- end # class PostgresAdapter
226
-
227
- const_added(:OracleAdapter)
228
- end # module Adapters
229
- end # module DataMapper
@@ -1,22 +0,0 @@
1
- require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
-
3
- require 'do_postgres'
4
-
5
- module DataMapper
6
- module Adapters
7
- class PostgresAdapter < DataObjectsAdapter
8
- module SQL #:nodoc:
9
- private
10
-
11
- # @api private
12
- def supports_returning?
13
- true
14
- end
15
- end #module SQL
16
-
17
- include SQL
18
- end # class PostgresAdapter
19
-
20
- const_added(:PostgresAdapter)
21
- end # module Adapters
22
- end # module DataMapper
@@ -1,17 +0,0 @@
1
- require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
-
3
- require 'do_sqlite3'
4
-
5
- module DataMapper
6
- module Adapters
7
- class Sqlite3Adapter < DataObjectsAdapter
8
- # @api private
9
- def supports_subquery?(query, source_key, target_key, qualify)
10
- # SQLite3 cannot match a subquery against more than one column
11
- source_key.size == 1 && target_key.size == 1
12
- end
13
- end # class Sqlite3Adapter
14
-
15
- const_added(:Sqlite3Adapter)
16
- end # module Adapters
17
- end # module DataMapper
@@ -1,114 +0,0 @@
1
- require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
-
3
- require 'do_sqlserver'
4
-
5
- DataObjects::Sqlserver = DataObjects::SqlServer
6
-
7
- module DataMapper
8
- module Adapters
9
- class SqlserverAdapter < DataObjectsAdapter
10
- module SQL #:nodoc:
11
- private
12
-
13
- # Constructs INSERT statement for given query,
14
- #
15
- # @return [String] INSERT statement as a string
16
- #
17
- # @api private
18
- def insert_statement(model, properties, serial)
19
- statement = ""
20
- # Check if there is a serial property being set directly
21
- require_identity_insert = !properties.empty? && properties.any? { |property| property.serial? }
22
- set_identity_insert(model, statement, true) if require_identity_insert
23
- statement << super
24
- set_identity_insert(model, statement, false) if require_identity_insert
25
- statement
26
- end
27
-
28
- def set_identity_insert(model, statement, enable = true)
29
- statement << " SET IDENTITY_INSERT #{quote_name(model.storage_name(name))} #{enable ? 'ON' : 'OFF'} "
30
- end
31
-
32
- def select_statement(query)
33
- name = self.name
34
- qualify = query.links.any?
35
- fields = query.fields
36
- offset = query.offset
37
- limit = query.limit
38
- order_by = query.order
39
- group_by = if qualify || query.unique?
40
- fields.select { |property| property.kind_of?(Property) }
41
- end
42
-
43
- conditions_statement, bind_values = conditions_statement(query.conditions, qualify)
44
-
45
- use_limit_offset_subquery = limit && offset > 0
46
-
47
- columns_statement = columns_statement(fields, qualify)
48
- from_statement = " FROM #{quote_name(query.model.storage_name(name))}"
49
- where_statement = " WHERE #{conditions_statement}" unless conditions_statement.blank?
50
- join_statement = join_statement(query, qualify)
51
- order_statement = order_statement(order_by, qualify)
52
- no_group_by = group_by ? group_by.empty? : true
53
- no_order_by = order_by ? order_by.empty? : true
54
-
55
- if use_limit_offset_subquery
56
- # If using qualifiers, we must qualify elements outside the subquery
57
- # with 'RowResults' -- this is a different scope to the subquery.
58
- # Otherwise, we hit upon "multi-part identifier cannot be bound"
59
- # error from SQL Server.
60
- statement = "SELECT #{columns_statement(fields, qualify, 'RowResults')}"
61
- statement << " FROM ( SELECT Row_Number() OVER (ORDER BY #{order_statement}) AS RowID,"
62
- statement << " #{columns_statement}"
63
- statement << from_statement
64
- statement << join_statement if qualify
65
- statement << where_statement if where_statement
66
- statement << ") AS RowResults"
67
- statement << " WHERE RowId > #{offset} AND RowId <= #{offset + limit}"
68
- statement << " GROUP BY #{columns_statement(group_by, qualify, 'RowResults')}" unless no_group_by
69
- statement << " ORDER BY #{order_statement(order_by, qualify, 'RowResults')}" unless no_order_by
70
- else
71
- statement = "SELECT #{columns_statement}"
72
- statement << from_statement
73
- statement << join_statement if qualify
74
- statement << where_statement if where_statement
75
- statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless no_group_by
76
- statement << " ORDER BY #{order_statement}" unless no_order_by
77
- end
78
-
79
- add_limit_offset!(statement, limit, offset, bind_values) unless use_limit_offset_subquery
80
-
81
- return statement, bind_values
82
- end
83
-
84
- # SQL Server does not support LIMIT and OFFSET
85
- # Functionality therefore must be mimicked through the use of nested selects.
86
- # See also:
87
- # - http://stackoverflow.com/questions/2840/paging-sql-server-2005-results
88
- # - http://stackoverflow.com/questions/216673/emulate-mysql-limit-clause-in-microsoft-sql-server-2000
89
- #
90
- def add_limit_offset!(statement, limit, offset, bind_values)
91
- # Limit and offset is handled by subqueries (see #select_statement).
92
- if limit
93
- # If there is just a limit on rows to return, but no offset, then we
94
- # can use TOP clause.
95
- statement.sub!(/^\s*SELECT(\s+DISTINCT)?/i) { "SELECT#{$1} TOP #{limit}" }
96
- # bind_values << limit
97
- end
98
- end
99
-
100
- # @api private
101
- # TODO: Not actually supported out of the box. Is theoretically possible
102
- # via CLR integration, custom functions.
103
- def regexp_operator(operand)
104
- 'REGEXP'
105
- end
106
-
107
- end #module SQL
108
-
109
- include SQL
110
- end # class SqlserverAdapter
111
-
112
- const_added(:SqlserverAdapter)
113
- end # module Adapters
114
- end # module DataMapper
@@ -1,111 +0,0 @@
1
- require 'pathname'
2
- require 'yaml'
3
-
4
- module DataMapper
5
- module Adapters
6
- class YamlAdapter < AbstractAdapter
7
- # @api semipublic
8
- def create(resources)
9
- update_records(resources.first.model) do |records|
10
- resources.each do |resource|
11
- initialize_serial(resource, records.size.succ)
12
- records << resource.attributes(:field)
13
- end
14
- end
15
- end
16
-
17
- # @api semipublic
18
- def read(query)
19
- query.filter_records(records_for(query.model).dup)
20
- end
21
-
22
- # @api semipublic
23
- def update(attributes, collection)
24
- attributes = attributes_as_fields(attributes)
25
-
26
- update_records(collection.model) do |records|
27
- records_to_update = collection.query.filter_records(records.dup)
28
- records_to_update.each { |resource| resource.update(attributes) }.size
29
- end
30
- end
31
-
32
- # @api semipublic
33
- def delete(collection)
34
- update_records(collection.model) do |records|
35
- records_to_delete = collection.query.filter_records(records.dup)
36
- records.replace(records - records_to_delete)
37
- records_to_delete.size
38
- end
39
- end
40
-
41
- private
42
-
43
- # @api semipublic
44
- def initialize(name, options = {})
45
- super
46
- (@path = Pathname(@options[:path]).freeze).mkpath
47
- end
48
-
49
- # Retrieves all records for a model and yeilds them to a block.
50
- #
51
- # The block should make any changes to the records in-place. After
52
- # the block executes all the records are dumped back to the file.
53
- #
54
- # @param [Model, #to_s] model
55
- # Used to determine which file to read/write to
56
- #
57
- # @yieldparam [Hash]
58
- # A hash of record.key => record pairs retrieved from the file
59
- #
60
- # @api private
61
- def update_records(model)
62
- records = records_for(model)
63
- result = yield records
64
- write_records(model, records)
65
- result
66
- end
67
-
68
- # Read all records from a file for a model
69
- #
70
- # @param [#storage_name] model
71
- # The model/name to retieve records for
72
- #
73
- # @api private
74
- def records_for(model)
75
- file = yaml_file(model)
76
- file.readable? && YAML.load_file(file) || []
77
- end
78
-
79
- # Writes all records to a file
80
- #
81
- # @param [#storage_name] model
82
- # The model/name to write the records for
83
- #
84
- # @param [Hash] records
85
- # A hash of record.key => record pairs to be written
86
- #
87
- # @api private
88
- def write_records(model, records)
89
- yaml_file(model).open('w') do |fh|
90
- YAML.dump(records, fh)
91
- end
92
- end
93
-
94
- # Given a model, gives the filename to be used for record storage
95
- #
96
- # @example
97
- # yaml_file(Article) #=> "/path/to/files/articles.yml"
98
- #
99
- # @param [#storage_name] model
100
- # The model to be used to determine the file name.
101
- #
102
- # @api private
103
- def yaml_file(model)
104
- @path / "#{model.storage_name(name)}.yml"
105
- end
106
-
107
- end # class YamlAdapter
108
-
109
- const_added(:YamlAdapter)
110
- end # module Adapters
111
- end # module DataMapper