datamapper-dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
@@ -1,87 +1,108 @@
1
1
  module DataMapper
2
2
  module Adapters
3
+ # This is probably the simplest functional adapter possible. It simply
4
+ # stores and queries from a hash containing the model classes as keys,
5
+ # and an array of hashes. It is not persistent whatsoever; when the Ruby
6
+ # process finishes, everything that was stored it lost. However, it doesn't
7
+ # require any other external libraries, such as data_objects, so it is ideal
8
+ # for writing specs against. It also serves as an excellent example for
9
+ # budding adapter developers, so it is critical that it remains well documented
10
+ # and up to date.
3
11
  class InMemoryAdapter < AbstractAdapter
4
- def initialize(name, uri_or_options)
5
- @records = Hash.new { |hash,model| hash[model] = Array.new }
6
- end
7
-
12
+ # Used by DataMapper to put records into a data-store: "INSERT" in SQL-speak.
13
+ # It takes an array of the resources (model instances) to be saved. Resources
14
+ # each have a key that can be used to quickly look them up later without
15
+ # searching, if the adapter supports it.
16
+ #
17
+ # @param [Enumerable(Resource)] resources
18
+ # The set of resources (model instances)
19
+ #
20
+ # @api semipublic
8
21
  def create(resources)
9
- resources.each do |resource|
10
- @records[resource.model] << resource
11
- end.size # just return the number of records
12
- end
22
+ records = records_for(resources.first.model)
13
23
 
14
- def update(attributes, query)
15
- read_many(query).each do |resource|
16
- attributes.each do |property,value|
17
- property.set!(resource, value)
18
- end
19
- end.size
24
+ resources.each do |resource|
25
+ initialize_serial(resource, records.size.succ)
26
+ records << resource.attributes(:field)
27
+ end
20
28
  end
21
29
 
22
- def read_one(query)
23
- read(query, query.model, false)
30
+ # Looks up one record or a collection of records from the data-store:
31
+ # "SELECT" in SQL.
32
+ #
33
+ # @param [Query] query
34
+ # The query to be used to seach for the resources
35
+ #
36
+ # @return [Array]
37
+ # An Array of Hashes containing the key-value pairs for
38
+ # each record
39
+ #
40
+ # @api semipublic
41
+ def read(query)
42
+ query.filter_records(records_for(query.model).dup)
24
43
  end
25
44
 
26
- def read_many(query)
27
- Collection.new(query) do |set|
28
- read(query, set, true)
29
- end
45
+ # Used by DataMapper to update the attributes on existing records in a
46
+ # data-store: "UPDATE" in SQL-speak. It takes a hash of the attributes
47
+ # to update with, as well as a collection object that specifies which resources
48
+ # should be updated.
49
+ #
50
+ # @param [Hash] attributes
51
+ # A set of key-value pairs of the attributes to update the resources with.
52
+ # @param [DataMapper::Collection] resources
53
+ # The collection of resources to update.
54
+ #
55
+ # @api semipublic
56
+ def update(attributes, collection)
57
+ attributes = attributes_as_fields(attributes)
58
+ read(collection.query).each { |resource| resource.update(attributes) }.size
30
59
  end
31
60
 
32
- def delete(query)
33
- records = @records[query.model]
34
-
35
- read_many(query).each do |resource|
36
- records.delete(resource)
37
- end.size
61
+ # Destroys all the records matching the given query. "DELETE" in SQL.
62
+ #
63
+ # @param [DataMapper::Collection] resources
64
+ # The collection of resources to delete.
65
+ #
66
+ # @return [Integer]
67
+ # The number of records that were deleted.
68
+ #
69
+ # @api semipublic
70
+ def delete(collection)
71
+ records = records_for(collection.model)
72
+ records_to_delete = collection.query.filter_records(records.dup)
73
+ records.replace(records - records_to_delete)
74
+ records_to_delete.size
38
75
  end
39
76
 
40
77
  private
41
78
 
42
- def read(query, set, many = true)
43
- model = query.model
44
- conditions = query.conditions
45
-
46
- match_with = many ? :select : :detect
47
-
48
- # Iterate over the records for this model, and return
49
- # the ones that match the conditions
50
- result = @records[model].send(match_with) do |resource|
51
- conditions.all? do |tuple|
52
- operator, property, bind_value = *tuple
53
-
54
- value = property.get!(resource)
55
-
56
- case operator
57
- when :eql, :in then equality_comparison(bind_value, value)
58
- when :not then !equality_comparison(bind_value, value)
59
- when :like then Regexp.new(bind_value) =~ value
60
- when :gt then !value.nil? && value > bind_value
61
- when :gte then !value.nil? && value >= bind_value
62
- when :lt then !value.nil? && value < bind_value
63
- when :lte then !value.nil? && value <= bind_value
64
- else raise "Invalid query operator: #{operator.inspect}"
65
- end
66
- end
67
- end
68
-
69
- return result unless many
70
-
71
- # TODO Sort
72
-
73
- # TODO Limit
74
-
75
- set.replace(result)
79
+ # Make a new instance of the adapter. The @records ivar is the 'data-store'
80
+ # for this adapter. It is not shared amongst multiple incarnations of this
81
+ # adapter, eg DataMapper.setup(:default, :adapter => :in_memory);
82
+ # DataMapper.setup(:alternate, :adapter => :in_memory) do not share the
83
+ # data-store between them.
84
+ #
85
+ # @param [String, Symbol] name
86
+ # The name of the Repository using this adapter.
87
+ # @param [String, Hash] uri_or_options
88
+ # The connection uri string, or a hash of options to set up
89
+ # the adapter
90
+ #
91
+ # @api semipublic
92
+ def initialize(name, options = {})
93
+ super
94
+ @records = {}
76
95
  end
77
96
 
78
- def equality_comparison(bind_value, value)
79
- case bind_value
80
- when Array, Range then bind_value.include?(value)
81
- when NilClass then value.nil?
82
- else bind_value == value
83
- end
97
+ # All the records we're storing. This method will look them up by model name
98
+ #
99
+ # @api private
100
+ def records_for(model)
101
+ @records[model.storage_name(name)] ||= []
84
102
  end
85
- end
86
- end
87
- end
103
+
104
+ end # class InMemoryAdapter
105
+
106
+ const_added(:InMemoryAdapter)
107
+ end # module Adapters
108
+ end # module DataMapper
@@ -1,136 +1,43 @@
1
- gem 'do_mysql', '~>0.9.12'
1
+ require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
2
+
2
3
  require 'do_mysql'
3
4
 
4
5
  module DataMapper
5
6
  module Adapters
6
- # Options:
7
- # host, user, password, database (path), socket(uri query string), port
8
7
  class MysqlAdapter < DataObjectsAdapter
9
- module SQL
8
+ module SQL #:nodoc:
9
+ IDENTIFIER_MAX_LENGTH = 64
10
+
10
11
  private
11
12
 
12
- def supports_default_values?
13
+ # TODO: document
14
+ # @api private
15
+ def supports_default_values? #:nodoc:
13
16
  false
14
17
  end
15
18
 
16
- def quote_table_name(table_name)
17
- "`#{table_name.gsub('`', '``')}`"
19
+ # TODO: document
20
+ # @api private
21
+ def regexp_operator(operand)
22
+ 'REGEXP'
18
23
  end
19
24
 
20
- def quote_column_name(column_name)
21
- "`#{column_name.gsub('`', '``')}`"
25
+ # TODO: document
26
+ # @api private
27
+ def not_regexp_operator(operand)
28
+ 'NOT REGEXP'
22
29
  end
23
30
 
24
- def quote_column_value(column_value)
25
- case column_value
26
- when TrueClass then quote_column_value(1)
27
- when FalseClass then quote_column_value(0)
28
- else
29
- super
30
- end
31
+ # TODO: document
32
+ # @api private
33
+ def quote_name(name)
34
+ "`#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('`', '``')}`"
31
35
  end
32
36
  end #module SQL
33
37
 
34
38
  include SQL
35
-
36
- # TODO: move to dm-more/dm-migrations
37
- module Migration
38
- # TODO: move to dm-more/dm-migrations (if possible)
39
- def storage_exists?(storage_name)
40
- statement = <<-EOS.compress_lines
41
- SELECT COUNT(*)
42
- FROM `information_schema`.`tables`
43
- WHERE `table_type` = 'BASE TABLE'
44
- AND `table_schema` = ?
45
- AND `table_name` = ?
46
- EOS
47
-
48
- query(statement, db_name, storage_name).first > 0
49
- end
50
-
51
- # TODO: move to dm-more/dm-migrations (if possible)
52
- def field_exists?(storage_name, field_name)
53
- statement = <<-EOS.compress_lines
54
- SELECT COUNT(*)
55
- FROM `information_schema`.`columns`
56
- WHERE `table_schema` = ?
57
- AND `table_name` = ?
58
- AND `column_name` = ?
59
- EOS
60
-
61
- query(statement, db_name, storage_name, field_name).first > 0
62
- end
63
-
64
- private
65
-
66
- # TODO: move to dm-more/dm-migrations (if possible)
67
- def db_name
68
- @uri.path.split('/').last
69
- end
70
-
71
- module SQL
72
- private
73
-
74
- # TODO: move to dm-more/dm-migrations
75
- def supports_serial?
76
- true
77
- end
78
-
79
- # TODO: move to dm-more/dm-migrations
80
- def create_table_statement(repository, model)
81
- "#{super} ENGINE = InnoDB CHARACTER SET #{character_set} COLLATE #{collation}"
82
- end
83
-
84
- # TODO: move to dm-more/dm-migrations
85
- def property_schema_hash(property, model)
86
- schema = super
87
- schema.delete(:default) if schema[:primitive] == 'TEXT'
88
- schema
89
- end
90
-
91
- # TODO: move to dm-more/dm-migrations
92
- def property_schema_statement(schema)
93
- statement = super
94
- statement << ' AUTO_INCREMENT' if supports_serial? && schema[:serial?]
95
- statement
96
- end
97
-
98
- # TODO: move to dm-more/dm-migrations
99
- def character_set
100
- @character_set ||= show_variable('character_set_connection') || 'utf8'
101
- end
102
-
103
- # TODO: move to dm-more/dm-migrations
104
- def collation
105
- @collation ||= show_variable('collation_connection') || 'utf8_general_ci'
106
- end
107
-
108
- # TODO: move to dm-more/dm-migrations
109
- def show_variable(name)
110
- query('SHOW VARIABLES WHERE `variable_name` = ?', name).first.value rescue nil
111
- end
112
- end # module SQL
113
-
114
- include SQL
115
-
116
- module ClassMethods
117
- # TypeMap for MySql databases.
118
- #
119
- # @return <DataMapper::TypeMap> default TypeMap for MySql databases.
120
- #
121
- # TODO: move to dm-more/dm-migrations
122
- def type_map
123
- @type_map ||= TypeMap.new(super) do |tm|
124
- tm.map(Integer).to('INT').with(:size => 11)
125
- tm.map(TrueClass).to('TINYINT').with(:size => 1) # TODO: map this to a BIT or CHAR(0) field?
126
- tm.map(Object).to('TEXT')
127
- end
128
- end
129
- end # module ClassMethods
130
- end # module Migration
131
-
132
- include Migration
133
- extend Migration::ClassMethods
134
39
  end # class MysqlAdapter
40
+
41
+ const_added(:MysqlAdapter)
135
42
  end # module Adapters
136
43
  end # module DataMapper
@@ -0,0 +1,249 @@
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
+ custom_sequence = serial && serial.options[:sequence]
28
+
29
+ if supports_default_values? && properties.empty? && !custom_sequence
30
+ statement << "(#{quote_name(serial.field)}) " if serial
31
+ statement << default_values_clause
32
+ else
33
+ # do not use custom sequence if identity field was assigned a value
34
+ if custom_sequence && properties.include?(serial)
35
+ custom_sequence = nil
36
+ end
37
+ statement << "("
38
+ if custom_sequence
39
+ statement << "#{quote_name(serial.field)}"
40
+ statement << ", " unless properties.empty?
41
+ end
42
+ statement << "#{properties.map { |p| quote_name(p.field) }.join(', ')}) "
43
+ statement << "VALUES ("
44
+ if custom_sequence
45
+ statement << "#{quote_name(custom_sequence)}.NEXTVAL"
46
+ statement << ", " unless properties.empty?
47
+ end
48
+ statement << "#{(['?'] * properties.size).join(', ')})"
49
+ end
50
+
51
+ if supports_returning? && serial
52
+ statement << returning_clause(serial)
53
+ end
54
+
55
+ statement
56
+ end
57
+
58
+ # Oracle syntax for inserting default values
59
+ def default_values_clause
60
+ 'VALUES (DEFAULT)'
61
+ end
62
+
63
+ # TODO: document
64
+ # @api private
65
+ def supports_returning?
66
+ true
67
+ end
68
+
69
+ # INTO :insert_id is recognized by Oracle DataObjects driver
70
+ def returning_clause(serial)
71
+ " RETURNING #{quote_name(serial.field)} INTO :insert_id"
72
+ end
73
+
74
+ # Constructs SELECT statement for given query,
75
+ # Overrides DataObjects adapter implementation with using subquery instead of GROUP BY to get unique records
76
+ #
77
+ # @return [String] SELECT statement as a string
78
+ #
79
+ # @api private
80
+ def select_statement(query)
81
+ model = query.model
82
+ fields = query.fields
83
+ conditions = query.conditions
84
+ limit = query.limit
85
+ offset = query.offset
86
+ order = query.order
87
+ group_by = nil
88
+
89
+ # FIXME: using a boolean for qualify does not work in some cases,
90
+ # such as when you have a self-referrential many to many association.
91
+ # if you don't qualfiy the columns with a unique alias, then the
92
+ # SQL query will fail. This may mean though, that it might not
93
+ # be enough to pass in a Property, but we may need to know the
94
+ # table and the alias we should use for the column.
95
+
96
+ qualify = query.links.any?
97
+
98
+ if query.unique?
99
+ group_by = fields.select { |p| p.kind_of?(Property) }
100
+ end
101
+
102
+ # create subquery to find all valid keys and then use these keys to retrive all other columns
103
+ use_subquery = qualify
104
+
105
+ # when we can include ROWNUM condition in main WHERE clause
106
+ use_simple_rownum_limit = limit && (offset||0 == 0) && group_by.blank? && order.blank?
107
+
108
+ unless (limit && limit > 1) || offset > 0 || qualify
109
+ # TODO: move this method to Query, so that it walks the conditions
110
+ # and finds an OR operator
111
+
112
+ # TODO: handle cases where two or more properties need to be
113
+ # used together to be unique
114
+
115
+ # if a unique property is used, and there is no OR operator, then an ORDER
116
+ # and LIMIT are unecessary because it should only return a single row
117
+ if conditions.kind_of?(Query::Conditions::AndOperation) &&
118
+ conditions.any? { |operand| operand.kind_of?(Query::Conditions::EqualToComparison) && operand.subject.respond_to?(:unique?) && operand.subject.unique? } &&
119
+ !conditions.any? { |operand| operand.kind_of?(Query::Conditions::OrOperation) }
120
+ order = nil
121
+ limit = nil
122
+ end
123
+ end
124
+
125
+ conditions_statement, bind_values = conditions_statement(conditions, qualify)
126
+
127
+ statement = "SELECT #{columns_statement(fields, qualify)}"
128
+ if use_subquery
129
+ statement << " FROM #{quote_name(model.storage_name(name))}"
130
+ statement << " WHERE (#{columns_statement(model.key, qualify)}) IN"
131
+ statement << " (SELECT DISTINCT #{columns_statement(model.key, qualify)}"
132
+ end
133
+ statement << " FROM #{quote_name(model.storage_name(name))}"
134
+ statement << join_statement(query, qualify) if qualify
135
+ statement << " WHERE (#{conditions_statement})" unless conditions_statement.blank?
136
+ if use_subquery
137
+ statement << ")"
138
+ end
139
+ if use_simple_rownum_limit
140
+ statement << " AND rownum <= ?"
141
+ bind_values << limit
142
+ end
143
+ statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless group_by.blank?
144
+ statement << " ORDER BY #{order_statement(order, qualify)}" unless order.blank?
145
+
146
+ add_limit_offset!(statement, limit, offset, bind_values) unless use_simple_rownum_limit
147
+
148
+ return statement, bind_values
149
+ end
150
+
151
+ # Oracle does not support LIMIT and OFFSET
152
+ # Functionality is mimiced through the use of nested selects.
153
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
154
+ def add_limit_offset!(statement, limit, offset, bind_values)
155
+ if limit && offset > 0
156
+ statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_ where rownum <= ?) where raw_rnum_ > ?"
157
+ bind_values << offset + limit << offset
158
+ elsif limit
159
+ statement.replace "select raw_sql_.* from (#{statement}) raw_sql_ where rownum <= ?"
160
+ bind_values << limit
161
+ elsif offset > 0
162
+ statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_) where raw_rnum_ > ?"
163
+ bind_values << offset
164
+ end
165
+ end
166
+
167
+ # TODO: document
168
+ # @api private
169
+ # Oracle does not allow " in table or column names therefore substitute them with underscore
170
+ def quote_name(name)
171
+ "\"#{oracle_upcase(name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_')}\""
172
+ end
173
+
174
+ # If table or column name contains just lowercase characters then do uppercase
175
+ # as uppercase version will be used in Oracle data dictionary tables
176
+ def oracle_upcase(name)
177
+ name =~ /[A-Z]/ ? name : name.upcase
178
+ end
179
+
180
+ # CLOB value should be compared using DBMS_LOB.SUBSTR function
181
+ # NOTE: just first 32767 bytes will be compared!
182
+ # @api private
183
+ def equality_operator(property, operand)
184
+ if property.type == Types::Text
185
+ operand.nil? ? 'IS' : 'DBMS_LOB.SUBSTR(%s) = ?'
186
+ else
187
+ operand.nil? ? 'IS' : '='
188
+ end
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 inequality_operator(property, operand)
195
+ if property.type == Types::Text
196
+ operand.nil? ? 'IS NOT' : 'DBMS_LOB.SUBSTR(%s) <> ?'
197
+ else
198
+ operand.nil? ? 'IS NOT' : '<>'
199
+ end
200
+ end
201
+
202
+ # TODO: document
203
+ # @api private
204
+ def include_operator(property, operand)
205
+ operator = case operand
206
+ when Array then 'IN'
207
+ when Range then 'BETWEEN'
208
+ end
209
+ if property.type == Types::Text
210
+ "DBMS_LOB.SUBSTR(%s) #{operator} ?"
211
+ else
212
+ operator
213
+ end
214
+ end
215
+
216
+ # TODO: document
217
+ # @api private
218
+ def exclude_operator(property, operand)
219
+ operator = case operand
220
+ when Array then 'NOT IN'
221
+ when Range then 'NOT BETWEEN'
222
+ end
223
+ if property.type == Types::Text
224
+ "DBMS_LOB.SUBSTR(%s) #{operator} ?"
225
+ else
226
+ operator
227
+ end
228
+ end
229
+
230
+ # TODO: document
231
+ # @api private
232
+ def regexp_operator(operand)
233
+ 'REGEXP_LIKE(%s, ?)'
234
+ end
235
+
236
+ # TODO: document
237
+ # @api private
238
+ def not_regexp_operator(operand)
239
+ 'NOT REGEXP_LIKE(%s, ?)'
240
+ end
241
+
242
+ end #module SQL
243
+
244
+ include SQL
245
+ end # class PostgresAdapter
246
+
247
+ const_added(:OracleAdapter)
248
+ end # module Adapters
249
+ end # module DataMapper