datamapper 0.1.1 → 0.2.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 (131) hide show
  1. data/CHANGELOG +65 -0
  2. data/README +193 -1
  3. data/do_performance.rb +153 -0
  4. data/environment.rb +45 -0
  5. data/example.rb +119 -22
  6. data/lib/data_mapper.rb +36 -16
  7. data/lib/data_mapper/adapters/abstract_adapter.rb +8 -0
  8. data/lib/data_mapper/adapters/data_object_adapter.rb +360 -0
  9. data/lib/data_mapper/adapters/mysql_adapter.rb +30 -179
  10. data/lib/data_mapper/adapters/postgresql_adapter.rb +90 -199
  11. data/lib/data_mapper/adapters/sql/coersion.rb +32 -3
  12. data/lib/data_mapper/adapters/sql/commands/conditions.rb +97 -128
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +234 -231
  14. data/lib/data_mapper/adapters/sql/commands/loader.rb +99 -0
  15. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +30 -0
  16. data/lib/data_mapper/adapters/sql/mappings/column.rb +68 -6
  17. data/lib/data_mapper/adapters/sql/mappings/schema.rb +6 -3
  18. data/lib/data_mapper/adapters/sql/mappings/table.rb +71 -42
  19. data/lib/data_mapper/adapters/sql/quoting.rb +8 -2
  20. data/lib/data_mapper/adapters/sqlite3_adapter.rb +32 -201
  21. data/lib/data_mapper/associations.rb +21 -7
  22. data/lib/data_mapper/associations/belongs_to_association.rb +96 -80
  23. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +158 -67
  24. data/lib/data_mapper/associations/has_many_association.rb +96 -78
  25. data/lib/data_mapper/associations/has_n_association.rb +64 -0
  26. data/lib/data_mapper/associations/has_one_association.rb +49 -79
  27. data/lib/data_mapper/associations/reference.rb +47 -0
  28. data/lib/data_mapper/base.rb +216 -50
  29. data/lib/data_mapper/callbacks.rb +71 -24
  30. data/lib/data_mapper/{session.rb → context.rb} +20 -8
  31. data/lib/data_mapper/database.rb +176 -45
  32. data/lib/data_mapper/embedded_value.rb +65 -0
  33. data/lib/data_mapper/identity_map.rb +12 -4
  34. data/lib/data_mapper/support/active_record_impersonation.rb +12 -8
  35. data/lib/data_mapper/support/enumerable.rb +8 -0
  36. data/lib/data_mapper/support/serialization.rb +13 -0
  37. data/lib/data_mapper/support/string.rb +1 -12
  38. data/lib/data_mapper/support/symbol.rb +3 -0
  39. data/lib/data_mapper/validations/unique_validator.rb +1 -2
  40. data/lib/data_mapper/validations/validation_helper.rb +18 -1
  41. data/performance.rb +109 -34
  42. data/plugins/can_has_sphinx/LICENSE +23 -0
  43. data/plugins/can_has_sphinx/README +4 -0
  44. data/plugins/can_has_sphinx/REVISION +1 -0
  45. data/plugins/can_has_sphinx/Rakefile +22 -0
  46. data/plugins/can_has_sphinx/init.rb +1 -0
  47. data/plugins/can_has_sphinx/install.rb +1 -0
  48. data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +123 -0
  49. data/plugins/can_has_sphinx/lib/sphinx.rb +460 -0
  50. data/plugins/can_has_sphinx/scripts/sphinx.sh +47 -0
  51. data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +41 -0
  52. data/plugins/dataobjects/REVISION +1 -0
  53. data/plugins/dataobjects/Rakefile +7 -0
  54. data/plugins/dataobjects/do.rb +246 -0
  55. data/plugins/dataobjects/do_mysql.rb +179 -0
  56. data/plugins/dataobjects/do_postgres.rb +181 -0
  57. data/plugins/dataobjects/do_sqlite3.rb +153 -0
  58. data/plugins/dataobjects/spec/do_spec.rb +150 -0
  59. data/plugins/dataobjects/spec/spec_helper.rb +81 -0
  60. data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
  61. data/plugins/dataobjects/swig_mysql/extconf.rb +33 -0
  62. data/plugins/dataobjects/swig_mysql/mysql_c.c +18800 -0
  63. data/plugins/dataobjects/swig_mysql/mysql_c.i +8 -0
  64. data/plugins/dataobjects/swig_mysql/mysql_supp.i +46 -0
  65. data/plugins/dataobjects/swig_postgres/Makefile +146 -0
  66. data/plugins/dataobjects/swig_postgres/extconf.rb +29 -0
  67. data/plugins/dataobjects/swig_postgres/postgres_c.bundle +0 -0
  68. data/plugins/dataobjects/swig_postgres/postgres_c.c +8185 -0
  69. data/plugins/dataobjects/swig_postgres/postgres_c.i +73 -0
  70. data/plugins/dataobjects/swig_sqlite/db +0 -0
  71. data/plugins/dataobjects/swig_sqlite/extconf.rb +9 -0
  72. data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +4725 -0
  73. data/plugins/dataobjects/swig_sqlite/sqlite_c.i +168 -0
  74. data/rakefile.rb +45 -23
  75. data/spec/acts_as_tree_spec.rb +39 -0
  76. data/spec/associations_spec.rb +220 -0
  77. data/spec/attributes_spec.rb +15 -0
  78. data/spec/base_spec.rb +44 -0
  79. data/spec/callbacks_spec.rb +45 -0
  80. data/spec/can_has_sphinx.rb +6 -0
  81. data/spec/coersion_spec.rb +34 -0
  82. data/spec/conditions_spec.rb +49 -0
  83. data/spec/conversions_to_yaml_spec.rb +17 -0
  84. data/spec/count_command_spec.rb +11 -0
  85. data/spec/delete_command_spec.rb +1 -1
  86. data/spec/embedded_value_spec.rb +23 -0
  87. data/spec/fixtures/animals_exhibits.yaml +2 -0
  88. data/spec/fixtures/people.yaml +18 -1
  89. data/spec/{legacy.rb → legacy_spec.rb} +3 -3
  90. data/spec/load_command_spec.rb +157 -20
  91. data/spec/magic_columns_spec.rb +9 -0
  92. data/spec/mock_adapter.rb +20 -0
  93. data/spec/models/animal.rb +1 -1
  94. data/spec/models/animals_exhibit.rb +6 -0
  95. data/spec/models/exhibit.rb +2 -0
  96. data/spec/models/person.rb +26 -1
  97. data/spec/models/project.rb +19 -0
  98. data/spec/models/sales_person.rb +1 -0
  99. data/spec/models/section.rb +6 -0
  100. data/spec/models/zoo.rb +3 -1
  101. data/spec/query_spec.rb +9 -0
  102. data/spec/save_command_spec.rb +65 -1
  103. data/spec/schema_spec.rb +89 -0
  104. data/spec/single_table_inheritance_spec.rb +27 -0
  105. data/spec/spec_helper.rb +9 -55
  106. data/spec/{symbolic_operators.rb → symbolic_operators_spec.rb} +9 -5
  107. data/spec/{validates_confirmation_of.rb → validates_confirmation_of_spec.rb} +4 -3
  108. data/spec/{validates_format_of.rb → validates_format_of_spec.rb} +5 -4
  109. data/spec/{validates_length_of.rb → validates_length_of_spec.rb} +8 -7
  110. data/spec/{validates_uniqueness_of.rb → validates_uniqueness_of_spec.rb} +7 -10
  111. data/spec/{validations.rb → validations_spec.rb} +24 -6
  112. data/tasks/drivers.rb +20 -0
  113. data/tasks/fixtures.rb +42 -0
  114. metadata +181 -42
  115. data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +0 -140
  116. data/lib/data_mapper/adapters/sql/commands/delete_command.rb +0 -113
  117. data/lib/data_mapper/adapters/sql/commands/save_command.rb +0 -141
  118. data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +0 -33
  119. data/lib/data_mapper/adapters/sql_adapter.rb +0 -163
  120. data/lib/data_mapper/associations/advanced_has_many_association.rb +0 -55
  121. data/lib/data_mapper/support/blank_slate.rb +0 -3
  122. data/lib/data_mapper/support/proc.rb +0 -69
  123. data/lib/data_mapper/support/struct.rb +0 -26
  124. data/lib/data_mapper/unit_of_work.rb +0 -38
  125. data/spec/basic_finder.rb +0 -67
  126. data/spec/belongs_to.rb +0 -47
  127. data/spec/has_and_belongs_to_many.rb +0 -25
  128. data/spec/has_many.rb +0 -34
  129. data/spec/new_record.rb +0 -24
  130. data/spec/sub_select.rb +0 -16
  131. data/spec/support/string_spec.rb +0 -7
@@ -1,3 +1,6 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+
1
4
  module DataMapper
2
5
  module Adapters
3
6
  module Sql
@@ -8,8 +11,11 @@ module DataMapper
8
11
  # it.
9
12
  module Coersion
10
13
 
11
- TRUE_ALIASES = ['true'.freeze, 'TRUE'.freeze]
12
- FALSE_ALIASES = [nil]
14
+ class CoersionError < StandardError
15
+ end
16
+
17
+ TRUE_ALIASES = ['true'.freeze, 'TRUE'.freeze, '1'.freeze]
18
+ FALSE_ALIASES = [nil, '0'.freeze]
13
19
 
14
20
  def self.included(base)
15
21
  base.const_set('TRUE_ALIASES', TRUE_ALIASES.dup)
@@ -47,6 +53,18 @@ module DataMapper
47
53
  nil
48
54
  end
49
55
 
56
+ def type_cast_decimal(raw_value)
57
+ return nil if raw_value.blank?
58
+ raw_value.to_d
59
+ rescue ArgumentError
60
+ nil
61
+ end
62
+
63
+ def type_cast_float(raw_value)
64
+ return nil if raw_value.blank?
65
+ raw_value.to_f
66
+ end
67
+
50
68
  def type_cast_datetime(raw_value)
51
69
  return nil if raw_value.blank?
52
70
 
@@ -54,7 +72,18 @@ module DataMapper
54
72
  when DateTime then raw_value
55
73
  when Date then DateTime.new(raw_value)
56
74
  when String then DateTime::parse(raw_value)
57
- else "Can't type-cast #{raw_value.inspect} to a datetime"
75
+ else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a datetime")
76
+ end
77
+ end
78
+
79
+ def type_cast_date(raw_value)
80
+ return nil if raw_value.blank?
81
+
82
+ case raw_value
83
+ when Date then raw_value
84
+ when DateTime, Time then Date::civil(raw_value.year, raw_value.month, raw_value.day)
85
+ when String then Date::parse(raw_value)
86
+ else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a date")
58
87
  end
59
88
  end
60
89
 
@@ -5,154 +5,123 @@ module DataMapper
5
5
 
6
6
  class Conditions
7
7
 
8
- def initialize(adapter, loader)
8
+ def initialize(adapter, loader, conditions_hash)
9
9
  @adapter, @loader = adapter, loader
10
- @has_id = false
10
+ @conditions = parse_conditions(conditions_hash)
11
11
  end
12
12
 
13
- class NormalizationError < StandardError
14
-
15
- attr_reader :inner_error
16
-
17
- def initialize(clause, inner_error = nil)
18
- @clause = clause
19
- @inner_error = inner_error
20
-
21
- message = "Failed to normalize clause: #{clause.inspect}"
22
- message << ", Error: #{inner_error.inspect}" unless inner_error.nil?
23
-
24
- super(message)
25
- end
26
-
27
- end
28
-
29
- def normalize(clause, collector)
30
- case clause
31
- when Hash then
32
- clause.each_pair do |k,v|
33
- if k.kind_of?(Symbol::Operator)
34
- if k.type == :select
35
- k.options[:class] ||= @loader.klass
36
-
37
- k.options[:select] ||= if k.value.to_s == @adapter[k.options[:class]].default_foreign_key
38
- @adapter[k.options[:class]].key.column_name
39
- else
40
- k.value
41
- end
42
-
43
- sub_select = @adapter.select_statement(k.options.merge(v))
44
- normalize(["#{@adapter[@loader.klass][k.value.to_sym].to_sql} IN ?", sub_select], collector)
45
- else
46
- @has_id = true if k.value == :id
47
- op = case k.type
48
- when :gt then '>'
49
- when :gte then '>='
50
- when :lt then '<'
51
- when :lte then '<='
52
- when :not then v.nil? ? 'IS NOT' : (v.kind_of?(Array) ? 'NOT IN' : '<>')
53
- when :eql then v.nil? ? 'IS' : (v.kind_of?(Array) ? 'IN' : '=')
54
- when :like then 'LIKE'
55
- when :in then 'IN'
56
- else raise ArgumentError.new('Operator type not supported')
57
- end
58
- normalize(["#{@adapter[@loader.klass][k.value.to_sym].to_sql} #{op} ?", v], collector)
59
- end
60
- else
61
- @has_id = true if k == :id
62
- case v
63
- when Array then
64
- normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} IN ?", v], collector)
65
- when LoadCommand then
66
- normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} IN ?", v], collector)
67
- else
68
- normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} = ?", v], collector)
69
- end
70
- end
71
- end
72
- when Array then
73
- return collector if clause.empty?
74
- @has_id = true if clause.first =~ /(^|\s|\`)id(\`|\s|\=|\<)/ && !clause[1].kind_of?(LoadCommand)
75
- collector << escape(clause)
76
- when String then
77
- @has_id = true if clause =~ /(^|\s|\`)id(\`|\s|\=|\<)/
78
- collector << clause
79
- else raise NormalizationError.new(clause)
80
- end
81
-
82
- return collector
13
+ def empty?
14
+ @conditions.empty?
83
15
  end
84
16
 
85
- def escape(conditions)
86
- clause = conditions.shift
87
-
88
- clause.gsub(/\?/) do |x|
89
- # Check if the condition is an in, clause.
90
- case conditions.first
17
+ def to_parameterized_sql
18
+ sql = []
19
+ parameters = []
20
+
21
+ @conditions.each do |condition|
22
+ case condition
23
+ when String then sql << condition
91
24
  when Array then
92
- '(' << conditions.shift.map { |c| @adapter.quote_value(c) }.join(', ') << ')'
93
- when LoadCommand then
94
- '(' << conditions.shift.to_sql << ')'
25
+ sql << condition.shift
26
+ parameters += condition
95
27
  else
96
- @adapter.quote_value(conditions.shift)
28
+ raise "Unable to parse condition: #{condition.inspect}" if condition
97
29
  end
98
30
  end
31
+
32
+ parameters.unshift("(#{sql.join(') AND (')})")
99
33
  end
100
-
101
- def has_id?
102
- normalized_conditions
103
- @has_id
104
- end
105
-
106
- def normalized_conditions
107
-
108
- if @normalized_conditions.nil?
109
- @normalized_conditions = []
110
-
111
- normalize(implicits, @normalized_conditions)
112
34
 
113
- if @loader.options.has_key?(:conditions)
114
- normalize(@loader.options[:conditions], @normalized_conditions)
35
+ private
36
+
37
+ class ConditionsError < StandardError
38
+
39
+ attr_reader :inner_error
40
+
41
+ def initialize(clause, value, inner_error)
42
+ @clause, @value, @inner_error = clause, value, inner_error
115
43
  end
116
-
117
- end
118
-
119
- return @normalized_conditions
120
- end
121
-
122
- def table
123
- @table || (@table = @adapter[@loader.klass])
124
- end
125
-
126
- def implicits
127
- @implicits || @implicits = begin
128
44
 
129
- invalid_keys = false
45
+ def message
46
+ "Conditions (:clause => #{@clause.inspect}, :value => #{@value.inspect}) failed: #{@inner_error}"
47
+ end
130
48
 
131
- implicit_conditions = @loader.options.reject do |k,v|
132
- standard_key = @adapter.class::FIND_OPTIONS.include?(k)
133
- invalid_keys = true if !standard_key && table[k.to_sym].nil?
134
- standard_key
49
+ def backtrace
50
+ @inner_error.backtrace
51
+ end
52
+
53
+ end
54
+
55
+ def expression_to_sql(clause, value, collector)
56
+ qualify_columns = @loader.qualify_columns?
57
+
58
+ case clause
59
+ when Symbol::Operator then
60
+ operator = case clause.type
61
+ when :gt then '>'
62
+ when :gte then '>='
63
+ when :lt then '<'
64
+ when :lte then '<='
65
+ when :not then inequality_operator(value)
66
+ when :eql then equality_operator(value)
67
+ when :like then equality_operator(value, 'LIKE')
68
+ when :in then equality_operator(value)
69
+ else raise ArgumentError.new('Operator type not supported')
70
+ end
71
+ collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{operator} ?", value]
72
+ when Symbol then
73
+ collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
74
+ when String then
75
+ collector << [clause, value]
76
+ when Mappings::Column then
77
+ collector << ["#{clause.to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
78
+ else raise "CAN HAS CRASH? #{clause.inspect}"
135
79
  end
80
+ rescue => e
81
+ raise ConditionsError.new(clause, value, e)
82
+ end
83
+
84
+ def equality_operator(value, default = '=')
85
+ case value
86
+ when NilClass then 'IS'
87
+ when Array then 'IN'
88
+ else default
89
+ end
90
+ end
91
+
92
+ def inequality_operator(value, default = '<>')
93
+ case value
94
+ when NilClass then 'IS NOT'
95
+ when Array then 'NOT IN'
96
+ else default
97
+ end
98
+ end
99
+
100
+ def parse_conditions(conditions_hash)
101
+ collection = []
136
102
 
137
- if invalid_keys
138
- invalid_keys = implicit_conditions.select do |k,v|
139
- table[k.to_sym].nil?
103
+ case x = conditions_hash.delete(:conditions)
104
+ when Array then
105
+ clause = x.shift
106
+ expression_to_sql(clause, x, collection)
107
+ when Hash then
108
+ x.each_pair do |key,value|
109
+ expression_to_sql(key, value, collection)
140
110
  end
141
-
142
- raise "Invalid options: #{invalid_keys.inspect}" unless invalid_keys.nil?
143
111
  else
144
- implicit_conditions
112
+ raise "Unable to parse conditions: #{x.inspect}" if x
113
+ end
114
+
115
+ conditions_hash.each_pair do |key,value|
116
+ expression_to_sql(key, value, collection)
145
117
  end
118
+
119
+ collection
120
+ end
121
+
122
+ def primary_class_table
123
+ @primary_class_table || (@primary_class_table = @loader.send(:primary_class_table))
146
124
  end
147
- end
148
-
149
- def empty?
150
- !@loader.options.has_key?(:conditions) && implicits.empty?
151
- end
152
-
153
- def to_a
154
- normalized_conditions
155
- end
156
125
  end
157
126
 
158
127
  end
@@ -1,4 +1,5 @@
1
- require File.dirname(__FILE__) + '/conditions'
1
+ require 'data_mapper/adapters/sql/commands/conditions'
2
+ require 'data_mapper/adapters/sql/commands/loader'
2
3
 
3
4
  module DataMapper
4
5
  module Adapters
@@ -7,287 +8,289 @@ module DataMapper
7
8
 
8
9
  class LoadCommand
9
10
 
10
- attr_reader :klass, :order, :limit, :instance_id, :conditions, :options
11
+ attr_reader :conditions, :session, :options
11
12
 
12
- def initialize(adapter, session, klass, options)
13
- @adapter, @session, @klass, @options = adapter, session, klass, options
13
+ def initialize(adapter, session, primary_class, options = {})
14
+ @adapter, @session, @primary_class = adapter, session, primary_class
15
+
16
+ @options, conditions_hash = partition_options(options)
14
17
 
15
18
  @order = @options[:order]
16
19
  @limit = @options[:limit]
20
+ @offset = @options[:offset]
17
21
  @reload = @options[:reload]
18
- @instance_id = @options[:id]
19
- @conditions = Conditions.new(@adapter, self)
22
+ @instance_id = conditions_hash[:id]
23
+ @conditions = Conditions.new(@adapter, self, conditions_hash)
24
+ @loaders = Hash.new { |h,k| h[k] = Loader.new(self, k) }
25
+ end
26
+
27
+ # Display an overview of load options at a glance.
28
+ def inspect
29
+ <<-EOS.compress_lines % (object_id * 2)
30
+ #<#{self.class.name}:0x%x
31
+ @database=#{@adapter.name}
32
+ @reload=#{@reload.inspect}
33
+ @order=#{@order.inspect}
34
+ @limit=#{@limit.inspect}
35
+ @offset=#{@offset.inspect}
36
+ @options=#{@options.inspect}>
37
+ EOS
38
+ end
39
+
40
+ # Access the Conditions instance
41
+ def conditions
42
+ @conditions
20
43
  end
21
44
 
45
+ # If +true+ then force the command to reload any objects
46
+ # already existing in the IdentityMap when executing.
22
47
  def reload?
23
48
  @reload
24
49
  end
25
-
26
- def escape(conditions)
27
- @adapter.escape(conditions)
28
- end
29
-
30
- def inspect
31
- @options.inspect
50
+
51
+ # Determine if there is a limitation on the number of
52
+ # instances returned in the results. If +nil+, no limit
53
+ # is set. Can be used in conjunction with #offset for
54
+ # paging through a set of results.
55
+ def limit
56
+ @limit
32
57
  end
33
-
34
- def include?(association_name)
35
- return false if includes.empty?
36
- includes.include?(association_name)
58
+
59
+ # Used in conjunction with #limit to page through a set
60
+ # of results.
61
+ def offset
62
+ @offset
37
63
  end
38
-
39
- def includes
40
- @includes || @includes = begin
41
- list = @options[:include] || []
42
- list.kind_of?(Array) ? list : [list]
43
- list
64
+
65
+ def call
66
+
67
+ # Check to see if the query is for a specific id and return if found
68
+ #
69
+ # NOTE: If the :id option is an Array:
70
+ # We could search for loaded instance ids and reject from
71
+ # the Array for already loaded instances, but working under the
72
+ # assumption that we'll probably have to issue a query to find
73
+ # at-least some of the instances we're looking for, it's faster to
74
+ # just skip that and go straight for the query.
75
+ unless reload? || @instance_id.blank? || @instance_id.is_a?(Array)
76
+ # If the id is for only a single record, attempt to find it.
77
+ if instance = @session.identity_map.get(@primary_class, @instance_id)
78
+ return instance
79
+ end
44
80
  end
45
- end
46
-
47
- def select
48
- @select_columns || @select_columns = begin
49
- select_columns = @options[:select]
50
- unless select_columns.nil?
51
- select_columns = select_columns.kind_of?(Array) ? select_columns : [select_columns]
52
- select_columns.map { |column| @adapter.quote_column_name(column.to_s) }
81
+
82
+ results = []
83
+
84
+ # Execute the statement and load the objects.
85
+ @adapter.execute(*to_parameterized_sql) do |reader, num_rows|
86
+ if @options.has_key?(:intercept_load)
87
+ load(reader, &@options[:intercept_load])
53
88
  else
54
- @options[:select] = @adapter[klass].columns.select do |column|
55
- include?(column.name) || !column.lazy?
56
- end.map { |column| column.to_sql }
89
+ load(reader)
57
90
  end
58
91
  end
59
- end
60
-
61
- def table_name
62
- @table_name || @table_name = if @options.has_key?(:table)
63
- @adapter.quote_table_name(@options[:table])
92
+
93
+ results += @loaders[@primary_class].loaded_set
94
+
95
+ if @limit == 1 || (@instance_id && !@instance_id.is_a?(Array))
96
+ results.first
64
97
  else
65
- @adapter[klass].to_sql
98
+ results
66
99
  end
67
100
  end
68
101
 
69
- def to_sql
70
- sql = 'SELECT ' << select.join(', ') << ' FROM ' << table_name
71
-
72
- where = []
73
-
74
- where += conditions.to_a unless conditions.empty?
75
-
76
- unless where.empty?
77
- sql << ' WHERE (' << where.join(') AND (') << ')'
78
- end
79
-
80
- unless order.nil?
81
- sql << ' ORDER BY ' << order.to_s
82
- end
83
-
84
- unless limit.nil?
85
- sql << ' LIMIT ' << limit.to_s
102
+ def load(reader)
103
+ # The following blocks are identical aside from the yield.
104
+ # It's written this way to avoid a conditional within each
105
+ # iterator, and to take advantage of the performance of
106
+ # yield vs. Proc#call.
107
+ if block_given?
108
+ reader.each do
109
+ @loaders.each_pair do |klass,loader|
110
+ row = reader.current_row
111
+ yield(loader.materialize(row), @columns, row)
112
+ end
113
+ end
114
+ else
115
+ reader.each do
116
+ @loaders.each_pair do |klass,loader|
117
+ loader.materialize(reader.current_row)
118
+ end
119
+ end
86
120
  end
87
-
88
- return sql
89
121
  end
90
122
 
91
- def call
92
- if instance_id && !reload?
93
- if instance_id.kind_of?(Array)
94
- instances = instance_id.map do |id|
95
- @session.identity_map.get(klass, id)
96
- end.compact
97
-
98
- return instances if instances.size == instance_id.size
99
- else
100
- instance = @session.identity_map.get(klass, instance_id)
101
- return instance unless instance.nil?
102
- end
123
+ # Generate a select statement based on the initialization
124
+ # arguments.
125
+ def to_parameterized_sql
126
+ parameters = []
127
+
128
+ sql = 'SELECT ' << columns_for_select.join(', ')
129
+ sql << ' FROM ' << from_table_name
130
+
131
+ included_associations.each do |association|
132
+ sql << ' ' << association.to_sql
103
133
  end
104
-
105
- reader = execute(to_sql)
106
-
107
- results = if eof?(reader)
108
- nil
109
- elsif limit == 1 || ( instance_id && !instance_id.kind_of?(Array) )
110
- fetch_one(reader)
111
- else
112
- fetch_all(reader)
134
+
135
+ shallow_included_associations.each do |association|
136
+ sql << ' ' << association.to_shallow_sql
113
137
  end
114
138
 
115
- close_reader(reader)
139
+ unless conditions.empty?
140
+ where_clause, *parameters = conditions.to_parameterized_sql
141
+ sql << ' WHERE ' << where_clause
142
+ end
116
143
 
117
- return results
118
- end
119
-
120
- def load(hash, set = [])
121
-
122
- instance_class = unless hash['type'].nil?
123
- Kernel::const_get(hash['type'])
124
- else
125
- klass
144
+ unless @order.nil?
145
+ sql << ' ORDER BY ' << @order.to_s
126
146
  end
127
-
128
- mapping = @adapter[instance_class]
129
-
130
- instance_id = mapping.key.type_cast_value(hash['id'])
131
- instance = @session.identity_map.get(instance_class, instance_id)
132
-
133
- if instance.nil? || reload?
134
- instance ||= instance_class.new
135
- instance.class.callbacks.execute(:before_materialize, instance)
136
-
137
- instance.instance_variable_set(:@new_record, false)
138
- hash.each_pair do |name_as_string,raw_value|
139
- name = name_as_string.to_sym
140
- if column = mapping.find_by_column_name(name)
141
- value = column.type_cast_value(raw_value)
142
- instance.instance_variable_set(column.instance_variable_name, value)
143
- else
144
- instance.instance_variable_set("@#{name}", value)
145
- end
146
- instance.original_hashes[name] = value.hash
147
- end
148
-
149
- instance.instance_variable_set(:@__key, instance_id)
150
-
151
- instance.class.callbacks.execute(:after_materialize, instance)
152
-
153
- @session.identity_map.set(instance)
147
+
148
+ unless @limit.nil?
149
+ sql << ' LIMIT ' << @limit.to_s
154
150
  end
155
-
156
- instance.instance_variable_set(:@loaded_set, set)
157
- instance.session = @session
158
- set << instance
159
- return instance
160
- end
161
-
162
- def load_instances(fields, rows)
163
- table = @adapter[klass]
164
151
 
165
- set = []
166
- columns = {}
167
- key_ordinal = nil
168
- key_column = table.key
169
- type_ordinal = nil
170
- type_column = nil
171
-
172
- fields.each_with_index do |field, i|
173
- column = table.find_by_column_name(field.to_sym)
174
- key_ordinal = i if column.key?
175
- type_ordinal, type_column = i, column if column.name == :type
176
- columns[column] = i
152
+ unless @offset.nil?
153
+ sql << ' OFFSET ' << @offset.to_s
177
154
  end
178
155
 
179
- if type_ordinal
180
-
181
- tables = Hash.new() do |h,k|
182
-
183
- table_for_row = @adapter[k.blank? ? klass : type_column.type_cast_value(k)]
184
- key_ordinal_for_row = nil
185
- columns_for_row = {}
156
+ parameters.unshift(sql)
157
+ end
158
+
159
+ def qualify_columns?
160
+ return @qualify_columns unless @qualify_columns.nil?
161
+ @qualify_columns = !(included_associations.empty? && shallow_included_associations.empty?)
162
+ end
163
+
164
+ private
165
+ # Return the Sql-escaped columns names to be selected in the results.
166
+ def columns_for_select
167
+ @columns_for_select || begin
168
+ qualify_columns = qualify_columns?
169
+ @columns_for_select = []
186
170
 
187
- fields.each_with_index do |field, i|
188
- column = table_for_row.find_by_column_name(field.to_sym)
189
- key_ordinal_for_row = i if column.key?
190
- columns_for_row[column] = i
171
+ columns.each_with_index do |column,i|
172
+ class_for_loader = column.table.klass
173
+ @loaders[class_for_loader].add_column(column, i) if class_for_loader
174
+ @columns_for_select << column.to_sql(qualify_columns)
191
175
  end
192
176
 
193
- h[k] = [ table_for_row.klass, table_for_row.key, key_ordinal_for_row, columns_for_row ]
177
+ @columns_for_select
194
178
  end
195
179
 
196
- rows.each do |row|
197
- klass_for_row, key_column_for_row, key_ordinal_for_row, columns_for_row = *tables[row[type_ordinal]]
180
+ end
181
+
182
+ # Returns the DataMapper::Adapters::Sql::Mappings::Column instances to
183
+ # be selected in the results.
184
+ def columns
185
+ @columns || begin
186
+ @columns = primary_class_columns
187
+ @columns += included_columns
198
188
 
199
- load_instance(
200
- create_instance(
201
- klass_for_row,
202
- key_column_for_row.type_cast_value(row[key_ordinal_for_row])
203
- ),
204
- columns_for_row,
205
- row,
206
- set
207
- )
208
- end
209
- else
210
- rows.each do |row|
211
- load_instance(
212
- create_instance(
213
- klass,
214
- key_column.type_cast_value(row[key_ordinal])
215
- ),
216
- columns,
217
- row,
218
- set
219
- )
189
+ included_associations.each do |assoc|
190
+ @columns += assoc.association_columns
191
+ end
192
+
193
+ shallow_included_associations.each do |assoc|
194
+ @columns += assoc.join_columns
195
+ end
196
+
197
+ @columns
220
198
  end
221
199
  end
222
200
 
223
- set.dup
224
- end
225
-
226
- # Create an instance for the specified Class and id in
227
- # preparation for loading. This method first checks to
228
- # see if the instance is in the IdentityMap.
229
- # If not, then a new class is created, it's marked as
230
- # not-new, the key is set and it's added to the IdentityMap.
231
- # Afterwards the instance's Session is updated to the current
232
- # session, and the instance returned.
233
- def create_instance(instance_class, instance_id)
234
- instance = @session.identity_map.get(instance_class, instance_id)
235
-
236
- if instance.nil? || reload?
237
- instance = instance_class.new()
238
- instance.instance_variable_set(:@__key, instance_id)
239
- instance.instance_variable_set(:@new_record, false)
240
- @session.identity_map.set(instance)
201
+ # Returns the default columns for the primary_class_table,
202
+ # or maps symbols specified in a +:select+ option to columns
203
+ # in the primary_class_table.
204
+ def primary_class_columns
205
+ @primary_class_columns || @primary_class_columns = begin
206
+ if @options.has_key?(:select)
207
+ case x = @options[:select]
208
+ when Array then x
209
+ when Symbol then [x]
210
+ else raise ':select option must be a Symbol, or an Array of Symbols'
211
+ end.map { |name| primary_class_table[name] }
212
+ else
213
+ primary_class_table.columns.reject { |column| column.lazy? }
214
+ end
215
+ end
241
216
  end
242
217
 
243
- instance.session = @session
244
-
245
- return instance
246
- end
247
-
248
- def load_instance(instance, columns, values, set = [])
218
+ def included_associations
219
+ @included_associations || @included_associations = begin
220
+ associations = primary_class_table.associations
221
+ include_options.map do |name|
222
+ associations[name]
223
+ end.compact
224
+ end
225
+ end
249
226
 
250
- instance.class.callbacks.execute(:before_materialize, instance)
227
+ def shallow_included_associations
228
+ @shallow_included_associations || @shallow_included_associations = begin
229
+ associations = primary_class_table.associations
230
+ shallow_include_options.map do |name|
231
+ associations[name]
232
+ end.compact
233
+ end
234
+ end
251
235
 
252
- hashes = {}
236
+ def included_columns
237
+ @included_columns || @included_columns = begin
238
+ include_options.map do |name|
239
+ primary_class_table[name]
240
+ end.compact
241
+ end
242
+ end
253
243
 
254
- columns.each_pair do |column, i|
255
- hashes[column.name] = instance.instance_variable_set(
256
- column.instance_variable_name,
257
- column.type_cast_value(values[i])
258
- ).hash
244
+ def include_options
245
+ @include_options || @include_options = begin
246
+ case x = @options[:include]
247
+ when Array then x
248
+ when Symbol then [x]
249
+ else []
250
+ end
251
+ end
259
252
  end
260
253
 
261
- instance.instance_variable_set(:@original_hashes, hashes)
254
+ def shallow_include_options
255
+ @shallow_include_options || @shallow_include_options = begin
256
+ case x = @options[:shallow_include]
257
+ when Array then x
258
+ when Symbol then [x]
259
+ else []
260
+ end
261
+ end
262
+ end
262
263
 
263
- instance.instance_variable_set(:@loaded_set, set)
264
- set << instance
264
+ # Determine if a Column should be included based on the
265
+ # value of the +:include+ option.
266
+ def include_column?(name)
267
+ !primary_class_table[name].lazy? || include_options.includes?(name)
268
+ end
269
+
270
+ # Return the Sql-escaped table name of the +primary_class+.
271
+ def from_table_name
272
+ @from_table_name || (@from_table_name = @adapter.table(@primary_class).to_sql)
273
+ end
265
274
 
266
- instance.class.callbacks.execute(:after_materialize, instance)
275
+ # Returns the DataMapper::Adapters::Sql::Mappings::Table for the +primary_class+.
276
+ def primary_class_table
277
+ @primary_class_table || (@primary_class_table = @adapter.table(@primary_class))
278
+ end
267
279
 
268
- return instance
269
- end
270
-
271
- protected
272
- def count_rows(reader)
273
- raise NotImplementedError.new
274
- end
275
-
276
- def close_reader(reader)
277
- raise NotImplementedError.new
278
- end
279
-
280
- def execute(sql)
281
- raise NotImplementedError.new
282
- end
283
-
284
- def fetch_one(reader)
285
- raise NotImplementedError.new
286
- end
287
-
288
- def fetch_all(reader)
289
- raise NotImplementedError.new
290
- end
280
+ def partition_options(options)
281
+ find_options = @adapter.class::FIND_OPTIONS
282
+ conditions_hash = {}
283
+ options_hash = {}
284
+ options.each do |key,value|
285
+ if key != :conditions && find_options.include?(key)
286
+ options_hash[key] = value
287
+ else
288
+ conditions_hash[key] = value
289
+ end
290
+ end
291
+
292
+ [ options_hash, conditions_hash ]
293
+ end
291
294
 
292
295
  end # class LoadCommand
293
296
  end # module Commands