datamapper 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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