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.
- data/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
|
|
104
|
+
end # class InMemoryAdapter
|
|
105
|
+
|
|
106
|
+
const_added(:InMemoryAdapter)
|
|
107
|
+
end # module Adapters
|
|
108
|
+
end # module DataMapper
|
|
@@ -1,136 +1,43 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
13
|
+
# TODO: document
|
|
14
|
+
# @api private
|
|
15
|
+
def supports_default_values? #:nodoc:
|
|
13
16
|
false
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
# TODO: document
|
|
20
|
+
# @api private
|
|
21
|
+
def regexp_operator(operand)
|
|
22
|
+
'REGEXP'
|
|
18
23
|
end
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
# TODO: document
|
|
26
|
+
# @api private
|
|
27
|
+
def not_regexp_operator(operand)
|
|
28
|
+
'NOT REGEXP'
|
|
22
29
|
end
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|