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.
- data/CHANGELOG +65 -0
- data/README +193 -1
- data/do_performance.rb +153 -0
- data/environment.rb +45 -0
- data/example.rb +119 -22
- data/lib/data_mapper.rb +36 -16
- data/lib/data_mapper/adapters/abstract_adapter.rb +8 -0
- data/lib/data_mapper/adapters/data_object_adapter.rb +360 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +30 -179
- data/lib/data_mapper/adapters/postgresql_adapter.rb +90 -199
- data/lib/data_mapper/adapters/sql/coersion.rb +32 -3
- data/lib/data_mapper/adapters/sql/commands/conditions.rb +97 -128
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +234 -231
- data/lib/data_mapper/adapters/sql/commands/loader.rb +99 -0
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +30 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +68 -6
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +6 -3
- data/lib/data_mapper/adapters/sql/mappings/table.rb +71 -42
- data/lib/data_mapper/adapters/sql/quoting.rb +8 -2
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +32 -201
- data/lib/data_mapper/associations.rb +21 -7
- data/lib/data_mapper/associations/belongs_to_association.rb +96 -80
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +158 -67
- data/lib/data_mapper/associations/has_many_association.rb +96 -78
- data/lib/data_mapper/associations/has_n_association.rb +64 -0
- data/lib/data_mapper/associations/has_one_association.rb +49 -79
- data/lib/data_mapper/associations/reference.rb +47 -0
- data/lib/data_mapper/base.rb +216 -50
- data/lib/data_mapper/callbacks.rb +71 -24
- data/lib/data_mapper/{session.rb → context.rb} +20 -8
- data/lib/data_mapper/database.rb +176 -45
- data/lib/data_mapper/embedded_value.rb +65 -0
- data/lib/data_mapper/identity_map.rb +12 -4
- data/lib/data_mapper/support/active_record_impersonation.rb +12 -8
- data/lib/data_mapper/support/enumerable.rb +8 -0
- data/lib/data_mapper/support/serialization.rb +13 -0
- data/lib/data_mapper/support/string.rb +1 -12
- data/lib/data_mapper/support/symbol.rb +3 -0
- data/lib/data_mapper/validations/unique_validator.rb +1 -2
- data/lib/data_mapper/validations/validation_helper.rb +18 -1
- data/performance.rb +109 -34
- data/plugins/can_has_sphinx/LICENSE +23 -0
- data/plugins/can_has_sphinx/README +4 -0
- data/plugins/can_has_sphinx/REVISION +1 -0
- data/plugins/can_has_sphinx/Rakefile +22 -0
- data/plugins/can_has_sphinx/init.rb +1 -0
- data/plugins/can_has_sphinx/install.rb +1 -0
- data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +123 -0
- data/plugins/can_has_sphinx/lib/sphinx.rb +460 -0
- data/plugins/can_has_sphinx/scripts/sphinx.sh +47 -0
- data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +41 -0
- data/plugins/dataobjects/REVISION +1 -0
- data/plugins/dataobjects/Rakefile +7 -0
- data/plugins/dataobjects/do.rb +246 -0
- data/plugins/dataobjects/do_mysql.rb +179 -0
- data/plugins/dataobjects/do_postgres.rb +181 -0
- data/plugins/dataobjects/do_sqlite3.rb +153 -0
- data/plugins/dataobjects/spec/do_spec.rb +150 -0
- data/plugins/dataobjects/spec/spec_helper.rb +81 -0
- data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
- data/plugins/dataobjects/swig_mysql/extconf.rb +33 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.c +18800 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.i +8 -0
- data/plugins/dataobjects/swig_mysql/mysql_supp.i +46 -0
- data/plugins/dataobjects/swig_postgres/Makefile +146 -0
- data/plugins/dataobjects/swig_postgres/extconf.rb +29 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.bundle +0 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.c +8185 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.i +73 -0
- data/plugins/dataobjects/swig_sqlite/db +0 -0
- data/plugins/dataobjects/swig_sqlite/extconf.rb +9 -0
- data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +4725 -0
- data/plugins/dataobjects/swig_sqlite/sqlite_c.i +168 -0
- data/rakefile.rb +45 -23
- data/spec/acts_as_tree_spec.rb +39 -0
- data/spec/associations_spec.rb +220 -0
- data/spec/attributes_spec.rb +15 -0
- data/spec/base_spec.rb +44 -0
- data/spec/callbacks_spec.rb +45 -0
- data/spec/can_has_sphinx.rb +6 -0
- data/spec/coersion_spec.rb +34 -0
- data/spec/conditions_spec.rb +49 -0
- data/spec/conversions_to_yaml_spec.rb +17 -0
- data/spec/count_command_spec.rb +11 -0
- data/spec/delete_command_spec.rb +1 -1
- data/spec/embedded_value_spec.rb +23 -0
- data/spec/fixtures/animals_exhibits.yaml +2 -0
- data/spec/fixtures/people.yaml +18 -1
- data/spec/{legacy.rb → legacy_spec.rb} +3 -3
- data/spec/load_command_spec.rb +157 -20
- data/spec/magic_columns_spec.rb +9 -0
- data/spec/mock_adapter.rb +20 -0
- data/spec/models/animal.rb +1 -1
- data/spec/models/animals_exhibit.rb +6 -0
- data/spec/models/exhibit.rb +2 -0
- data/spec/models/person.rb +26 -1
- data/spec/models/project.rb +19 -0
- data/spec/models/sales_person.rb +1 -0
- data/spec/models/section.rb +6 -0
- data/spec/models/zoo.rb +3 -1
- data/spec/query_spec.rb +9 -0
- data/spec/save_command_spec.rb +65 -1
- data/spec/schema_spec.rb +89 -0
- data/spec/single_table_inheritance_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -55
- data/spec/{symbolic_operators.rb → symbolic_operators_spec.rb} +9 -5
- data/spec/{validates_confirmation_of.rb → validates_confirmation_of_spec.rb} +4 -3
- data/spec/{validates_format_of.rb → validates_format_of_spec.rb} +5 -4
- data/spec/{validates_length_of.rb → validates_length_of_spec.rb} +8 -7
- data/spec/{validates_uniqueness_of.rb → validates_uniqueness_of_spec.rb} +7 -10
- data/spec/{validations.rb → validations_spec.rb} +24 -6
- data/tasks/drivers.rb +20 -0
- data/tasks/fixtures.rb +42 -0
- metadata +181 -42
- data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +0 -140
- data/lib/data_mapper/adapters/sql/commands/delete_command.rb +0 -113
- data/lib/data_mapper/adapters/sql/commands/save_command.rb +0 -141
- data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +0 -33
- data/lib/data_mapper/adapters/sql_adapter.rb +0 -163
- data/lib/data_mapper/associations/advanced_has_many_association.rb +0 -55
- data/lib/data_mapper/support/blank_slate.rb +0 -3
- data/lib/data_mapper/support/proc.rb +0 -69
- data/lib/data_mapper/support/struct.rb +0 -26
- data/lib/data_mapper/unit_of_work.rb +0 -38
- data/spec/basic_finder.rb +0 -67
- data/spec/belongs_to.rb +0 -47
- data/spec/has_and_belongs_to_many.rb +0 -25
- data/spec/has_many.rb +0 -34
- data/spec/new_record.rb +0 -24
- data/spec/sub_select.rb +0 -16
- data/spec/support/string_spec.rb +0 -7
data/example.rb
CHANGED
|
@@ -1,33 +1,130 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ENV['LOG_NAME'] = 'example'
|
|
4
|
+
require 'environment'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
unless ENV['LOGGER'] == 'false'
|
|
11
|
-
log_stream 'example.log'
|
|
12
|
-
log_level Logger::DEBUG
|
|
13
|
-
end
|
|
6
|
+
# Define a fixtures helper method to load up our test data.
|
|
7
|
+
def fixtures(name, force = false)
|
|
8
|
+
entry = YAML::load_file(File.dirname(__FILE__) + "/spec/fixtures/#{name}.yaml")
|
|
9
|
+
klass = Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
klass.auto_migrate!
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
database_name << '.db'
|
|
13
|
+
(entry.kind_of?(Array) ? entry : [entry]).each do |hash|
|
|
14
|
+
if hash['type']
|
|
15
|
+
Object::const_get(hash['type'])::create(hash)
|
|
16
|
+
else
|
|
17
|
+
klass::create(hash)
|
|
18
|
+
end
|
|
24
19
|
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Pre-fill the database so non-destructive tests don't need to reload fixtures.
|
|
23
|
+
Dir[File.dirname(__FILE__) + "/spec/fixtures/*.yaml"].each do |path|
|
|
24
|
+
fixtures(File::basename(path).sub(/\.yaml$/, ''), true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
require 'irb'
|
|
28
|
+
|
|
29
|
+
database { IRB::start }
|
|
30
|
+
|
|
31
|
+
if false
|
|
32
|
+
|
|
33
|
+
# Simple example to setup a database:
|
|
34
|
+
DataMapper::Database.setup({
|
|
35
|
+
:adapter => 'mysql',
|
|
36
|
+
:database => 'data_mapper_1',
|
|
37
|
+
:username => 'root'
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
class Animal < DataMapper::Base
|
|
41
|
+
set_table_name 'animals' # Just as an example. Same inflector as Rails,
|
|
42
|
+
# so this really isn't necessary.
|
|
43
|
+
|
|
44
|
+
property :name, :string
|
|
45
|
+
property :notes, :string, :lazy => true
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
has_and_belongs_to_many :exhibits
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Exhibit < DataMapper::Base
|
|
51
|
+
property :name, :string
|
|
52
|
+
belongs_to :zoo
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Zoo < DataMapper::Base
|
|
56
|
+
property :name, :string
|
|
57
|
+
has_many :exhibits
|
|
27
58
|
end
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|
|
60
|
+
class Person < DataMapper::Base
|
|
61
|
+
|
|
62
|
+
property :name, :string
|
|
63
|
+
property :age, :integer
|
|
64
|
+
property :occupation, :string
|
|
65
|
+
property :notes, :text, :lazy => true
|
|
66
|
+
|
|
67
|
+
# Generates Person::Address class:
|
|
68
|
+
embed :address do
|
|
69
|
+
property :street, :string
|
|
70
|
+
property :city, :string
|
|
71
|
+
property :state, :string, :size => 2
|
|
72
|
+
property :postal_code, :string
|
|
73
|
+
end
|
|
31
74
|
end
|
|
32
75
|
|
|
33
|
-
#
|
|
76
|
+
# Compatible with ActiveRecord finder syntax:
|
|
77
|
+
Zoo.find(1)
|
|
78
|
+
Zoo.find(:first, :conditions => ['name = ?', 'Galveston'])
|
|
79
|
+
Zoo.find(:all)
|
|
80
|
+
|
|
81
|
+
# These are options as well:
|
|
82
|
+
Zoo[1]
|
|
83
|
+
Zoo.first(:name => 'Galveston')
|
|
84
|
+
Zoo.all
|
|
85
|
+
|
|
86
|
+
# Or even this as an alias to ::first:
|
|
87
|
+
Zoo[:name => 'Galveston']
|
|
88
|
+
|
|
89
|
+
# EmbeddedValues are just nice sugar to partition
|
|
90
|
+
# denormalized data.
|
|
91
|
+
Person.first.address.city
|
|
92
|
+
|
|
93
|
+
# Remove all data in a table...
|
|
94
|
+
Person.truncate!
|
|
95
|
+
|
|
96
|
+
# Create a new object...
|
|
97
|
+
Person::create(:name => 'Sam', :age => 30, :occupation => 'Software Monkey')
|
|
98
|
+
|
|
99
|
+
# Saving only updates the values that have changed,
|
|
100
|
+
# and is skipped entirely if the object is not dirty.
|
|
101
|
+
dumbo = Animal.first(:name => 'Elephant')
|
|
102
|
+
dumbo.notes = 'He can fly!'
|
|
103
|
+
dumbo.save # returns true
|
|
104
|
+
dumbo.save # The object is no longer dirty, so returns false
|
|
105
|
+
|
|
106
|
+
# DataMapper associations are loaded as sets.
|
|
107
|
+
# Here's the code:
|
|
108
|
+
Zoo.all.each { |zoo| zoo.exhibits.entries }
|
|
109
|
+
# The important bit to understand about the above is that
|
|
110
|
+
# every Zoo that was loaded by Zoo.all has a reference to
|
|
111
|
+
# every other Zoo it was loaded with through Zoo#loaded_set.
|
|
112
|
+
# This is then used to load all other instances in the set
|
|
113
|
+
# when the association of one instance is accessed. So while
|
|
114
|
+
# it looks like we'd run into the dreaded 1+N query problem
|
|
115
|
+
# with the above, we actually avoid it entirely. The above
|
|
116
|
+
# code will only execute two queries. The first to find all
|
|
117
|
+
# zoos, the second to load all exhibits with zoo_id's that
|
|
118
|
+
# are a part of the set of loaded zoos.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Objects within the same session are uniqued, so this is both
|
|
122
|
+
# faster, and fulfills obvious expectations.
|
|
123
|
+
database do
|
|
124
|
+
Zoo.first == Zoo.first
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# DataMapper find_by_sql equivilent
|
|
128
|
+
database.query("SELECT * FROM zoos")
|
|
129
|
+
|
|
130
|
+
end
|
data/lib/data_mapper.rb
CHANGED
|
@@ -1,35 +1,55 @@
|
|
|
1
|
+
# This file begins the loading sequence.
|
|
2
|
+
#
|
|
3
|
+
# Quick Overview:
|
|
4
|
+
# * Requires set, fastthread, support libs, and base
|
|
5
|
+
# * Sets the applications root and environment for compatibility with rails or merb
|
|
6
|
+
# * Checks for the database.yml and loads it if it exists
|
|
7
|
+
# * Sets up the database using the config from the yaml file or from the environment
|
|
8
|
+
# *
|
|
9
|
+
|
|
1
10
|
# This line just let's us require anything in the +lib+ sub-folder
|
|
2
11
|
# without specifying a full path.
|
|
12
|
+
|
|
3
13
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
4
14
|
|
|
15
|
+
DM_PLUGINS_ROOT = (File.dirname(__FILE__) + '/../plugins')
|
|
16
|
+
|
|
5
17
|
# Require the basics...
|
|
18
|
+
require 'yaml'
|
|
6
19
|
require 'set'
|
|
7
20
|
require 'fastthread'
|
|
8
21
|
require 'data_mapper/support/blank'
|
|
9
22
|
require 'data_mapper/support/enumerable'
|
|
10
23
|
require 'data_mapper/support/symbol'
|
|
11
24
|
require 'data_mapper/support/string'
|
|
12
|
-
require 'data_mapper/support/proc'
|
|
13
25
|
require 'data_mapper/support/inflector'
|
|
14
|
-
require 'data_mapper/support/struct'
|
|
15
26
|
require 'data_mapper/database'
|
|
16
27
|
require 'data_mapper/base'
|
|
17
28
|
|
|
18
|
-
# This block of code is for compatibility with Ruby On Rails' database.yml
|
|
29
|
+
# This block of code is for compatibility with Ruby On Rails' or Merb's database.yml
|
|
19
30
|
# file, allowing you to simply require the data_mapper.rb in your
|
|
20
31
|
# Rails application's environment.rb to configure the DataMapper.
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
|
|
33
|
+
application_root, application_environment = *if defined?(MERB_ROOT)
|
|
34
|
+
[MERB_ROOT, MERB_ENV]
|
|
35
|
+
elsif defined?(RAILS_ROOT)
|
|
36
|
+
[RAILS_ROOT, RAILS_ENV]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
DM_APP_ROOT = application_root || Dir::pwd
|
|
40
|
+
|
|
41
|
+
if application_root && File.exists?(application_root + '/config/database.yml')
|
|
42
|
+
|
|
43
|
+
database_configurations = YAML::load_file(application_root + '/config/database.yml')
|
|
44
|
+
current_database_config = database_configurations[application_environment] || database_configurations[application_environment.to_sym]
|
|
45
|
+
|
|
46
|
+
default_database_config = {
|
|
47
|
+
:adapter => current_database_config['adapter'] || current_database_config[:adapter],
|
|
48
|
+
:host => current_database_config['host'] || current_database_config[:host],
|
|
49
|
+
:database => current_database_config['database'] || current_database_config[:database],
|
|
50
|
+
:username => current_database_config['username'] || current_database_config[:username],
|
|
51
|
+
:password => current_database_config['password'] || current_database_config[:password]
|
|
52
|
+
}
|
|
23
53
|
|
|
24
|
-
|
|
25
|
-
current_config = rails_config[RAILS_ENV.to_s]
|
|
26
|
-
|
|
27
|
-
DataMapper::Database.setup do
|
|
28
|
-
adapter current_config['adapter']
|
|
29
|
-
host current_config['host']
|
|
30
|
-
database current_config['database']
|
|
31
|
-
username current_config['username']
|
|
32
|
-
password current_config['password']
|
|
33
|
-
cache WeakHash::Factory
|
|
34
|
-
end
|
|
54
|
+
DataMapper::Database.setup(default_database_config)
|
|
35
55
|
end
|
|
@@ -9,6 +9,14 @@ module DataMapper
|
|
|
9
9
|
@configuration = configuration
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def index_path
|
|
13
|
+
@configuration.index_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
@configuration.name
|
|
18
|
+
end
|
|
19
|
+
|
|
12
20
|
def delete(instance_or_klass, options = nil)
|
|
13
21
|
raise NotImplementedError.new
|
|
14
22
|
end
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
require 'data_mapper/adapters/abstract_adapter'
|
|
2
|
+
require 'data_mapper/adapters/sql/commands/load_command'
|
|
3
|
+
require 'data_mapper/adapters/sql/coersion'
|
|
4
|
+
require 'data_mapper/adapters/sql/quoting'
|
|
5
|
+
require 'data_mapper/adapters/sql/mappings/schema'
|
|
6
|
+
require 'data_mapper/support/connection_pool'
|
|
7
|
+
|
|
8
|
+
module DataMapper
|
|
9
|
+
|
|
10
|
+
# An Adapter is really a Factory for three types of object,
|
|
11
|
+
# so they can be selectively sub-classed where needed.
|
|
12
|
+
#
|
|
13
|
+
# The first type is a Query. The Query is an object describing
|
|
14
|
+
# the database-specific operations we wish to perform, in an
|
|
15
|
+
# abstract manner. For example: While most if not all databases
|
|
16
|
+
# support a mechanism for limiting the size of results returned,
|
|
17
|
+
# some use a "LIMIT" keyword, while others use a "TOP" keyword.
|
|
18
|
+
# We can set a SelectStatement#limit field then, and allow
|
|
19
|
+
# the adapter to override the underlying SQL generated.
|
|
20
|
+
# Refer to DataMapper::Queries.
|
|
21
|
+
#
|
|
22
|
+
# The final type provided is a DataMapper::Transaction.
|
|
23
|
+
# Transactions are duck-typed Connections that span multiple queries.
|
|
24
|
+
#
|
|
25
|
+
# Note: It is assumed that the Adapter implements it's own
|
|
26
|
+
# ConnectionPool if any since some libraries implement their own at
|
|
27
|
+
# a low-level, and it wouldn't make sense to pay a performance
|
|
28
|
+
# cost twice by implementing a secondary pool in the DataMapper itself.
|
|
29
|
+
# If the library being adapted does not provide such functionality,
|
|
30
|
+
# DataMapper::Support::ConnectionPool can be used.
|
|
31
|
+
module Adapters
|
|
32
|
+
|
|
33
|
+
# You must inherit from the DoAdapter, and implement the
|
|
34
|
+
# required methods to adapt a database library for use with the DataMapper.
|
|
35
|
+
#
|
|
36
|
+
# NOTE: By inheriting from DoAdapter, you get a copy of all the
|
|
37
|
+
# standard sub-modules (Quoting, Coersion and Queries) in your own Adapter.
|
|
38
|
+
# You can extend and overwrite these copies without affecting the originals.
|
|
39
|
+
class DataObjectAdapter < AbstractAdapter
|
|
40
|
+
|
|
41
|
+
$LOAD_PATH << (DM_PLUGINS_ROOT + '/dataobjects')
|
|
42
|
+
|
|
43
|
+
FIND_OPTIONS = [
|
|
44
|
+
:select, :offset, :limit, :class, :include, :shallow_include, :reload, :conditions, :order, :intercept_load
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
TABLE_QUOTING_CHARACTER = '`'.freeze
|
|
48
|
+
COLUMN_QUOTING_CHARACTER = '`'.freeze
|
|
49
|
+
|
|
50
|
+
def initialize(configuration)
|
|
51
|
+
super
|
|
52
|
+
|
|
53
|
+
unless @configuration.single_threaded?
|
|
54
|
+
@connection_pool = Support::ConnectionPool.new { create_connection }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def create_connection
|
|
59
|
+
raise NotImplementedError.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Yields an available connection. Flushes the connection-pool and reconnects
|
|
63
|
+
# if the connection returns an error.
|
|
64
|
+
def connection
|
|
65
|
+
begin
|
|
66
|
+
# Yield the appropriate connection
|
|
67
|
+
if @configuration.single_threaded?
|
|
68
|
+
yield(@active_connection || @active_connection = create_connection)
|
|
69
|
+
else
|
|
70
|
+
@connection_pool.hold { |active_connection| yield(active_connection) }
|
|
71
|
+
end
|
|
72
|
+
rescue => execution_error
|
|
73
|
+
# Log error on failure
|
|
74
|
+
@configuration.log.error(execution_error)
|
|
75
|
+
|
|
76
|
+
# Close all open connections, assuming that if one
|
|
77
|
+
# had an error, it's likely due to a lost connection,
|
|
78
|
+
# in which case all connections are likely broken.
|
|
79
|
+
begin
|
|
80
|
+
if @configuration.single_threaded?
|
|
81
|
+
@active_connection.close
|
|
82
|
+
else
|
|
83
|
+
@connection_pool.available_connections.each do |active_connection|
|
|
84
|
+
active_connection.close
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
rescue => close_connection_error
|
|
88
|
+
# An error on closing the connection is almost expected
|
|
89
|
+
# if the socket is broken.
|
|
90
|
+
@configuration.log.warn(close_connection_error)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Reopen fresh connections.
|
|
94
|
+
if @configuration.single_threaded?
|
|
95
|
+
@active_connection = create_connection
|
|
96
|
+
else
|
|
97
|
+
@connection_pool.available_connections.clear
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise execution_error
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def transaction(&block)
|
|
105
|
+
raise NotImplementedError.new
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def execute(*args)
|
|
109
|
+
connection do |db|
|
|
110
|
+
sql = escape_sql(*args)
|
|
111
|
+
log.debug { sql }
|
|
112
|
+
result = nil
|
|
113
|
+
|
|
114
|
+
command = db.create_command(sql)
|
|
115
|
+
|
|
116
|
+
if block_given?
|
|
117
|
+
reader = command.execute_reader
|
|
118
|
+
result = yield(reader)
|
|
119
|
+
reader.close
|
|
120
|
+
else
|
|
121
|
+
result = command.execute_non_query
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
result
|
|
125
|
+
end
|
|
126
|
+
rescue => e
|
|
127
|
+
handle_error(e)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def query(*args)
|
|
131
|
+
execute(*args) do |reader|
|
|
132
|
+
fields = reader.fields.map { |field| Inflector.underscore(field).to_sym }
|
|
133
|
+
|
|
134
|
+
results = []
|
|
135
|
+
|
|
136
|
+
if fields.size > 1
|
|
137
|
+
struct = Struct.new(*fields)
|
|
138
|
+
|
|
139
|
+
reader.each do
|
|
140
|
+
results << struct.new(*reader.current_row)
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
reader.each do
|
|
144
|
+
results << reader.item(0)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
results
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def handle_error(error)
|
|
153
|
+
raise error
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def schema
|
|
157
|
+
@schema || ( @schema = Mappings::Schema.new(self, @configuration.database) )
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def table_exists?(name)
|
|
161
|
+
execute(table(name).to_exists_sql) { |reader| reader.has_rows? }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def truncate(session, name)
|
|
165
|
+
result = execute("TRUNCATE TABLE #{table(name).to_sql}")
|
|
166
|
+
session.identity_map.clear!(name)
|
|
167
|
+
result.to_i > 0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def drop(session, name)
|
|
171
|
+
result = execute("DROP TABLE #{table(name).to_sql}")
|
|
172
|
+
session.identity_map.clear!(name)
|
|
173
|
+
true
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def create_table(name)
|
|
177
|
+
execute(table(name).to_create_table_sql); true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def delete(session, instance)
|
|
181
|
+
table = self.table(instance)
|
|
182
|
+
|
|
183
|
+
if instance.is_a?(Class)
|
|
184
|
+
execute("DELETE FROM #{table.to_sql}")
|
|
185
|
+
session.identity_map.clear!(instance)
|
|
186
|
+
else
|
|
187
|
+
callback(instance, :before_destroy)
|
|
188
|
+
|
|
189
|
+
if execute("DELETE FROM #{table.to_sql} WHERE #{table.key.to_sql} = #{quote_value(instance.key)}").to_i > 0
|
|
190
|
+
instance.instance_variable_set(:@new_record, true)
|
|
191
|
+
instance.session = session
|
|
192
|
+
instance.original_hashes.clear
|
|
193
|
+
session.identity_map.delete(instance)
|
|
194
|
+
callback(instance, :after_destroy)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def save(session, instance)
|
|
200
|
+
case instance
|
|
201
|
+
when Class, Mappings::Table then create_table(instance)
|
|
202
|
+
when DataMapper::Base then
|
|
203
|
+
return false unless instance.dirty? && instance.valid?
|
|
204
|
+
|
|
205
|
+
callback(instance, :before_save)
|
|
206
|
+
|
|
207
|
+
table = self.table(instance)
|
|
208
|
+
attributes = instance.dirty_attributes
|
|
209
|
+
|
|
210
|
+
unless attributes.empty?
|
|
211
|
+
attributes[:type] = instance.class.name if table.multi_class?
|
|
212
|
+
|
|
213
|
+
# INSERT
|
|
214
|
+
result = if instance.new_record?
|
|
215
|
+
callback(instance, :before_create)
|
|
216
|
+
|
|
217
|
+
keys = []
|
|
218
|
+
values = []
|
|
219
|
+
attributes.each_pair do |key, value|
|
|
220
|
+
keys << table[key].to_sql
|
|
221
|
+
values << value
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Formatting is a bit off here, but it looks nicer in the log this way.
|
|
225
|
+
insert_id = execute("INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES (#{values.map { |v| quote_value(v) }.join(', ')})").last_insert_row
|
|
226
|
+
instance.instance_variable_set(:@new_record, false)
|
|
227
|
+
instance.key = insert_id if table.key.serial? && !attributes.include?(table.key.name)
|
|
228
|
+
session.identity_map.set(instance)
|
|
229
|
+
callback(instance, :after_create)
|
|
230
|
+
# UPDATE
|
|
231
|
+
else
|
|
232
|
+
callback(instance, :before_update)
|
|
233
|
+
|
|
234
|
+
sql = "UPDATE " << table.to_sql << " SET "
|
|
235
|
+
|
|
236
|
+
sql << attributes.map do |key, value|
|
|
237
|
+
"#{table[key].to_sql} = #{quote_value(value)}"
|
|
238
|
+
end.join(', ')
|
|
239
|
+
|
|
240
|
+
sql << " WHERE #{table.key.to_sql} = " << quote_value(instance.key)
|
|
241
|
+
|
|
242
|
+
execute(sql).to_i > 0 && callback(instance, :after_update)
|
|
243
|
+
end
|
|
244
|
+
end # unless attributes.empty?
|
|
245
|
+
|
|
246
|
+
instance.attributes.each_pair do |name, value|
|
|
247
|
+
instance.original_hashes[name] = value.hash
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
instance.loaded_associations.each do |association|
|
|
251
|
+
association.save if association.respond_to?(:save)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
instance.session = session
|
|
255
|
+
callback(instance, :after_save)
|
|
256
|
+
result
|
|
257
|
+
end
|
|
258
|
+
rescue => error
|
|
259
|
+
log.error(error)
|
|
260
|
+
raise error
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def load(session, klass, options)
|
|
264
|
+
self.class::Commands::LoadCommand.new(self, session, klass, options).call
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def count(klass_or_instance, options)
|
|
268
|
+
query("SELECT COUNT(*) AS row_count FROM " + table(klass_or_instance).to_sql).first.to_i
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def table(instance)
|
|
272
|
+
case instance
|
|
273
|
+
when DataMapper::Adapters::Sql::Mappings::Table then instance
|
|
274
|
+
when DataMapper::Base then schema[instance.class]
|
|
275
|
+
when Class, String then schema[instance]
|
|
276
|
+
else raise "Don't know how to map #{instance.inspect} to a table."
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def callback(instance, callback_name)
|
|
281
|
+
instance.class.callbacks.execute(callback_name, instance)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Escape a string of SQL with a set of arguments.
|
|
285
|
+
# The first argument is assumed to be the SQL to escape,
|
|
286
|
+
# the remaining arguments (if any) are assumed to be
|
|
287
|
+
# values to escape and interpolate.
|
|
288
|
+
#
|
|
289
|
+
# ==== Examples
|
|
290
|
+
# escape_sql("SELECT * FROM zoos")
|
|
291
|
+
# # => "SELECT * FROM zoos"
|
|
292
|
+
#
|
|
293
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
|
|
294
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas`"
|
|
295
|
+
#
|
|
296
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
|
|
297
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
|
|
298
|
+
#
|
|
299
|
+
# ==== Warning
|
|
300
|
+
# This method is meant mostly for adapters that don't support
|
|
301
|
+
# bind-parameters.
|
|
302
|
+
def escape_sql(*args)
|
|
303
|
+
sql = args.shift
|
|
304
|
+
|
|
305
|
+
unless args.empty?
|
|
306
|
+
sql.gsub!(/\?/) do |x|
|
|
307
|
+
quote_value(args.shift)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
sql
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# This callback copies and sub-classes modules and classes
|
|
315
|
+
# in the DoAdapter to the inherited class so you don't
|
|
316
|
+
# have to copy and paste large blocks of code from the
|
|
317
|
+
# DoAdapter.
|
|
318
|
+
#
|
|
319
|
+
# Basically, when inheriting from the DoAdapter, you
|
|
320
|
+
# aren't just inheriting a single class, you're inheriting
|
|
321
|
+
# a whole graph of Types. For convenience.
|
|
322
|
+
def self.inherited(base)
|
|
323
|
+
|
|
324
|
+
commands = base.const_set('Commands', Module.new)
|
|
325
|
+
|
|
326
|
+
Sql::Commands.constants.each do |name|
|
|
327
|
+
commands.const_set(name, Class.new(Sql::Commands.const_get(name)))
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
mappings = base.const_set('Mappings', Module.new)
|
|
331
|
+
|
|
332
|
+
Sql::Mappings.constants.each do |name|
|
|
333
|
+
mappings.const_set(name, Class.new(Sql::Mappings.const_get(name)))
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
base.const_set('TYPES', TYPES.dup)
|
|
337
|
+
base.const_set('FIND_OPTIONS', FIND_OPTIONS.dup)
|
|
338
|
+
|
|
339
|
+
super
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
TYPES = {
|
|
343
|
+
:integer => 'int'.freeze,
|
|
344
|
+
:string => 'varchar'.freeze,
|
|
345
|
+
:text => 'text'.freeze,
|
|
346
|
+
:class => 'varchar'.freeze,
|
|
347
|
+
:decimal => 'decimal'.freeze,
|
|
348
|
+
:float => 'float'.freeze,
|
|
349
|
+
:datetime => 'datetime'.freeze,
|
|
350
|
+
:date => 'date'.freeze
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
include Sql
|
|
354
|
+
include Quoting
|
|
355
|
+
include Coersion
|
|
356
|
+
|
|
357
|
+
end # class DoAdapter
|
|
358
|
+
|
|
359
|
+
end # module Adapters
|
|
360
|
+
end # module DataMapper
|