datamapper-dm-core 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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