datamapper 0.1.0 → 0.1.1
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 +31 -1
- data/MIT-LICENSE +1 -1
- data/README +9 -1
- data/example.rb +23 -15
- data/lib/data_mapper.rb +5 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +9 -207
- data/lib/data_mapper/adapters/mysql_adapter.rb +132 -108
- data/lib/data_mapper/adapters/postgresql_adapter.rb +242 -0
- data/lib/data_mapper/adapters/sql/coersion.rb +74 -0
- data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +140 -0
- data/lib/data_mapper/adapters/sql/commands/conditions.rb +161 -0
- data/lib/data_mapper/adapters/sql/commands/delete_command.rb +113 -0
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +296 -0
- data/lib/data_mapper/adapters/sql/commands/save_command.rb +141 -0
- data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +33 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +91 -0
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +30 -0
- data/lib/data_mapper/adapters/sql/mappings/table.rb +143 -0
- data/lib/data_mapper/adapters/sql/quoting.rb +38 -0
- data/lib/data_mapper/adapters/sql_adapter.rb +163 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +155 -116
- data/lib/data_mapper/associations.rb +2 -0
- data/lib/data_mapper/associations/advanced_has_many_association.rb +55 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +2 -2
- data/lib/data_mapper/associations/has_many_association.rb +3 -3
- data/lib/data_mapper/associations/has_one_association.rb +2 -2
- data/lib/data_mapper/base.rb +30 -11
- data/lib/data_mapper/callbacks.rb +4 -1
- data/lib/data_mapper/database.rb +8 -41
- data/lib/data_mapper/identity_map.rb +23 -3
- data/lib/data_mapper/session.rb +34 -186
- data/lib/data_mapper/{extensions → support}/active_record_impersonation.rb +16 -12
- data/lib/data_mapper/support/blank.rb +35 -0
- data/lib/data_mapper/support/connection_pool.rb +2 -1
- data/lib/data_mapper/support/string.rb +27 -0
- data/lib/data_mapper/support/struct.rb +26 -0
- data/lib/data_mapper/validations/unique_validator.rb +1 -3
- data/lib/data_mapper/validations/validation_helper.rb +1 -1
- data/performance.rb +24 -7
- data/profile_data_mapper.rb +24 -2
- data/rakefile.rb +2 -2
- data/spec/basic_finder.rb +2 -2
- data/spec/belongs_to.rb +1 -1
- data/spec/delete_command_spec.rb +9 -0
- data/spec/fixtures/zoos.yaml +4 -0
- data/spec/has_many.rb +1 -1
- data/spec/load_command_spec.rb +44 -0
- data/spec/models/zoo.rb +2 -0
- data/spec/save_command_spec.rb +13 -0
- data/spec/spec_helper.rb +10 -1
- data/spec/support/string_spec.rb +7 -0
- data/spec/validates_confirmation_of.rb +1 -1
- data/spec/validates_format_of.rb +1 -1
- data/spec/validates_length_of.rb +1 -1
- data/spec/validations.rb +1 -1
- metadata +23 -20
- data/lib/data_mapper/extensions/callback_helpers.rb +0 -35
- data/lib/data_mapper/loaded_set.rb +0 -45
- data/lib/data_mapper/mappings/column.rb +0 -78
- data/lib/data_mapper/mappings/schema.rb +0 -28
- data/lib/data_mapper/mappings/table.rb +0 -99
- data/lib/data_mapper/queries/conditions.rb +0 -141
- data/lib/data_mapper/queries/connection.rb +0 -34
- data/lib/data_mapper/queries/create_table_statement.rb +0 -38
- data/lib/data_mapper/queries/delete_statement.rb +0 -17
- data/lib/data_mapper/queries/drop_table_statement.rb +0 -17
- data/lib/data_mapper/queries/insert_statement.rb +0 -29
- data/lib/data_mapper/queries/reader.rb +0 -42
- data/lib/data_mapper/queries/result.rb +0 -19
- data/lib/data_mapper/queries/select_statement.rb +0 -103
- data/lib/data_mapper/queries/table_exists_statement.rb +0 -17
- data/lib/data_mapper/queries/truncate_table_statement.rb +0 -17
- data/lib/data_mapper/queries/update_statement.rb +0 -25
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'data_mapper/adapters/abstract_adapter'
|
2
|
+
require 'data_mapper/adapters/sql/commands/load_command'
|
3
|
+
require 'data_mapper/adapters/sql/commands/advanced_load_command'
|
4
|
+
require 'data_mapper/adapters/sql/commands/save_command'
|
5
|
+
require 'data_mapper/adapters/sql/commands/delete_command'
|
6
|
+
require 'data_mapper/adapters/sql/commands/table_exists_command'
|
7
|
+
require 'data_mapper/adapters/sql/coersion'
|
8
|
+
require 'data_mapper/adapters/sql/quoting'
|
9
|
+
require 'data_mapper/adapters/sql/mappings/schema'
|
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 SqlAdapter, and implement the
|
41
|
+
# required methods to adapt a database library for use with the DataMapper.
|
42
|
+
#
|
43
|
+
# NOTE: By inheriting from SqlAdapter, 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 SqlAdapter < AbstractAdapter
|
47
|
+
|
48
|
+
FIND_OPTIONS = [
|
49
|
+
:select, :limit, :class, :include, :reload, :conditions, :order
|
50
|
+
]
|
51
|
+
|
52
|
+
def initialize(configuration)
|
53
|
+
super
|
54
|
+
@single_threaded = configuration.single_threaded
|
55
|
+
end
|
56
|
+
|
57
|
+
def single_threaded?
|
58
|
+
@single_threaded
|
59
|
+
end
|
60
|
+
|
61
|
+
def connection(&block)
|
62
|
+
raise NotImplementedError.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def transaction(&block)
|
66
|
+
raise NotImplementedError.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def query(sql)
|
70
|
+
raise NotImplementedError.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def schema
|
74
|
+
@schema || ( @schema = Mappings::Schema.new(self) )
|
75
|
+
end
|
76
|
+
|
77
|
+
def table_exists?(name)
|
78
|
+
self.class::Commands::TableExistsCommand.new(self, name).call
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete(klass_or_instance, options = nil)
|
82
|
+
self.class::Commands::DeleteCommand.new(self, klass_or_instance, options).call
|
83
|
+
end
|
84
|
+
|
85
|
+
def save(session, instance)
|
86
|
+
self.class::Commands::SaveCommand.new(self, session, instance).call
|
87
|
+
end
|
88
|
+
|
89
|
+
def load(session, klass, options)
|
90
|
+
self.class::Commands::LoadCommand.new(self, session, klass, options).call
|
91
|
+
end
|
92
|
+
|
93
|
+
def [](klass_or_table_name)
|
94
|
+
schema[klass_or_table_name]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Escape a string of SQL with a set of arguments.
|
98
|
+
# The first argument is assumed to be the SQL to escape,
|
99
|
+
# the remaining arguments (if any) are assumed to be
|
100
|
+
# values to escape and interpolate.
|
101
|
+
#
|
102
|
+
# ==== Examples
|
103
|
+
# escape_sql("SELECT * FROM zoos")
|
104
|
+
# # => "SELECT * FROM zoos"
|
105
|
+
#
|
106
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
|
107
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas`"
|
108
|
+
#
|
109
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
|
110
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
|
111
|
+
#
|
112
|
+
# ==== Warning
|
113
|
+
# This method is meant mostly for adapters that don't support
|
114
|
+
# bind-parameters.
|
115
|
+
def escape_sql(*args)
|
116
|
+
sql = args.shift
|
117
|
+
|
118
|
+
unless args.empty?
|
119
|
+
sql.gsub!(/\?/) do |x|
|
120
|
+
quote_value(args.shift)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sql
|
125
|
+
end
|
126
|
+
|
127
|
+
# This callback copies and sub-classes modules and classes
|
128
|
+
# in the AbstractAdapter to the inherited class so you don't
|
129
|
+
# have to copy and paste large blocks of code from the
|
130
|
+
# SqlAdapter.
|
131
|
+
#
|
132
|
+
# Basically, when inheriting from the AbstractAdapter, you
|
133
|
+
# aren't just inheriting a single class, you're inheriting
|
134
|
+
# a whole graph of Types. For convenience.
|
135
|
+
def self.inherited(base)
|
136
|
+
|
137
|
+
queries = base.const_set('Commands', Module.new)
|
138
|
+
|
139
|
+
Sql::Commands.constants.each do |name|
|
140
|
+
queries.const_set(name, Class.new(Sql::Commands.const_get(name)))
|
141
|
+
end
|
142
|
+
|
143
|
+
base.const_set('TYPES', TYPES.dup)
|
144
|
+
base.const_set('FIND_OPTIONS', FIND_OPTIONS.dup)
|
145
|
+
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
149
|
+
TYPES = {
|
150
|
+
:integer => 'int'.freeze,
|
151
|
+
:string => 'varchar'.freeze,
|
152
|
+
:text => 'text'.freeze,
|
153
|
+
:class => 'varchar'.freeze
|
154
|
+
}
|
155
|
+
|
156
|
+
include Sql
|
157
|
+
include Quoting
|
158
|
+
include Coersion
|
159
|
+
|
160
|
+
end # class SqlAdapter
|
161
|
+
|
162
|
+
end # module Adapters
|
163
|
+
end # module DataMapper
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'data_mapper/adapters/
|
1
|
+
require 'data_mapper/adapters/sql_adapter'
|
2
2
|
require 'data_mapper/support/connection_pool'
|
3
3
|
|
4
4
|
require 'sqlite3'
|
@@ -6,11 +6,15 @@ require 'sqlite3'
|
|
6
6
|
module DataMapper
|
7
7
|
module Adapters
|
8
8
|
|
9
|
-
class Sqlite3Adapter <
|
9
|
+
class Sqlite3Adapter < SqlAdapter
|
10
10
|
|
11
11
|
def initialize(configuration)
|
12
12
|
super
|
13
|
-
@connections = Support::ConnectionPool.new
|
13
|
+
@connections = Support::ConnectionPool.new do
|
14
|
+
dbh = SQLite3::Database.new(configuration.database)
|
15
|
+
dbh.results_as_hash = true
|
16
|
+
dbh
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
def connection
|
@@ -34,126 +38,76 @@ module DataMapper
|
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
41
|
+
def query(*args)
|
42
|
+
reader = connection { |db| db.query(escape_sql(*args)) }
|
43
|
+
|
44
|
+
fields = nil
|
45
|
+
rows = []
|
46
|
+
|
47
|
+
until reader.eof?
|
48
|
+
hash = reader.next
|
49
|
+
break if hash.nil?
|
50
|
+
|
51
|
+
fields = hash.keys.select { |field| field.is_a?(String) } unless fields
|
52
|
+
|
53
|
+
rows << fields.map { |field| hash[field] }
|
54
|
+
end
|
55
|
+
|
56
|
+
reader.close
|
57
|
+
|
58
|
+
struct = Support::Struct::define(fields)
|
59
|
+
|
60
|
+
rows.map do |row|
|
61
|
+
struct.new(row)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
37
65
|
TYPES.merge!({
|
38
66
|
:integer => 'INTEGER'.freeze,
|
39
67
|
:string => 'TEXT'.freeze,
|
40
68
|
:text => 'TEXT'.freeze,
|
41
69
|
:class => 'TEXT'.freeze
|
42
70
|
})
|
43
|
-
|
44
|
-
module Coersion
|
45
71
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
72
|
+
TABLE_QUOTING_CHARACTER = '"'.freeze
|
73
|
+
COLUMN_QUOTING_CHARACTER = '"'.freeze
|
74
|
+
|
75
|
+
def type_cast_boolean(value)
|
76
|
+
case value
|
77
|
+
when TrueClass, FalseClass then value
|
78
|
+
when "1", "true", "TRUE" then true
|
79
|
+
when "0", nil then false
|
80
|
+
else "Can't type-cast #{value.inspect} to a boolean"
|
53
81
|
end
|
82
|
+
end
|
54
83
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
84
|
+
def type_cast_datetime(value)
|
85
|
+
case value
|
86
|
+
when DateTime then value
|
87
|
+
when Date then DateTime.new(value)
|
88
|
+
when String then DateTime::parse(value)
|
89
|
+
else "Can't type-cast #{value.inspect} to a datetime"
|
62
90
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
module Queries
|
67
|
-
|
68
|
-
class Connection
|
69
|
-
|
70
|
-
def initialize(database)
|
71
|
-
@database = database
|
72
|
-
@dbh = SQLite3::Database.new(database.database)
|
73
|
-
database.log.debug("Initializing Connection for Database[#{database.name}]")
|
74
|
-
super(database.log)
|
75
|
-
end
|
76
|
-
|
77
|
-
def execute(statement)
|
78
|
-
send_query(statement)
|
79
|
-
Result.new(@dbh.total_changes, @dbh.last_insert_row_id)
|
80
|
-
end
|
81
|
-
|
82
|
-
def query(statement)
|
83
|
-
Reader.new(send_query(statement))
|
84
|
-
end
|
85
|
-
|
86
|
-
def close
|
87
|
-
@dbh.close
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
def send_query(statement)
|
92
|
-
sql = statement.respond_to?(:to_sql) ? statement.to_sql : statement
|
93
|
-
log.debug("Database[#{@database.name}] => #{sql}")
|
94
|
-
@dbh.query(sql)
|
95
|
-
end
|
96
|
-
|
97
|
-
end # class Connection
|
91
|
+
end
|
92
|
+
|
93
|
+
module Commands
|
98
94
|
|
99
|
-
class
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
def initialize(results)
|
104
|
-
@results = results
|
105
|
-
@columns = {}
|
106
|
-
@results.columns.each_with_index do |name, index|
|
107
|
-
@columns[name] = index
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def eof?
|
112
|
-
@results.eof?
|
113
|
-
end
|
114
|
-
|
115
|
-
def records_affected
|
116
|
-
@results.entries.size
|
117
|
-
end
|
118
|
-
|
119
|
-
def next
|
120
|
-
@current_row = @results.next
|
121
|
-
self
|
122
|
-
end
|
123
|
-
|
124
|
-
def each
|
125
|
-
until self.next.eof?
|
126
|
-
yield(self)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def [](column)
|
131
|
-
index = @columns[column]
|
132
|
-
return nil if index.nil? || @current_row.nil?
|
133
|
-
@current_row[index]
|
134
|
-
end
|
135
|
-
|
136
|
-
def each_pair
|
137
|
-
@columns.each_pair do |column_name, index|
|
138
|
-
yield(column_name, @current_row.nil? ? nil : @current_row[index])
|
139
|
-
end
|
95
|
+
class TableExistsCommand
|
96
|
+
def to_sql
|
97
|
+
"SELECT name FROM sqlite_master WHERE type = \"table\" AND name = #{table_name}"
|
140
98
|
end
|
141
99
|
|
142
|
-
def
|
143
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
class TableExistsStatement
|
149
|
-
def to_sql
|
150
|
-
"SELECT name FROM sqlite_master WHERE type = \"table\" AND name = #{@database.quote_value(@database[@klass].name)}"
|
100
|
+
def call
|
101
|
+
reader = @adapter.connection { |db| db.query(to_sql) }
|
102
|
+
result = reader.entries.size > 0
|
103
|
+
reader.close
|
104
|
+
result
|
151
105
|
end
|
152
|
-
end # class
|
106
|
+
end # class TableExistsCommand
|
153
107
|
|
154
|
-
class
|
155
|
-
def
|
156
|
-
table = @
|
108
|
+
class SaveCommand
|
109
|
+
def to_create_table_sql
|
110
|
+
table = @adapter[@instance]
|
157
111
|
|
158
112
|
sql = "CREATE TABLE " << table.to_sql
|
159
113
|
|
@@ -165,7 +119,7 @@ module DataMapper
|
|
165
119
|
end
|
166
120
|
|
167
121
|
def column_long_form(column)
|
168
|
-
long_form = "#{column.to_sql} #{@
|
122
|
+
long_form = "#{column.to_sql} #{@adapter.class::TYPES[column.type] || column.type}"
|
169
123
|
|
170
124
|
long_form << " NOT NULL" unless column.nullable?
|
171
125
|
long_form << " PRIMARY KEY" if column.key?
|
@@ -173,16 +127,101 @@ module DataMapper
|
|
173
127
|
|
174
128
|
return long_form
|
175
129
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
130
|
+
|
131
|
+
def execute_insert(sql)
|
132
|
+
@adapter.connection do |db|
|
133
|
+
db.query(sql)
|
134
|
+
db.last_insert_row_id
|
135
|
+
end
|
181
136
|
end
|
182
|
-
|
137
|
+
|
138
|
+
def execute_update(sql)
|
139
|
+
@adapter.connection do |db|
|
140
|
+
db.query(sql)
|
141
|
+
db.total_changes > 0
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def execute_create_table(sql)
|
146
|
+
@adapter.connection { |db| db.query(sql) }
|
147
|
+
true
|
148
|
+
end
|
149
|
+
end # class SaveCommand
|
183
150
|
|
184
|
-
|
151
|
+
class DeleteCommand
|
152
|
+
def to_truncate_sql
|
153
|
+
"DELETE FROM " << @adapter[@klass_or_instance].to_sql
|
154
|
+
end
|
155
|
+
|
156
|
+
def execute(sql)
|
157
|
+
@adapter.connection do |db|
|
158
|
+
db.query(sql)
|
159
|
+
db.total_changes > 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def execute_drop(sql)
|
164
|
+
@adapter.connection { |db| db.query(sql) }
|
165
|
+
true
|
166
|
+
end
|
167
|
+
end # class DeleteCommand
|
168
|
+
|
169
|
+
class LoadCommand
|
170
|
+
def eof?(reader)
|
171
|
+
reader.eof?
|
172
|
+
end
|
173
|
+
|
174
|
+
def close_reader(reader)
|
175
|
+
reader.close
|
176
|
+
end
|
177
|
+
|
178
|
+
def execute(sql)
|
179
|
+
@adapter.connection { |db| db.query(to_sql) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def fetch_one(reader)
|
183
|
+
load(reader.next)
|
184
|
+
end
|
185
|
+
|
186
|
+
def fetch_all(reader)
|
187
|
+
fields = nil
|
188
|
+
rows = []
|
189
|
+
|
190
|
+
until reader.eof?
|
191
|
+
hash = reader.next
|
192
|
+
break if hash.nil?
|
193
|
+
|
194
|
+
fields = hash.keys.select { |field| field.is_a?(String) } unless fields
|
195
|
+
|
196
|
+
rows << fields.map { |name| hash[name] }
|
197
|
+
end
|
198
|
+
|
199
|
+
load_instances(fields, rows)
|
200
|
+
end
|
201
|
+
|
202
|
+
def fetch_structs(reader)
|
203
|
+
fields = nil
|
204
|
+
rows = []
|
205
|
+
|
206
|
+
until reader.eof?
|
207
|
+
hash = reader.next
|
208
|
+
break if hash.nil?
|
209
|
+
|
210
|
+
fields = hash.keys.select { |field| field.is_a?(String) } unless fields
|
211
|
+
|
212
|
+
rows << fields.map { |name| hash[name] }
|
213
|
+
end
|
214
|
+
|
215
|
+
fields = fields.inject({}) do |h,f|
|
216
|
+
h[f] = fields.index(f); h
|
217
|
+
end
|
218
|
+
|
219
|
+
load_structs(fields, rows)
|
220
|
+
end
|
221
|
+
end
|
185
222
|
|
223
|
+
end # module Commands
|
224
|
+
|
186
225
|
end # class Sqlite3Adapter
|
187
226
|
|
188
227
|
end # module Adapters
|