datamapper 0.1.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/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- metadata +134 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2007 Samuel Smoot
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Start out with example.rb and go from there if you want to try it out live.
|
data/example.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift('lib')
|
2
|
+
|
3
|
+
require 'data_mapper'
|
4
|
+
|
5
|
+
if ENV['ADAPTER'] == 'sqlite3'
|
6
|
+
DataMapper::Database.setup do
|
7
|
+
adapter 'sqlite3'
|
8
|
+
database 'data_mapper_1.db'
|
9
|
+
log_stream 'example.log'
|
10
|
+
log_level Logger::DEBUG
|
11
|
+
end
|
12
|
+
else
|
13
|
+
DataMapper::Database.setup do
|
14
|
+
adapter 'mysql'
|
15
|
+
host 'localhost'
|
16
|
+
username 'root'
|
17
|
+
database 'data_mapper_1'
|
18
|
+
log_stream 'example.log'
|
19
|
+
log_level Logger::DEBUG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir[File.dirname(__FILE__) + '/spec/models/*.rb'].each do |path|
|
24
|
+
load path
|
25
|
+
end
|
data/lib/data_mapper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This line just let's us require anything in the +lib+ sub-folder
|
2
|
+
# without specifying a full path.
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
# Require the basics...
|
6
|
+
require 'data_mapper/support/symbol'
|
7
|
+
require 'data_mapper/support/string'
|
8
|
+
require 'data_mapper/support/proc'
|
9
|
+
require 'data_mapper/support/inflector'
|
10
|
+
require 'data_mapper/database'
|
11
|
+
require 'data_mapper/base'
|
12
|
+
|
13
|
+
# This block of code is for compatibility with Ruby On Rails' database.yml
|
14
|
+
# file, allowing you to simply require the data_mapper.rb in your
|
15
|
+
# Rails application's environment.rb to configure the DataMapper.
|
16
|
+
if defined? RAILS_ENV
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
rails_config = YAML::load_file(RAILS_ROOT + '/config/database.yml')
|
20
|
+
current_config = rails_config[RAILS_ENV.to_s]
|
21
|
+
|
22
|
+
DataMapper::Database.setup do
|
23
|
+
adapter current_config['adapter']
|
24
|
+
host current_config['host']
|
25
|
+
database current_config['database']
|
26
|
+
username current_config['username']
|
27
|
+
password current_config['password']
|
28
|
+
cache WeakHash::Factory
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'data_mapper/queries/select_statement'
|
2
|
+
require 'data_mapper/queries/insert_statement'
|
3
|
+
require 'data_mapper/queries/update_statement'
|
4
|
+
require 'data_mapper/queries/delete_statement'
|
5
|
+
require 'data_mapper/queries/truncate_table_statement'
|
6
|
+
require 'data_mapper/queries/create_table_statement'
|
7
|
+
require 'data_mapper/queries/drop_table_statement'
|
8
|
+
require 'data_mapper/queries/table_exists_statement'
|
9
|
+
require 'data_mapper/queries/connection'
|
10
|
+
|
11
|
+
module DataMapper
|
12
|
+
|
13
|
+
# An Adapter is really a Factory for three types of object,
|
14
|
+
# so they can be selectively sub-classed where needed.
|
15
|
+
#
|
16
|
+
# The first type is a Query. The Query is an object describing
|
17
|
+
# the database-specific operations we wish to perform, in an
|
18
|
+
# abstract manner. For example: While most if not all databases
|
19
|
+
# support a mechanism for limiting the size of results returned,
|
20
|
+
# some use a "LIMIT" keyword, while others use a "TOP" keyword.
|
21
|
+
# We can set a SelectStatement#limit field then, and allow
|
22
|
+
# the adapter to override the underlying SQL generated.
|
23
|
+
# Refer to DataMapper::Queries.
|
24
|
+
#
|
25
|
+
# The second type provided by the Adapter is a DataMapper::Connection.
|
26
|
+
# This allows us to execute queries and return results in a clear and
|
27
|
+
# uniform manner we can use throughout the DataMapper.
|
28
|
+
#
|
29
|
+
# The final type provided is a DataMapper::Transaction.
|
30
|
+
# Transactions are duck-typed Connections that span multiple queries.
|
31
|
+
#
|
32
|
+
# Note: It is assumed that the Adapter implements it's own
|
33
|
+
# ConnectionPool if any since some libraries implement their own at
|
34
|
+
# a low-level, and it wouldn't make sense to pay a performance
|
35
|
+
# cost twice by implementing a secondary pool in the DataMapper itself.
|
36
|
+
# If the library being adapted does not provide such functionality,
|
37
|
+
# DataMapper::Support::ConnectionPool can be used.
|
38
|
+
module Adapters
|
39
|
+
|
40
|
+
# You must inherit from the Abstract::Adapter, and implement the
|
41
|
+
# required methods to adapt a database library for use with the DataMapper.
|
42
|
+
#
|
43
|
+
# NOTE: By inheriting from AbstractAdapter, you get a copy of all the
|
44
|
+
# standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
|
45
|
+
# You can extend and overwrite these copies without affecting the originals.
|
46
|
+
class AbstractAdapter
|
47
|
+
|
48
|
+
# Instantiate an Adapter by passing it a DataMapper::Database
|
49
|
+
# object for configuration.
|
50
|
+
def initialize(configuration)
|
51
|
+
@configuration = configuration
|
52
|
+
end
|
53
|
+
|
54
|
+
def connection(&block)
|
55
|
+
raise NotImplementedError.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def transaction(&block)
|
59
|
+
raise NotImplementedError.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# This callback copies and sub-classes modules and classes
|
63
|
+
# in the AbstractAdapter to the inherited class so you don't
|
64
|
+
# have to copy and paste large blocks of code from the
|
65
|
+
# AbstractAdapter.
|
66
|
+
#
|
67
|
+
# Basically, when inheriting from the AbstractAdapter, you
|
68
|
+
# aren't just inheriting a single class, you're inheriting
|
69
|
+
# a whole graph of Types. For convenience.
|
70
|
+
def self.inherited(base)
|
71
|
+
|
72
|
+
quoting = base.const_set('Quoting', Module.new)
|
73
|
+
quoting.send(:include, Quoting)
|
74
|
+
|
75
|
+
coersion = base.const_set('Coersion', Module.new)
|
76
|
+
coersion.send(:include, Coersion)
|
77
|
+
|
78
|
+
queries = base.const_set('Queries', Module.new)
|
79
|
+
|
80
|
+
Queries.constants.each do |name|
|
81
|
+
queries.const_set(name, Class.new(Queries.const_get(name)))
|
82
|
+
end
|
83
|
+
|
84
|
+
base.const_set('TYPES', TYPES.dup)
|
85
|
+
|
86
|
+
base.const_set('SYNTAX', SYNTAX.dup)
|
87
|
+
end
|
88
|
+
|
89
|
+
TYPES = {
|
90
|
+
:integer => 'int'.freeze,
|
91
|
+
:string => 'varchar'.freeze,
|
92
|
+
:text => 'text'.freeze,
|
93
|
+
:class => 'varchar'.freeze
|
94
|
+
}
|
95
|
+
|
96
|
+
SYNTAX = {
|
97
|
+
:auto_increment => 'auto_increment'.freeze
|
98
|
+
}
|
99
|
+
|
100
|
+
# Quoting is a mixin that extends your DataMapper::Database singleton-class
|
101
|
+
# to allow for object-name and value quoting to be exposed to the queries.
|
102
|
+
#
|
103
|
+
# DESIGN: Is there any need for this outside of the query objects? Should
|
104
|
+
# we just include it in our query object subclasses and not rely on a Quoting
|
105
|
+
# mixin being part of the "standard" Adapter interface?
|
106
|
+
module Quoting
|
107
|
+
|
108
|
+
def quote_table_name(name)
|
109
|
+
name.ensure_wrapped_with('"')
|
110
|
+
end
|
111
|
+
|
112
|
+
def quote_column_name(name)
|
113
|
+
name.ensure_wrapped_with('"')
|
114
|
+
end
|
115
|
+
|
116
|
+
def quote_value(value)
|
117
|
+
return 'NULL' if value.nil?
|
118
|
+
|
119
|
+
case value
|
120
|
+
when Numeric then value.to_s
|
121
|
+
when String then "'#{value.gsub("'", "''")}'"
|
122
|
+
when Class then "'#{value.name}'"
|
123
|
+
when Date then "'#{value.to_s}'"
|
124
|
+
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
125
|
+
when TrueClass, FalseClass then value.to_s.upcase
|
126
|
+
else raise "Don't know how to quote #{value.inspect}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end # module Quoting
|
131
|
+
|
132
|
+
# Coersion is a mixin that allows for coercing database values to Ruby Types.
|
133
|
+
#
|
134
|
+
# DESIGN: Probably should handle the opposite scenario here too. I believe that's
|
135
|
+
# currently in DataMapper::Database, which is obviously not a very good spot for
|
136
|
+
# it.
|
137
|
+
module Coersion
|
138
|
+
|
139
|
+
def type_cast_value(type, raw_value)
|
140
|
+
return nil if raw_value.nil?
|
141
|
+
|
142
|
+
case type
|
143
|
+
when :class then Kernel::const_get(raw_value)
|
144
|
+
when :string, :text then
|
145
|
+
return nil if raw_value.nil?
|
146
|
+
value_as_string = raw_value.to_s.strip
|
147
|
+
return nil if value_as_string.empty?
|
148
|
+
value_as_string
|
149
|
+
when :integer then
|
150
|
+
return nil if raw_value.nil? || (raw_value.kind_of?(String) && raw_value.empty?)
|
151
|
+
begin
|
152
|
+
Integer(raw_value)
|
153
|
+
rescue ArgumentError
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
else
|
157
|
+
if respond_to?("type_cast_#{type}")
|
158
|
+
send("type_cast_#{type}", raw_value)
|
159
|
+
else
|
160
|
+
raise "Don't know how to type-cast #{{ type => raw_value }.inspect }"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end # module Coersion
|
166
|
+
|
167
|
+
# You define your custom queries in a sub-module called Queries.
|
168
|
+
# If you don't need to redefine any of the default functionality/syntax,
|
169
|
+
# you can just create constants that point to the standard queries:
|
170
|
+
#
|
171
|
+
# SelectStatement = DataMapper::Queries::SelectStatement
|
172
|
+
#
|
173
|
+
# It's just as easy to turn that into a sub-class however:
|
174
|
+
#
|
175
|
+
# class SelectStatement < DataMapper::Queries::SelectStatement
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# You sub-class and edit instead of overwrite because you want to
|
179
|
+
# make sure your changes only affect this database adapter and avoid
|
180
|
+
# introducing incompatibilities into other adapters.
|
181
|
+
module Queries
|
182
|
+
|
183
|
+
# Your Connection class is one of two that will be almost completely custom.
|
184
|
+
# Refer to DataMapper::Queries::Connection for the required interface.
|
185
|
+
class Connection < DataMapper::Queries::Connection
|
186
|
+
end
|
187
|
+
|
188
|
+
# Reader is the other Connection related class that will be almost completely custom.
|
189
|
+
# The idea with the Reader is to avoid creating a large Array of Hash objects to
|
190
|
+
# represent rows since the hashes will be discarded almost immediately. Create only
|
191
|
+
# what you need. So the reader creates a single Hash to associate columns with their
|
192
|
+
# ordinals in the result set, then indexing the Reader for each row results in looking
|
193
|
+
# up the column index, then the value for that index in the current row array.
|
194
|
+
class Reader < DataMapper::Queries::Reader
|
195
|
+
end
|
196
|
+
|
197
|
+
class Result < DataMapper::Queries::Result
|
198
|
+
end
|
199
|
+
|
200
|
+
class DeleteStatement < DataMapper::Queries::DeleteStatement
|
201
|
+
end
|
202
|
+
|
203
|
+
class InsertStatement < DataMapper::Queries::InsertStatement
|
204
|
+
end
|
205
|
+
|
206
|
+
class SelectStatement < DataMapper::Queries::SelectStatement
|
207
|
+
end
|
208
|
+
|
209
|
+
class TruncateTableStatement < DataMapper::Queries::TruncateTableStatement
|
210
|
+
end
|
211
|
+
|
212
|
+
class UpdateStatement < DataMapper::Queries::UpdateStatement
|
213
|
+
end
|
214
|
+
|
215
|
+
class CreateTableStatement < DataMapper::Queries::CreateTableStatement
|
216
|
+
end
|
217
|
+
|
218
|
+
class DropTableStatement < DataMapper::Queries::DropTableStatement
|
219
|
+
end
|
220
|
+
|
221
|
+
class TableExistsStatement < DataMapper::Queries::TableExistsStatement
|
222
|
+
end
|
223
|
+
|
224
|
+
end # module Queries
|
225
|
+
|
226
|
+
end # class AbstractAdapter
|
227
|
+
|
228
|
+
end # module Adapters
|
229
|
+
end # module DataMapper
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'data_mapper/adapters/abstract_adapter'
|
2
|
+
require 'data_mapper/support/connection_pool'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'mysql'
|
6
|
+
rescue LoadError
|
7
|
+
STDERR.puts <<-EOS.gsub(/^(\s+)/, '')
|
8
|
+
This adapter currently depends on the \"mysql\" gem.
|
9
|
+
If some kind soul would like to make it work with
|
10
|
+
a pure-ruby version that'd be super spiffy.
|
11
|
+
EOS
|
12
|
+
|
13
|
+
raise
|
14
|
+
end
|
15
|
+
|
16
|
+
module DataMapper
|
17
|
+
module Adapters
|
18
|
+
|
19
|
+
class MysqlAdapter < AbstractAdapter
|
20
|
+
|
21
|
+
def initialize(configuration)
|
22
|
+
super
|
23
|
+
@connections = Support::ConnectionPool.new { Queries::Connection.new(@configuration) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def connection
|
27
|
+
raise ArgumentError.new('MysqlAdapter#connection requires a block-parameter') unless block_given?
|
28
|
+
begin
|
29
|
+
@connections.hold { |connection| yield connection }
|
30
|
+
rescue Mysql::Error => me
|
31
|
+
|
32
|
+
@configuration.log.fatal(me)
|
33
|
+
|
34
|
+
@connections.available_connections.each do |sock|
|
35
|
+
begin
|
36
|
+
sock.close
|
37
|
+
rescue => se
|
38
|
+
@configuration.log.error(se)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@connections.available_connections.clear
|
43
|
+
raise me
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Quoting
|
48
|
+
|
49
|
+
def quote_table_name(name)
|
50
|
+
name.ensure_wrapped_with('`')
|
51
|
+
end
|
52
|
+
|
53
|
+
def quote_column_name(name)
|
54
|
+
name.ensure_wrapped_with('`')
|
55
|
+
end
|
56
|
+
|
57
|
+
end # module Quoting
|
58
|
+
|
59
|
+
module Coersion
|
60
|
+
|
61
|
+
def type_cast_boolean(value)
|
62
|
+
case value
|
63
|
+
when TrueClass, FalseClass then value
|
64
|
+
when "1", "true", "TRUE" then true
|
65
|
+
when "0", nil then false
|
66
|
+
else "Can't type-cast #{value.inspect} to a boolean"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def type_cast_datetime(value)
|
71
|
+
case value
|
72
|
+
when DateTime then value
|
73
|
+
when Date then DateTime.new(value)
|
74
|
+
when String then DateTime::parse(value)
|
75
|
+
else "Can't type-cast #{value.inspect} to a datetime"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end # module Coersion
|
80
|
+
|
81
|
+
module Queries
|
82
|
+
|
83
|
+
class Connection
|
84
|
+
|
85
|
+
def initialize(database)
|
86
|
+
@database = database
|
87
|
+
@dbh = Mysql.new(database.host, database.username, database.password, database.database)
|
88
|
+
database.log.debug("Initializing Connection for Database[#{database.name}]")
|
89
|
+
super(database.log)
|
90
|
+
end
|
91
|
+
|
92
|
+
def execute(statement)
|
93
|
+
send_query(statement)
|
94
|
+
Result.new(@dbh.affected_rows, @dbh.insert_id)
|
95
|
+
end
|
96
|
+
|
97
|
+
def query(statement)
|
98
|
+
Reader.new(send_query(statement))
|
99
|
+
end
|
100
|
+
|
101
|
+
def close
|
102
|
+
@dbh.close
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def send_query(statement)
|
107
|
+
sql = statement.respond_to?(:to_sql) ? statement.to_sql : statement
|
108
|
+
log.debug("Database[#{@database.name}] => #{sql}")
|
109
|
+
@dbh.query(sql)
|
110
|
+
end
|
111
|
+
|
112
|
+
end # class Connection
|
113
|
+
|
114
|
+
class Reader
|
115
|
+
|
116
|
+
include Enumerable
|
117
|
+
|
118
|
+
def initialize(results)
|
119
|
+
@results = results
|
120
|
+
@columns = {}
|
121
|
+
results.fetch_fields.each_with_index do |field, index|
|
122
|
+
@columns[field.name] = index
|
123
|
+
end
|
124
|
+
@current_row_index = 0
|
125
|
+
end
|
126
|
+
|
127
|
+
def eof?
|
128
|
+
records_affected <= @current_row_index
|
129
|
+
end
|
130
|
+
|
131
|
+
def records_affected
|
132
|
+
@results.num_rows
|
133
|
+
end
|
134
|
+
|
135
|
+
def next
|
136
|
+
@current_row_index += 1
|
137
|
+
@current_row = @results.fetch_row
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def each
|
142
|
+
@results.each do |row|
|
143
|
+
@current_row = row
|
144
|
+
yield self
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def [](column)
|
149
|
+
index = @columns[column]
|
150
|
+
return nil if index.nil?
|
151
|
+
@current_row[index]
|
152
|
+
end
|
153
|
+
|
154
|
+
def each_pair
|
155
|
+
@columns.each_pair do |column_name, index|
|
156
|
+
yield(column_name, @current_row[index])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def close
|
161
|
+
@results.free
|
162
|
+
end
|
163
|
+
|
164
|
+
end # class Reader
|
165
|
+
|
166
|
+
end # module Queries
|
167
|
+
|
168
|
+
end # class MysqlAdapter
|
169
|
+
|
170
|
+
end # module Adapters
|
171
|
+
end # module DataMapper
|