activerecord 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +4928 -3
- data/README +45 -46
- data/RUNNING_UNIT_TESTS +8 -11
- data/Rakefile +247 -0
- data/install.rb +8 -38
- data/lib/active_record/aggregations.rb +64 -49
- data/lib/active_record/associations/association_collection.rb +217 -47
- data/lib/active_record/associations/association_proxy.rb +159 -0
- data/lib/active_record/associations/belongs_to_association.rb +56 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
- data/lib/active_record/associations/has_many_association.rb +145 -75
- data/lib/active_record/associations/has_many_through_association.rb +283 -0
- data/lib/active_record/associations/has_one_association.rb +96 -0
- data/lib/active_record/associations.rb +1537 -304
- data/lib/active_record/attribute_methods.rb +328 -0
- data/lib/active_record/base.rb +2001 -588
- data/lib/active_record/calculations.rb +269 -0
- data/lib/active_record/callbacks.rb +169 -165
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
- data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
- data/lib/active_record/fixtures.rb +946 -100
- data/lib/active_record/locking/optimistic.rb +144 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +417 -0
- data/lib/active_record/observer.rb +142 -32
- data/lib/active_record/query_cache.rb +23 -0
- data/lib/active_record/reflection.rb +163 -70
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +171 -0
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/serializers/xml_serializer.rb +315 -0
- data/lib/active_record/timestamp.rb +41 -0
- data/lib/active_record/transactions.rb +87 -57
- data/lib/active_record/validations.rb +909 -122
- data/lib/active_record/vendor/db2.rb +362 -0
- data/lib/active_record/vendor/mysql.rb +126 -29
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +35 -7
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +72 -0
- data/test/abstract_unit.rb +73 -5
- data/test/active_schema_test_mysql.rb +43 -0
- data/test/adapter_test.rb +105 -0
- data/test/adapter_test_sqlserver.rb +95 -0
- data/test/aggregations_test.rb +110 -16
- data/test/all.sh +2 -2
- data/test/ar_schema_test.rb +33 -0
- data/test/association_inheritance_reload.rb +14 -0
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +147 -0
- data/test/associations/cascaded_eager_loading_test.rb +110 -0
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +442 -0
- data/test/associations/extension_test.rb +47 -0
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +553 -0
- data/test/associations_test.rb +1930 -267
- data/test/attribute_methods_test.rb +146 -0
- data/test/base_test.rb +1316 -84
- data/test/binary_test.rb +32 -0
- data/test/calculations_test.rb +251 -0
- data/test/callbacks_test.rb +400 -0
- data/test/class_inheritable_attributes_test.rb +3 -4
- data/test/column_alias_test.rb +17 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_db2/connection.rb +25 -0
- data/test/connections/native_firebird/connection.rb +26 -0
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +21 -18
- data/test/connections/native_openbase/connection.rb +21 -0
- data/test/connections/native_oracle/connection.rb +27 -0
- data/test/connections/native_postgresql/connection.rb +17 -18
- data/test/connections/native_sqlite/connection.rb +17 -16
- data/test/connections/native_sqlite3/connection.rb +25 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/copy_table_test_sqlite.rb +69 -0
- data/test/datatype_test_postgresql.rb +203 -0
- data/test/date_time_test.rb +37 -0
- data/test/default_test_firebird.rb +16 -0
- data/test/defaults_test.rb +67 -0
- data/test/deprecated_finder_test.rb +30 -0
- data/test/finder_test.rb +607 -32
- data/test/fixtures/accounts.yml +28 -0
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +107 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
- data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
- data/test/fixtures/bad_fixtures/blank_line +3 -0
- data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
- data/test/fixtures/bad_fixtures/missing_value +1 -0
- data/test/fixtures/binaries.yml +132 -0
- data/test/fixtures/binary.rb +2 -0
- data/test/fixtures/book.rb +4 -0
- data/test/fixtures/books.yml +7 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/categories_posts.yml +23 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +17 -0
- data/test/fixtures/category.rb +26 -0
- data/test/fixtures/citation.rb +6 -0
- data/test/fixtures/comment.rb +23 -0
- data/test/fixtures/comments.yml +59 -0
- data/test/fixtures/companies.yml +55 -0
- data/test/fixtures/company.rb +81 -4
- data/test/fixtures/company_in_module.rb +32 -6
- data/test/fixtures/computer.rb +4 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customer.rb +28 -3
- data/test/fixtures/customers.yml +17 -0
- data/test/fixtures/db_definitions/db2.drop.sql +33 -0
- data/test/fixtures/db_definitions/db2.sql +235 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +5 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
- data/test/fixtures/db_definitions/firebird.sql +310 -0
- data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
- data/test/fixtures/db_definitions/firebird2.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
- data/test/fixtures/db_definitions/frontbase.sql +273 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +318 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
- data/test/fixtures/db_definitions/oracle.sql +330 -0
- data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle2.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
- data/test/fixtures/db_definitions/postgresql.sql +217 -38
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +2 -2
- data/test/fixtures/db_definitions/schema.rb +354 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
- data/test/fixtures/db_definitions/sqlite.sql +139 -5
- data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite2.sql +1 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
- data/test/fixtures/db_definitions/sybase.sql +222 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developer.rb +70 -6
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects/david_action_controller +2 -1
- data/test/fixtures/developers_projects/david_active_record +2 -1
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/funny_jokes.yml +10 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +3 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixed_case_monkey.rb +3 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixins.yml +29 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/people.yml +3 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +59 -0
- data/test/fixtures/posts.yml +48 -0
- data/test/fixtures/project.rb +27 -2
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +18 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +4 -3
- data/test/fixtures/tag.rb +7 -0
- data/test/fixtures/tagging.rb +10 -0
- data/test/fixtures/taggings.yml +25 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/task.rb +3 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures/topic.rb +20 -3
- data/test/fixtures/topics.yml +22 -0
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +574 -8
- data/test/inheritance_test.rb +113 -27
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +56 -29
- data/test/locking_test.rb +273 -0
- data/test/method_scoping_test.rb +416 -0
- data/test/migration_test.rb +933 -0
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_test.rb +95 -0
- data/test/modules_test.rb +23 -10
- data/test/multiple_db_test.rb +17 -3
- data/test/pk_test.rb +59 -15
- data/test/query_cache_test.rb +104 -0
- data/test/readonly_test.rb +107 -0
- data/test/reflection_test.rb +124 -27
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +131 -0
- data/test/schema_test_postgresql.rb +64 -0
- data/test/serialization_test.rb +47 -0
- data/test/synonym_test_oracle.rb +17 -0
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +48 -0
- data/test/transactions_test.rb +227 -29
- data/test/unconnected_test.rb +14 -6
- data/test/validations_test.rb +1293 -32
- data/test/xml_serialization_test.rb +202 -0
- metadata +347 -143
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/deprecated_associations_test.rb +0 -336
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/inflector_test.rb +0 -104
- data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Base
|
5
|
+
class ConnectionSpecification #:nodoc:
|
6
|
+
attr_reader :config, :adapter_method
|
7
|
+
def initialize (config, adapter_method)
|
8
|
+
@config, @adapter_method = config, adapter_method
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Check for activity after at least +verification_timeout+ seconds.
|
13
|
+
# Defaults to 0 (always check.)
|
14
|
+
cattr_accessor :verification_timeout, :instance_writer => false
|
15
|
+
@@verification_timeout = 0
|
16
|
+
|
17
|
+
# The class -> [adapter_method, config] map
|
18
|
+
@@defined_connections = {}
|
19
|
+
|
20
|
+
# The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
|
21
|
+
@@active_connections = {}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Retrieve the connection cache.
|
25
|
+
def thread_safe_active_connections #:nodoc:
|
26
|
+
@@active_connections[Thread.current.object_id] ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def single_threaded_active_connections #:nodoc:
|
30
|
+
@@active_connections
|
31
|
+
end
|
32
|
+
|
33
|
+
# pick up the right active_connection method from @@allow_concurrency
|
34
|
+
if @@allow_concurrency
|
35
|
+
alias_method :active_connections, :thread_safe_active_connections
|
36
|
+
else
|
37
|
+
alias_method :active_connections, :single_threaded_active_connections
|
38
|
+
end
|
39
|
+
|
40
|
+
# set concurrency support flag (not thread safe, like most of the methods in this file)
|
41
|
+
def allow_concurrency=(threaded) #:nodoc:
|
42
|
+
logger.debug "allow_concurrency=#{threaded}" if logger
|
43
|
+
return if @@allow_concurrency == threaded
|
44
|
+
clear_all_cached_connections!
|
45
|
+
@@allow_concurrency = threaded
|
46
|
+
method_prefix = threaded ? "thread_safe" : "single_threaded"
|
47
|
+
sing = (class << self; self; end)
|
48
|
+
[:active_connections, :scoped_methods].each do |method|
|
49
|
+
sing.send(:alias_method, method, "#{method_prefix}_#{method}")
|
50
|
+
end
|
51
|
+
log_connections if logger
|
52
|
+
end
|
53
|
+
|
54
|
+
def active_connection_name #:nodoc:
|
55
|
+
@active_connection_name ||=
|
56
|
+
if active_connections[name] || @@defined_connections[name]
|
57
|
+
name
|
58
|
+
elsif self == ActiveRecord::Base
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
superclass.active_connection_name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear_active_connection_name #:nodoc:
|
66
|
+
@active_connection_name = nil
|
67
|
+
subclasses.each { |klass| klass.clear_active_connection_name }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the connection currently associated with the class. This can
|
71
|
+
# also be used to "borrow" the connection to do database work unrelated
|
72
|
+
# to any of the specific Active Records.
|
73
|
+
def connection
|
74
|
+
if @active_connection_name && (conn = active_connections[@active_connection_name])
|
75
|
+
conn
|
76
|
+
else
|
77
|
+
# retrieve_connection sets the cache key.
|
78
|
+
conn = retrieve_connection
|
79
|
+
active_connections[@active_connection_name] = conn
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Clears the cache which maps classes to connections.
|
84
|
+
def clear_active_connections!
|
85
|
+
clear_cache!(@@active_connections) do |name, conn|
|
86
|
+
conn.disconnect!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Clears the cache which maps classes
|
91
|
+
def clear_reloadable_connections!
|
92
|
+
if @@allow_concurrency
|
93
|
+
# With concurrent connections @@active_connections is
|
94
|
+
# a hash keyed by thread id.
|
95
|
+
@@active_connections.each do |thread_id, conns|
|
96
|
+
conns.each do |name, conn|
|
97
|
+
if conn.requires_reloading?
|
98
|
+
conn.disconnect!
|
99
|
+
@@active_connections[thread_id].delete(name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
@@active_connections.each do |name, conn|
|
105
|
+
if conn.requires_reloading?
|
106
|
+
conn.disconnect!
|
107
|
+
@@active_connections.delete(name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Verify active connections.
|
114
|
+
def verify_active_connections! #:nodoc:
|
115
|
+
if @@allow_concurrency
|
116
|
+
remove_stale_cached_threads!(@@active_connections) do |name, conn|
|
117
|
+
conn.disconnect!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
active_connections.each_value do |connection|
|
122
|
+
connection.verify!(@@verification_timeout)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def clear_cache!(cache, thread_id = nil, &block)
|
128
|
+
if cache
|
129
|
+
if @@allow_concurrency
|
130
|
+
thread_id ||= Thread.current.object_id
|
131
|
+
thread_cache, cache = cache, cache[thread_id]
|
132
|
+
return unless cache
|
133
|
+
end
|
134
|
+
|
135
|
+
cache.each(&block) if block_given?
|
136
|
+
cache.clear
|
137
|
+
end
|
138
|
+
ensure
|
139
|
+
if thread_cache && @@allow_concurrency
|
140
|
+
thread_cache.delete(thread_id)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Remove stale threads from the cache.
|
145
|
+
def remove_stale_cached_threads!(cache, &block)
|
146
|
+
stale = Set.new(cache.keys)
|
147
|
+
|
148
|
+
Thread.list.each do |thread|
|
149
|
+
stale.delete(thread.object_id) if thread.alive?
|
150
|
+
end
|
151
|
+
|
152
|
+
stale.each do |thread_id|
|
153
|
+
clear_cache!(cache, thread_id, &block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def clear_all_cached_connections!
|
158
|
+
if @@allow_concurrency
|
159
|
+
@@active_connections.each_value do |connection_hash_for_thread|
|
160
|
+
connection_hash_for_thread.each_value {|conn| conn.disconnect! }
|
161
|
+
connection_hash_for_thread.clear
|
162
|
+
end
|
163
|
+
else
|
164
|
+
@@active_connections.each_value {|conn| conn.disconnect! }
|
165
|
+
end
|
166
|
+
@@active_connections.clear
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the connection currently associated with the class. This can
|
171
|
+
# also be used to "borrow" the connection to do database work that isn't
|
172
|
+
# easily done without going straight to SQL.
|
173
|
+
def connection
|
174
|
+
self.class.connection
|
175
|
+
end
|
176
|
+
|
177
|
+
# Establishes the connection to the database. Accepts a hash as input where
|
178
|
+
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
179
|
+
# example for regular databases (MySQL, Postgresql, etc):
|
180
|
+
#
|
181
|
+
# ActiveRecord::Base.establish_connection(
|
182
|
+
# :adapter => "mysql",
|
183
|
+
# :host => "localhost",
|
184
|
+
# :username => "myuser",
|
185
|
+
# :password => "mypass",
|
186
|
+
# :database => "somedatabase"
|
187
|
+
# )
|
188
|
+
#
|
189
|
+
# Example for SQLite database:
|
190
|
+
#
|
191
|
+
# ActiveRecord::Base.establish_connection(
|
192
|
+
# :adapter => "sqlite",
|
193
|
+
# :database => "path/to/dbfile"
|
194
|
+
# )
|
195
|
+
#
|
196
|
+
# Also accepts keys as strings (for parsing from yaml for example):
|
197
|
+
# ActiveRecord::Base.establish_connection(
|
198
|
+
# "adapter" => "sqlite",
|
199
|
+
# "database" => "path/to/dbfile"
|
200
|
+
# )
|
201
|
+
#
|
202
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
203
|
+
# may be returned on an error.
|
204
|
+
def self.establish_connection(spec = nil)
|
205
|
+
case spec
|
206
|
+
when nil
|
207
|
+
raise AdapterNotSpecified unless defined? RAILS_ENV
|
208
|
+
establish_connection(RAILS_ENV)
|
209
|
+
when ConnectionSpecification
|
210
|
+
clear_active_connection_name
|
211
|
+
@active_connection_name = name
|
212
|
+
@@defined_connections[name] = spec
|
213
|
+
when Symbol, String
|
214
|
+
if configuration = configurations[spec.to_s]
|
215
|
+
establish_connection(configuration)
|
216
|
+
else
|
217
|
+
raise AdapterNotSpecified, "#{spec} database is not configured"
|
218
|
+
end
|
219
|
+
else
|
220
|
+
spec = spec.symbolize_keys
|
221
|
+
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
222
|
+
|
223
|
+
begin
|
224
|
+
require 'rubygems'
|
225
|
+
gem "activerecord-#{spec[:adapter]}-adapter"
|
226
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
227
|
+
rescue LoadError
|
228
|
+
begin
|
229
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
230
|
+
rescue LoadError
|
231
|
+
raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
236
|
+
if !respond_to?(adapter_method)
|
237
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
|
238
|
+
end
|
239
|
+
|
240
|
+
remove_connection
|
241
|
+
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Locate the connection of the nearest super class. This can be an
|
246
|
+
# active or defined connection: if it is the latter, it will be
|
247
|
+
# opened and set as the active connection for the class it was defined
|
248
|
+
# for (not necessarily the current class).
|
249
|
+
def self.retrieve_connection #:nodoc:
|
250
|
+
# Name is nil if establish_connection hasn't been called for
|
251
|
+
# some class along the inheritance chain up to AR::Base yet.
|
252
|
+
if name = active_connection_name
|
253
|
+
if conn = active_connections[name]
|
254
|
+
# Verify the connection.
|
255
|
+
conn.verify!(@@verification_timeout)
|
256
|
+
elsif spec = @@defined_connections[name]
|
257
|
+
# Activate this connection specification.
|
258
|
+
klass = name.constantize
|
259
|
+
klass.connection = spec
|
260
|
+
conn = active_connections[name]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
conn or raise ConnectionNotEstablished
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns true if a connection that's accessible to this class has already been opened.
|
268
|
+
def self.connected?
|
269
|
+
active_connections[active_connection_name] ? true : false
|
270
|
+
end
|
271
|
+
|
272
|
+
# Remove the connection for this class. This will close the active
|
273
|
+
# connection and the defined connection (if they exist). The result
|
274
|
+
# can be used as an argument for establish_connection, for easily
|
275
|
+
# re-establishing the connection.
|
276
|
+
def self.remove_connection(klass=self)
|
277
|
+
spec = @@defined_connections[klass.name]
|
278
|
+
konn = active_connections[klass.name]
|
279
|
+
@@defined_connections.delete_if { |key, value| value == spec }
|
280
|
+
active_connections.delete_if { |key, value| value == konn }
|
281
|
+
konn.disconnect! if konn
|
282
|
+
spec.config if spec
|
283
|
+
end
|
284
|
+
|
285
|
+
# Set the connection for the class.
|
286
|
+
def self.connection=(spec) #:nodoc:
|
287
|
+
if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
|
288
|
+
active_connections[name] = spec
|
289
|
+
elsif spec.kind_of?(ConnectionSpecification)
|
290
|
+
config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
|
291
|
+
self.connection = self.send(spec.adapter_method, config)
|
292
|
+
elsif spec.nil?
|
293
|
+
raise ConnectionNotEstablished
|
294
|
+
else
|
295
|
+
establish_connection spec
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# connection state logging
|
300
|
+
def self.log_connections #:nodoc:
|
301
|
+
if logger
|
302
|
+
logger.info "Defined connections: #{@@defined_connections.inspect}"
|
303
|
+
logger.info "Active connections: #{active_connections.inspect}"
|
304
|
+
logger.info "Active connection name: #{@active_connection_name}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module DatabaseStatements
|
4
|
+
# Returns an array of record hashes with the column names as keys and
|
5
|
+
# column values as values.
|
6
|
+
def select_all(sql, name = nil)
|
7
|
+
select(sql, name)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a record hash with the column names as keys and column values
|
11
|
+
# as values.
|
12
|
+
def select_one(sql, name = nil)
|
13
|
+
result = select_all(sql, name)
|
14
|
+
result.first if result
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a single value from a record
|
18
|
+
def select_value(sql, name = nil)
|
19
|
+
if result = select_one(sql, name)
|
20
|
+
result.values.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns an array of the values of the first column in a select:
|
25
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
26
|
+
def select_values(sql, name = nil)
|
27
|
+
result = select_rows(sql, name)
|
28
|
+
result.map { |v| v[0] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an array of arrays containing the field values.
|
32
|
+
# Order is the same as that returned by #columns.
|
33
|
+
def select_rows(sql, name = nil)
|
34
|
+
raise NotImplementedError, "select_rows is an abstract method"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Executes the SQL statement in the context of this connection.
|
38
|
+
def execute(sql, name = nil)
|
39
|
+
raise NotImplementedError, "execute is an abstract method"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the last auto-generated ID from the affected table.
|
43
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
44
|
+
insert_sql(sql, name, pk, id_value, sequence_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Executes the update statement and returns the number of rows affected.
|
48
|
+
def update(sql, name = nil)
|
49
|
+
update_sql(sql, name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Executes the delete statement and returns the number of rows affected.
|
53
|
+
def delete(sql, name = nil)
|
54
|
+
delete_sql(sql, name)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Wrap a block in a transaction. Returns result of block.
|
58
|
+
def transaction(start_db_transaction = true)
|
59
|
+
transaction_open = false
|
60
|
+
begin
|
61
|
+
if block_given?
|
62
|
+
if start_db_transaction
|
63
|
+
begin_db_transaction
|
64
|
+
transaction_open = true
|
65
|
+
end
|
66
|
+
yield
|
67
|
+
end
|
68
|
+
rescue Exception => database_transaction_rollback
|
69
|
+
if transaction_open
|
70
|
+
transaction_open = false
|
71
|
+
rollback_db_transaction
|
72
|
+
end
|
73
|
+
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
74
|
+
end
|
75
|
+
ensure
|
76
|
+
if transaction_open
|
77
|
+
begin
|
78
|
+
commit_db_transaction
|
79
|
+
rescue Exception => database_transaction_rollback
|
80
|
+
rollback_db_transaction
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Begins the transaction (and turns off auto-committing).
|
87
|
+
def begin_db_transaction() end
|
88
|
+
|
89
|
+
# Commits the transaction (and turns on auto-committing).
|
90
|
+
def commit_db_transaction() end
|
91
|
+
|
92
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
93
|
+
# done if the transaction block raises an exception or returns false.
|
94
|
+
def rollback_db_transaction() end
|
95
|
+
|
96
|
+
# Alias for #add_limit_offset!.
|
97
|
+
def add_limit!(sql, options)
|
98
|
+
add_limit_offset!(sql, options) if options
|
99
|
+
end
|
100
|
+
|
101
|
+
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
|
102
|
+
# This method *modifies* the +sql+ parameter.
|
103
|
+
# ===== Examples
|
104
|
+
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
105
|
+
# generates
|
106
|
+
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
107
|
+
def add_limit_offset!(sql, options)
|
108
|
+
if limit = options[:limit]
|
109
|
+
sql << " LIMIT #{limit}"
|
110
|
+
if offset = options[:offset]
|
111
|
+
sql << " OFFSET #{offset}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Appends a locking clause to an SQL statement.
|
117
|
+
# This method *modifies* the +sql+ parameter.
|
118
|
+
# # SELECT * FROM suppliers FOR UPDATE
|
119
|
+
# add_lock! 'SELECT * FROM suppliers', :lock => true
|
120
|
+
# add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
|
121
|
+
def add_lock!(sql, options)
|
122
|
+
case lock = options[:lock]
|
123
|
+
when true; sql << ' FOR UPDATE'
|
124
|
+
when String; sql << " #{lock}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def default_sequence_name(table, column)
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Set the sequence to the max value of the table's column.
|
133
|
+
def reset_sequence!(table, column, sequence = nil)
|
134
|
+
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
135
|
+
end
|
136
|
+
|
137
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
138
|
+
# something beyond a simple insert (eg. Oracle).
|
139
|
+
def insert_fixture(fixture, table_name)
|
140
|
+
execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
141
|
+
end
|
142
|
+
|
143
|
+
def empty_insert_statement(table_name)
|
144
|
+
"INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
# Returns an array of record hashes with the column names as keys and
|
149
|
+
# column values as values.
|
150
|
+
def select(sql, name = nil)
|
151
|
+
raise NotImplementedError, "select is an abstract method"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the last auto-generated ID from the affected table.
|
155
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
156
|
+
execute(sql, name)
|
157
|
+
id_value
|
158
|
+
end
|
159
|
+
|
160
|
+
# Executes the update statement and returns the number of rows affected.
|
161
|
+
def update_sql(sql, name = nil)
|
162
|
+
execute(sql, name)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Executes the delete statement and returns the number of rows affected.
|
166
|
+
def delete_sql(sql, name = nil)
|
167
|
+
update_sql(sql, name)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module QueryCache
|
4
|
+
class << self
|
5
|
+
def included(base)
|
6
|
+
base.class_eval do
|
7
|
+
attr_accessor :query_cache_enabled
|
8
|
+
alias_method_chain :columns, :query_cache
|
9
|
+
alias_method_chain :select_all, :query_cache
|
10
|
+
end
|
11
|
+
|
12
|
+
dirties_query_cache base, :insert, :update, :delete
|
13
|
+
end
|
14
|
+
|
15
|
+
def dirties_query_cache(base, *method_names)
|
16
|
+
method_names.each do |method_name|
|
17
|
+
base.class_eval <<-end_code, __FILE__, __LINE__
|
18
|
+
def #{method_name}_with_query_dirty(*args)
|
19
|
+
clear_query_cache if @query_cache_enabled
|
20
|
+
#{method_name}_without_query_dirty(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method_chain :#{method_name}, :query_dirty
|
24
|
+
end_code
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Enable the query cache within the block.
|
30
|
+
def cache
|
31
|
+
old, @query_cache_enabled = @query_cache_enabled, true
|
32
|
+
@query_cache ||= {}
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
clear_query_cache
|
36
|
+
@query_cache_enabled = old
|
37
|
+
end
|
38
|
+
|
39
|
+
# Disable the query cache within the block.
|
40
|
+
def uncached
|
41
|
+
old, @query_cache_enabled = @query_cache_enabled, false
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
@query_cache_enabled = old
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear_query_cache
|
48
|
+
@query_cache.clear if @query_cache
|
49
|
+
end
|
50
|
+
|
51
|
+
def select_all_with_query_cache(*args)
|
52
|
+
if @query_cache_enabled
|
53
|
+
cache_sql(args.first) { select_all_without_query_cache(*args) }
|
54
|
+
else
|
55
|
+
select_all_without_query_cache(*args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def columns_with_query_cache(*args)
|
60
|
+
if @query_cache_enabled
|
61
|
+
@query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
|
62
|
+
else
|
63
|
+
columns_without_query_cache(*args)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def cache_sql(sql)
|
69
|
+
result =
|
70
|
+
if @query_cache.has_key?(sql)
|
71
|
+
log_info(sql, "CACHE", 0.0)
|
72
|
+
@query_cache[sql]
|
73
|
+
else
|
74
|
+
@query_cache[sql] = yield
|
75
|
+
end
|
76
|
+
|
77
|
+
if Array === result
|
78
|
+
result.collect { |row| row.dup }
|
79
|
+
else
|
80
|
+
result.duplicable? ? result.dup : result
|
81
|
+
end
|
82
|
+
rescue TypeError
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module Quoting
|
4
|
+
# Quotes the column value to help prevent
|
5
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
6
|
+
def quote(value, column = nil)
|
7
|
+
# records are quoted as their primary key
|
8
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
9
|
+
|
10
|
+
case value
|
11
|
+
when String, ActiveSupport::Multibyte::Chars
|
12
|
+
value = value.to_s
|
13
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
14
|
+
"#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
15
|
+
elsif column && [:integer, :float].include?(column.type)
|
16
|
+
value = column.type == :integer ? value.to_i : value.to_f
|
17
|
+
value.to_s
|
18
|
+
else
|
19
|
+
"#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode)
|
20
|
+
end
|
21
|
+
when NilClass then "NULL"
|
22
|
+
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
23
|
+
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
24
|
+
when Float, Fixnum, Bignum then value.to_s
|
25
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
26
|
+
when BigDecimal then value.to_s('F')
|
27
|
+
else
|
28
|
+
if value.acts_like?(:date) || value.acts_like?(:time)
|
29
|
+
"'#{quoted_date(value)}'"
|
30
|
+
else
|
31
|
+
"#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
37
|
+
# characters.
|
38
|
+
def quote_string(s)
|
39
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Quotes the column name. Defaults to no quoting.
|
43
|
+
def quote_column_name(column_name)
|
44
|
+
column_name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Quotes the table name. Defaults to column name quoting.
|
48
|
+
def quote_table_name(table_name)
|
49
|
+
quote_column_name(table_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def quoted_true
|
53
|
+
"'t'"
|
54
|
+
end
|
55
|
+
|
56
|
+
def quoted_false
|
57
|
+
"'f'"
|
58
|
+
end
|
59
|
+
|
60
|
+
def quoted_date(value)
|
61
|
+
value.to_s(:db)
|
62
|
+
end
|
63
|
+
|
64
|
+
def quoted_string_prefix
|
65
|
+
''
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|