activerecord_authorails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3043 -0
- data/README +360 -0
- data/RUNNING_UNIT_TESTS +64 -0
- data/Rakefile +226 -0
- data/examples/associations.png +0 -0
- data/examples/associations.rb +87 -0
- data/examples/shared_setup.rb +15 -0
- data/examples/validation.rb +85 -0
- data/install.rb +30 -0
- data/lib/active_record.rb +85 -0
- data/lib/active_record/acts/list.rb +244 -0
- data/lib/active_record/acts/nested_set.rb +211 -0
- data/lib/active_record/acts/tree.rb +89 -0
- data/lib/active_record/aggregations.rb +191 -0
- data/lib/active_record/associations.rb +1637 -0
- data/lib/active_record/associations/association_collection.rb +190 -0
- data/lib/active_record/associations/association_proxy.rb +158 -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 +169 -0
- data/lib/active_record/associations/has_many_association.rb +210 -0
- data/lib/active_record/associations/has_many_through_association.rb +247 -0
- data/lib/active_record/associations/has_one_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +2164 -0
- data/lib/active_record/calculations.rb +270 -0
- data/lib/active_record/callbacks.rb +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
- data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
- data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
- data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
- data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
- data/lib/active_record/deprecated_associations.rb +104 -0
- data/lib/active_record/deprecated_finders.rb +44 -0
- data/lib/active_record/fixtures.rb +628 -0
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +394 -0
- data/lib/active_record/observer.rb +178 -0
- data/lib/active_record/query_cache.rb +64 -0
- data/lib/active_record/reflection.rb +222 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +149 -0
- data/lib/active_record/timestamp.rb +51 -0
- data/lib/active_record/transactions.rb +136 -0
- data/lib/active_record/validations.rb +843 -0
- data/lib/active_record/vendor/db2.rb +362 -0
- data/lib/active_record/vendor/mysql.rb +1214 -0
- data/lib/active_record/vendor/simple.rb +693 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
- data/lib/active_record/wrappings.rb +58 -0
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +59 -0
- data/test/abstract_unit.rb +77 -0
- data/test/active_schema_test_mysql.rb +31 -0
- data/test/adapter_test.rb +87 -0
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +95 -0
- data/test/all.sh +8 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_inheritance_reload.rb +14 -0
- data/test/associations/callbacks_test.rb +126 -0
- data/test/associations/cascaded_eager_loading_test.rb +138 -0
- data/test/associations/eager_test.rb +393 -0
- data/test/associations/extension_test.rb +42 -0
- data/test/associations/join_model_test.rb +497 -0
- data/test/associations_test.rb +1809 -0
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +1586 -0
- data/test/binary_test.rb +37 -0
- data/test/calculations_test.rb +219 -0
- data/test/callbacks_test.rb +377 -0
- data/test/class_inheritable_attributes_test.rb +32 -0
- data/test/column_alias_test.rb +17 -0
- data/test/connection_test_firebird.rb +8 -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 +24 -0
- 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 +23 -0
- data/test/connections/native_sqlite/connection.rb +34 -0
- data/test/connections/native_sqlite3/connection.rb +34 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sqlserver/connection.rb +23 -0
- data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/default_test_firebird.rb +16 -0
- data/test/defaults_test.rb +60 -0
- data/test/deprecated_associations_test.rb +396 -0
- data/test/deprecated_finder_test.rb +151 -0
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +504 -0
- data/test/fixtures/accounts.yml +28 -0
- data/test/fixtures/author.rb +99 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/auto_id.rb +4 -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/binary.rb +2 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -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 +20 -0
- data/test/fixtures/column_name.rb +3 -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 +107 -0
- data/test/fixtures/company_in_module.rb +59 -0
- data/test/fixtures/computer.rb +3 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/course.rb +3 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customer.rb +55 -0
- data/test/fixtures/customers.yml +17 -0
- data/test/fixtures/db_definitions/db2.drop.sql +32 -0
- data/test/fixtures/db_definitions/db2.sql +231 -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 +63 -0
- data/test/fixtures/db_definitions/firebird.sql +304 -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 +32 -0
- data/test/fixtures/db_definitions/frontbase.sql +268 -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/mysql.drop.sql +32 -0
- data/test/fixtures/db_definitions/mysql.sql +234 -0
- data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql2.sql +5 -0
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +302 -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 +65 -0
- data/test/fixtures/db_definitions/oracle.sql +325 -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 +37 -0
- data/test/fixtures/db_definitions/postgresql.sql +263 -0
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +5 -0
- data/test/fixtures/db_definitions/schema.rb +60 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
- data/test/fixtures/db_definitions/sqlite.sql +215 -0
- data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite2.sql +5 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
- data/test/fixtures/db_definitions/sqlserver.sql +243 -0
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
- data/test/fixtures/db_definitions/sybase.sql +218 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/default.rb +2 -0
- data/test/fixtures/developer.rb +52 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/developers_projects/david_action_controller +3 -0
- data/test/fixtures/developers_projects/david_active_record +3 -0
- data/test/fixtures/developers_projects/jamis_active_record +2 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrant.rb +3 -0
- data/test/fixtures/entrants.yml +14 -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/joke.rb +6 -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/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/mixed_case_monkey.rb +3 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixin.rb +63 -0
- data/test/fixtures/mixins.yml +127 -0
- data/test/fixtures/movie.rb +5 -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/people.yml +3 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/post.rb +58 -0
- data/test/fixtures/posts.yml +48 -0
- data/test/fixtures/project.rb +27 -0
- 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 +37 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +6 -0
- data/test/fixtures/subscribers/first +2 -0
- data/test/fixtures/subscribers/second +2 -0
- data/test/fixtures/tag.rb +7 -0
- data/test/fixtures/tagging.rb +6 -0
- data/test/fixtures/taggings.yml +18 -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 +25 -0
- data/test/fixtures/topics.yml +22 -0
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +401 -0
- data/test/inheritance_test.rb +205 -0
- data/test/lifecycle_test.rb +137 -0
- data/test/locking_test.rb +190 -0
- data/test/method_scoping_test.rb +416 -0
- data/test/migration_test.rb +768 -0
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +196 -0
- data/test/mixin_test.rb +550 -0
- data/test/modules_test.rb +34 -0
- data/test/multiple_db_test.rb +60 -0
- data/test/pk_test.rb +104 -0
- data/test/readonly_test.rb +107 -0
- data/test/reflection_test.rb +159 -0
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +96 -0
- data/test/schema_test_postgresql.rb +64 -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 +230 -0
- data/test/unconnected_test.rb +32 -0
- data/test/validations_test.rb +1097 -0
- data/test/xml_serialization_test.rb +125 -0
- metadata +365 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'date'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'bigdecimal/util'
|
5
|
+
|
6
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
7
|
+
require 'active_record/connection_adapters/abstract/schema_statements'
|
8
|
+
require 'active_record/connection_adapters/abstract/database_statements'
|
9
|
+
require 'active_record/connection_adapters/abstract/quoting'
|
10
|
+
require 'active_record/connection_adapters/abstract/connection_specification'
|
11
|
+
|
12
|
+
module ActiveRecord
|
13
|
+
module ConnectionAdapters # :nodoc:
|
14
|
+
# All the concrete database adapters follow the interface laid down in this class.
|
15
|
+
# You can use this interface directly by borrowing the database connection from the Base with
|
16
|
+
# Base.connection.
|
17
|
+
#
|
18
|
+
# Most of the methods in the adapter are useful during migrations. Most
|
19
|
+
# notably, SchemaStatements#create_table, SchemaStatements#drop_table,
|
20
|
+
# SchemaStatements#add_index, SchemaStatements#remove_index,
|
21
|
+
# SchemaStatements#add_column, SchemaStatements#change_column and
|
22
|
+
# SchemaStatements#remove_column are very useful.
|
23
|
+
class AbstractAdapter
|
24
|
+
include Quoting, DatabaseStatements, SchemaStatements
|
25
|
+
@@row_even = true
|
26
|
+
|
27
|
+
def initialize(connection, logger = nil) #:nodoc:
|
28
|
+
@connection, @logger = connection, logger
|
29
|
+
@runtime = 0
|
30
|
+
@last_verification = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
34
|
+
# can always use downcase if needed.
|
35
|
+
def adapter_name
|
36
|
+
'Abstract'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Does this adapter support migrations? Backend specific, as the
|
40
|
+
# abstract adapter always returns +false+.
|
41
|
+
def supports_migrations?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
46
|
+
# for all adapters except sqlite.
|
47
|
+
def supports_count_distinct?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Should primary key values be selected from their corresponding
|
52
|
+
# sequence before the insert statement? If true, next_sequence_value
|
53
|
+
# is called before each insert to set the record's primary key.
|
54
|
+
# This is false for all adapters but Firebird.
|
55
|
+
def prefetch_primary_key?(table_name = nil)
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_runtime #:nodoc:
|
60
|
+
rt, @runtime = @runtime, 0
|
61
|
+
rt
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# CONNECTION MANAGEMENT ====================================
|
66
|
+
|
67
|
+
# Is this connection active and ready to perform queries?
|
68
|
+
def active?
|
69
|
+
@active != false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Close this connection and open a new one in its place.
|
73
|
+
def reconnect!
|
74
|
+
@active = true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Close this connection
|
78
|
+
def disconnect!
|
79
|
+
@active = false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns true if its safe to reload the connection between requests for development mode.
|
83
|
+
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
84
|
+
def requires_reloading?
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
# Lazily verify this connection, calling +active?+ only if it hasn't
|
89
|
+
# been called for +timeout+ seconds.
|
90
|
+
def verify!(timeout)
|
91
|
+
now = Time.now.to_i
|
92
|
+
if (now - @last_verification) > timeout
|
93
|
+
reconnect! unless active?
|
94
|
+
@last_verification = now
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Provides access to the underlying database connection. Useful for
|
99
|
+
# when you need to call a proprietary method such as postgresql's lo_*
|
100
|
+
# methods
|
101
|
+
def raw_connection
|
102
|
+
@connection
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
def log(sql, name)
|
107
|
+
if block_given?
|
108
|
+
if @logger and @logger.level <= Logger::INFO
|
109
|
+
result = nil
|
110
|
+
seconds = Benchmark.realtime { result = yield }
|
111
|
+
@runtime += seconds
|
112
|
+
log_info(sql, name, seconds)
|
113
|
+
result
|
114
|
+
else
|
115
|
+
yield
|
116
|
+
end
|
117
|
+
else
|
118
|
+
log_info(sql, name, 0)
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
rescue Exception => e
|
122
|
+
# Log message and raise exception.
|
123
|
+
# Set last_verfication to 0, so that connection gets verified
|
124
|
+
# upon reentering the request loop
|
125
|
+
@last_verification = 0
|
126
|
+
message = "#{e.class.name}: #{e.message}: #{sql}"
|
127
|
+
log_info(message, name, 0)
|
128
|
+
raise ActiveRecord::StatementInvalid, message
|
129
|
+
end
|
130
|
+
|
131
|
+
def log_info(sql, name, runtime)
|
132
|
+
return unless @logger
|
133
|
+
|
134
|
+
@logger.debug(
|
135
|
+
format_log_entry(
|
136
|
+
"#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
|
137
|
+
sql.gsub(/ +/, " ")
|
138
|
+
)
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def format_log_entry(message, dump = nil)
|
143
|
+
if ActiveRecord::Base.colorize_logging
|
144
|
+
if @@row_even
|
145
|
+
@@row_even = false
|
146
|
+
message_color, dump_color = "4;36;1", "0;1"
|
147
|
+
else
|
148
|
+
@@row_even = true
|
149
|
+
message_color, dump_color = "4;35;1", "0"
|
150
|
+
end
|
151
|
+
|
152
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
153
|
+
log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
|
154
|
+
log_entry
|
155
|
+
else
|
156
|
+
"%s %s" % [message, dump]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# Author/Maintainer: Maik Schmidt <contact@maik-schmidt.de>
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'db2/db2cli' unless self.class.const_defined?(:DB2CLI)
|
7
|
+
require 'active_record/vendor/db2'
|
8
|
+
|
9
|
+
module ActiveRecord
|
10
|
+
class Base
|
11
|
+
# Establishes a connection to the database that's used by
|
12
|
+
# all Active Record objects
|
13
|
+
def self.db2_connection(config) # :nodoc:
|
14
|
+
config = config.symbolize_keys
|
15
|
+
usr = config[:username]
|
16
|
+
pwd = config[:password]
|
17
|
+
schema = config[:schema]
|
18
|
+
|
19
|
+
if config.has_key?(:database)
|
20
|
+
database = config[:database]
|
21
|
+
else
|
22
|
+
raise ArgumentError, 'No database specified. Missing argument: database.'
|
23
|
+
end
|
24
|
+
|
25
|
+
connection = DB2::Connection.new(DB2::Environment.new)
|
26
|
+
connection.connect(database, usr, pwd)
|
27
|
+
ConnectionAdapters::DB2Adapter.new(connection, logger, :schema => schema)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ConnectionAdapters
|
32
|
+
# The DB2 adapter works with the C-based CLI driver (http://rubyforge.org/projects/ruby-dbi/)
|
33
|
+
#
|
34
|
+
# Options:
|
35
|
+
#
|
36
|
+
# * <tt>:username</tt> -- Defaults to nothing
|
37
|
+
# * <tt>:password</tt> -- Defaults to nothing
|
38
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
39
|
+
# * <tt>:schema</tt> -- Database schema to be set initially.
|
40
|
+
class DB2Adapter < AbstractAdapter
|
41
|
+
def initialize(connection, logger, connection_options)
|
42
|
+
super(connection, logger)
|
43
|
+
@connection_options = connection_options
|
44
|
+
if schema = @connection_options[:schema]
|
45
|
+
with_statement do |stmt|
|
46
|
+
stmt.exec_direct("SET SCHEMA=#{schema}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
52
|
+
execute(sql, name = nil)
|
53
|
+
id_value || last_insert_id
|
54
|
+
end
|
55
|
+
|
56
|
+
def execute(sql, name = nil)
|
57
|
+
rows_affected = 0
|
58
|
+
with_statement do |stmt|
|
59
|
+
log(sql, name) do
|
60
|
+
stmt.exec_direct(sql)
|
61
|
+
rows_affected = stmt.row_count
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rows_affected
|
65
|
+
end
|
66
|
+
|
67
|
+
def begin_db_transaction
|
68
|
+
@connection.set_auto_commit_off
|
69
|
+
end
|
70
|
+
|
71
|
+
def commit_db_transaction
|
72
|
+
@connection.commit
|
73
|
+
@connection.set_auto_commit_on
|
74
|
+
end
|
75
|
+
|
76
|
+
def rollback_db_transaction
|
77
|
+
@connection.rollback
|
78
|
+
@connection.set_auto_commit_on
|
79
|
+
end
|
80
|
+
|
81
|
+
def quote_column_name(column_name)
|
82
|
+
column_name
|
83
|
+
end
|
84
|
+
|
85
|
+
def adapter_name()
|
86
|
+
'DB2'
|
87
|
+
end
|
88
|
+
|
89
|
+
def quote_string(string)
|
90
|
+
string.gsub(/'/, "''") # ' (for ruby-mode)
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_limit_offset!(sql, options)
|
94
|
+
if limit = options[:limit]
|
95
|
+
offset = options[:offset] || 0
|
96
|
+
# The following trick was added by andrea+rails@webcom.it.
|
97
|
+
sql.gsub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
|
98
|
+
sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def tables(name = nil)
|
103
|
+
result = []
|
104
|
+
schema = @connection_options[:schema] || '%'
|
105
|
+
with_statement do |stmt|
|
106
|
+
stmt.tables(schema).each { |t| result << t[2].downcase }
|
107
|
+
end
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
def indexes(table_name, name = nil)
|
112
|
+
tmp = {}
|
113
|
+
schema = @connection_options[:schema] || ''
|
114
|
+
with_statement do |stmt|
|
115
|
+
stmt.indexes(table_name, schema).each do |t|
|
116
|
+
next unless t[5]
|
117
|
+
next if t[4] == 'SYSIBM' # Skip system indexes.
|
118
|
+
idx_name = t[5].downcase
|
119
|
+
col_name = t[8].downcase
|
120
|
+
if tmp.has_key?(idx_name)
|
121
|
+
tmp[idx_name].columns << col_name
|
122
|
+
else
|
123
|
+
is_unique = t[3] == 0
|
124
|
+
tmp[idx_name] = IndexDefinition.new(table_name, idx_name, is_unique, [col_name])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
tmp.values
|
129
|
+
end
|
130
|
+
|
131
|
+
def columns(table_name, name = nil)
|
132
|
+
result = []
|
133
|
+
schema = @connection_options[:schema] || '%'
|
134
|
+
with_statement do |stmt|
|
135
|
+
stmt.columns(table_name, schema).each do |c|
|
136
|
+
c_name = c[3].downcase
|
137
|
+
c_default = c[12] == 'NULL' ? nil : c[12]
|
138
|
+
c_default.gsub!(/^'(.*)'$/, '\1') if !c_default.nil?
|
139
|
+
c_type = c[5].downcase
|
140
|
+
c_type += "(#{c[6]})" if !c[6].nil? && c[6] != ''
|
141
|
+
result << Column.new(c_name, c_default, c_type, c[17] == 'YES')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
def native_database_types
|
148
|
+
{
|
149
|
+
:primary_key => 'int generated by default as identity (start with 42) primary key',
|
150
|
+
:string => { :name => 'varchar', :limit => 255 },
|
151
|
+
:text => { :name => 'clob', :limit => 32768 },
|
152
|
+
:integer => { :name => 'int' },
|
153
|
+
:float => { :name => 'float' },
|
154
|
+
:decimal => { :name => 'decimal' },
|
155
|
+
:datetime => { :name => 'timestamp' },
|
156
|
+
:timestamp => { :name => 'timestamp' },
|
157
|
+
:time => { :name => 'time' },
|
158
|
+
:date => { :name => 'date' },
|
159
|
+
:binary => { :name => 'blob', :limit => 32768 },
|
160
|
+
:boolean => { :name => 'decimal', :limit => 1 }
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def quoted_true
|
165
|
+
'1'
|
166
|
+
end
|
167
|
+
|
168
|
+
def quoted_false
|
169
|
+
'0'
|
170
|
+
end
|
171
|
+
|
172
|
+
def active?
|
173
|
+
@connection.select_one 'select 1 from ibm.sysdummy1'
|
174
|
+
true
|
175
|
+
rescue Exception
|
176
|
+
false
|
177
|
+
end
|
178
|
+
|
179
|
+
def reconnect!
|
180
|
+
end
|
181
|
+
|
182
|
+
def table_alias_length
|
183
|
+
128
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def with_statement
|
189
|
+
stmt = DB2::Statement.new(@connection)
|
190
|
+
yield stmt
|
191
|
+
stmt.free
|
192
|
+
end
|
193
|
+
|
194
|
+
def last_insert_id
|
195
|
+
row = select_one(<<-GETID.strip)
|
196
|
+
with temp(id) as (values (identity_val_local())) select * from temp
|
197
|
+
GETID
|
198
|
+
row['id'].to_i
|
199
|
+
end
|
200
|
+
|
201
|
+
def select(sql, name = nil)
|
202
|
+
rows = []
|
203
|
+
with_statement do |stmt|
|
204
|
+
log(sql, name) do
|
205
|
+
stmt.exec_direct("#{sql.gsub(/=\s*null/i, 'IS NULL')} with ur")
|
206
|
+
end
|
207
|
+
|
208
|
+
while row = stmt.fetch_as_hash
|
209
|
+
row.delete('internal$rownum')
|
210
|
+
rows << row
|
211
|
+
end
|
212
|
+
end
|
213
|
+
rows
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
rescue LoadError
|
219
|
+
# DB2 driver is unavailable.
|
220
|
+
module ActiveRecord # :nodoc:
|
221
|
+
class Base
|
222
|
+
def self.db2_connection(config) # :nodoc:
|
223
|
+
# Set up a reasonable error message
|
224
|
+
raise LoadError, "DB2 Libraries could not be loaded."
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,728 @@
|
|
1
|
+
# Author: Ken Kunz <kennethkunz@gmail.com>
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
module FireRuby # :nodoc: all
|
6
|
+
NON_EXISTENT_DOMAIN_ERROR = "335544569"
|
7
|
+
class Database
|
8
|
+
def self.db_string_for(config)
|
9
|
+
unless config.has_key?(:database)
|
10
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
11
|
+
end
|
12
|
+
host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host]
|
13
|
+
[host_string, config[:database]].join(":")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.new_from_config(config)
|
17
|
+
db = new db_string_for(config)
|
18
|
+
db.character_set = config[:charset]
|
19
|
+
return db
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ActiveRecord
|
25
|
+
class << Base
|
26
|
+
def firebird_connection(config) # :nodoc:
|
27
|
+
require_library_or_gem 'fireruby'
|
28
|
+
unless defined? FireRuby::SQLType
|
29
|
+
raise AdapterNotFound,
|
30
|
+
'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
|
31
|
+
'to be running an older version -- please update FireRuby (gem install fireruby).'
|
32
|
+
end
|
33
|
+
config.symbolize_keys!
|
34
|
+
db = FireRuby::Database.new_from_config(config)
|
35
|
+
connection_params = config.values_at(:username, :password)
|
36
|
+
connection = db.connect(*connection_params)
|
37
|
+
ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ConnectionAdapters
|
42
|
+
class FirebirdColumn < Column # :nodoc:
|
43
|
+
VARCHAR_MAX_LENGTH = 32_765
|
44
|
+
BLOB_MAX_LENGTH = 32_767
|
45
|
+
|
46
|
+
def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
|
47
|
+
@firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
|
48
|
+
|
49
|
+
super(name.downcase, nil, @firebird_type, !null_flag)
|
50
|
+
|
51
|
+
@default = parse_default(default_source) if default_source
|
52
|
+
@limit = decide_limit(length)
|
53
|
+
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale.abs
|
54
|
+
end
|
55
|
+
|
56
|
+
def type
|
57
|
+
if @domain =~ /BOOLEAN/
|
58
|
+
:boolean
|
59
|
+
elsif @type == :binary and @sub_type == 1
|
60
|
+
:text
|
61
|
+
else
|
62
|
+
@type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def default
|
67
|
+
type_cast(decide_default) if @default
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.value_to_boolean(value)
|
71
|
+
%W(#{FirebirdAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def parse_default(default_source)
|
76
|
+
default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
|
77
|
+
return $1 unless $1.upcase == "NULL"
|
78
|
+
end
|
79
|
+
|
80
|
+
def decide_default
|
81
|
+
if @default =~ /^'?(\d*\.?\d+)'?$/ or
|
82
|
+
@default =~ /^'(.*)'$/ && [:text, :string, :binary, :boolean].include?(type)
|
83
|
+
$1
|
84
|
+
else
|
85
|
+
firebird_cast_default
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
|
90
|
+
# This enables Firebird to provide an actual value when context variables are used as column
|
91
|
+
# defaults (such as CURRENT_TIMESTAMP).
|
92
|
+
def firebird_cast_default
|
93
|
+
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
94
|
+
if connection = Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
|
95
|
+
connection.execute(sql).to_a.first['CAST']
|
96
|
+
else
|
97
|
+
raise ConnectionNotEstablished, "No Firebird connections established."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def decide_limit(length)
|
102
|
+
if text? or number?
|
103
|
+
length
|
104
|
+
elsif @firebird_type == 'BLOB'
|
105
|
+
BLOB_MAX_LENGTH
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def column_def
|
110
|
+
case @firebird_type
|
111
|
+
when 'BLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
|
112
|
+
when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
|
113
|
+
when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
|
114
|
+
when 'DOUBLE' then "DOUBLE PRECISION"
|
115
|
+
else @firebird_type
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def simplified_type(field_type)
|
120
|
+
if field_type == 'TIMESTAMP'
|
121
|
+
:datetime
|
122
|
+
else
|
123
|
+
super
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
|
129
|
+
# extension, version 0.4.0 or later (available as a gem or from
|
130
|
+
# RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
|
131
|
+
# Firebird 1.5.x on Linux, OS X and Win32 platforms.
|
132
|
+
#
|
133
|
+
# == Usage Notes
|
134
|
+
#
|
135
|
+
# === Sequence (Generator) Names
|
136
|
+
# The Firebird adapter supports the same approach adopted for the Oracle
|
137
|
+
# adapter. See ActiveRecord::Base#set_sequence_name for more details.
|
138
|
+
#
|
139
|
+
# Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
|
140
|
+
# trigger corresponding to a Firebird sequence generator when using
|
141
|
+
# ActiveRecord. In other words, you don't have to try to make Firebird
|
142
|
+
# simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
|
143
|
+
# new record, ActiveRecord pre-fetches the next sequence value for the table
|
144
|
+
# and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
|
145
|
+
# next primary key value is the only reliable method for the Firebird
|
146
|
+
# adapter to report back the +id+ after a successful insert.)
|
147
|
+
#
|
148
|
+
# === BOOLEAN Domain
|
149
|
+
# Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
|
150
|
+
# define a +BOOLEAN+ _domain_ for this purpose, e.g.:
|
151
|
+
#
|
152
|
+
# CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1) OR VALUE IS NULL);
|
153
|
+
#
|
154
|
+
# When the Firebird adapter encounters a column that is based on a domain
|
155
|
+
# that includes "BOOLEAN" in the domain name, it will attempt to treat
|
156
|
+
# the column as a +BOOLEAN+.
|
157
|
+
#
|
158
|
+
# By default, the Firebird adapter will assume that the BOOLEAN domain is
|
159
|
+
# defined as above. This can be modified if needed. For example, if you
|
160
|
+
# have a legacy schema with the following +BOOLEAN+ domain defined:
|
161
|
+
#
|
162
|
+
# CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
|
163
|
+
#
|
164
|
+
# ...you can add the following line to your <tt>environment.rb</tt> file:
|
165
|
+
#
|
166
|
+
# ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }
|
167
|
+
#
|
168
|
+
# === BLOB Elements
|
169
|
+
# The Firebird adapter currently provides only limited support for +BLOB+
|
170
|
+
# columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
|
171
|
+
# When selecting a +BLOB+, the entire element is converted into a String.
|
172
|
+
# When inserting or updating a +BLOB+, the entire value is included in-line
|
173
|
+
# in the SQL statement, limiting you to values <= 32KB in size.
|
174
|
+
#
|
175
|
+
# === Column Name Case Semantics
|
176
|
+
# Firebird and ActiveRecord have somewhat conflicting case semantics for
|
177
|
+
# column names.
|
178
|
+
#
|
179
|
+
# [*Firebird*]
|
180
|
+
# The standard practice is to use unquoted column names, which can be
|
181
|
+
# thought of as case-insensitive. (In fact, Firebird converts them to
|
182
|
+
# uppercase.) Quoted column names (not typically used) are case-sensitive.
|
183
|
+
# [*ActiveRecord*]
|
184
|
+
# Attribute accessors corresponding to column names are case-sensitive.
|
185
|
+
# The defaults for primary key and inheritance columns are lowercase, and
|
186
|
+
# in general, people use lowercase attribute names.
|
187
|
+
#
|
188
|
+
# In order to map between the differing semantics in a way that conforms
|
189
|
+
# to common usage for both Firebird and ActiveRecord, uppercase column names
|
190
|
+
# in Firebird are converted to lowercase attribute names in ActiveRecord,
|
191
|
+
# and vice-versa. Mixed-case column names retain their case in both
|
192
|
+
# directions. Lowercase (quoted) Firebird column names are not supported.
|
193
|
+
# This is similar to the solutions adopted by other adapters.
|
194
|
+
#
|
195
|
+
# In general, the best approach is to use unqouted (case-insensitive) column
|
196
|
+
# names in your Firebird DDL (or if you must quote, use uppercase column
|
197
|
+
# names). These will correspond to lowercase attributes in ActiveRecord.
|
198
|
+
#
|
199
|
+
# For example, a Firebird table based on the following DDL:
|
200
|
+
#
|
201
|
+
# CREATE TABLE products (
|
202
|
+
# id BIGINT NOT NULL PRIMARY KEY,
|
203
|
+
# "TYPE" VARCHAR(50),
|
204
|
+
# name VARCHAR(255) );
|
205
|
+
#
|
206
|
+
# ...will correspond to an ActiveRecord model class called +Product+ with
|
207
|
+
# the following attributes: +id+, +type+, +name+.
|
208
|
+
#
|
209
|
+
# ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
|
210
|
+
# In ActiveRecord, the default inheritance column name is +type+. The word
|
211
|
+
# _type_ is a Firebird reserved word, so it must be quoted in any Firebird
|
212
|
+
# SQL statements. Because of the case mapping described above, you should
|
213
|
+
# always reference this column using quoted-uppercase syntax
|
214
|
+
# (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
|
215
|
+
# example above). This holds true for any other Firebird reserved words used
|
216
|
+
# as column names as well.
|
217
|
+
#
|
218
|
+
# === Migrations
|
219
|
+
# The Firebird Adapter now supports Migrations.
|
220
|
+
#
|
221
|
+
# ==== Create/Drop Table and Sequence Generators
|
222
|
+
# Creating or dropping a table will automatically create/drop a
|
223
|
+
# correpsonding sequence generator, using the default naming convension.
|
224
|
+
# You can specify a different name using the <tt>:sequence</tt> option; no
|
225
|
+
# generator is created if <tt>:sequence</tt> is set to +false+.
|
226
|
+
#
|
227
|
+
# ==== Rename Table
|
228
|
+
# The Firebird #rename_table Migration should be used with caution.
|
229
|
+
# Firebird 1.5 lacks built-in support for this feature, so it is
|
230
|
+
# implemented by making a copy of the original table (including column
|
231
|
+
# definitions, indexes and data records), and then dropping the original
|
232
|
+
# table. Constraints and Triggers are _not_ properly copied, so avoid
|
233
|
+
# this method if your original table includes constraints (other than
|
234
|
+
# the primary key) or triggers. (Consider manually copying your table
|
235
|
+
# or using a view instead.)
|
236
|
+
#
|
237
|
+
# == Connection Options
|
238
|
+
# The following options are supported by the Firebird adapter. None of the
|
239
|
+
# options have default values.
|
240
|
+
#
|
241
|
+
# <tt>:database</tt>::
|
242
|
+
# <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
|
243
|
+
# (ii) the full path of a database file; _or_ (iii) a full Firebird
|
244
|
+
# connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
|
245
|
+
# or <tt>:port</tt> as separate options when using a full connection
|
246
|
+
# string.</i>
|
247
|
+
# <tt>:host</tt>::
|
248
|
+
# Set to <tt>"remote.host.name"</tt> for remote database connections.
|
249
|
+
# May be omitted for local connections if a full database path is
|
250
|
+
# specified for <tt>:database</tt>. Some platforms require a value of
|
251
|
+
# <tt>"localhost"</tt> for local connections when using a Firebird
|
252
|
+
# database _alias_.
|
253
|
+
# <tt>:service</tt>::
|
254
|
+
# Specifies a service name for the connection. Only used if <tt>:host</tt>
|
255
|
+
# is provided. Required when connecting to a non-standard service.
|
256
|
+
# <tt>:port</tt>::
|
257
|
+
# Specifies the connection port. Only used if <tt>:host</tt> is provided
|
258
|
+
# and <tt>:service</tt> is not. Required when connecting to a non-standard
|
259
|
+
# port and <tt>:service</tt> is not defined.
|
260
|
+
# <tt>:username</tt>::
|
261
|
+
# Specifies the database user. May be omitted or set to +nil+ (together
|
262
|
+
# with <tt>:password</tt>) to use the underlying operating system user
|
263
|
+
# credentials on supported platforms.
|
264
|
+
# <tt>:password</tt>::
|
265
|
+
# Specifies the database password. Must be provided if <tt>:username</tt>
|
266
|
+
# is explicitly specified; should be omitted if OS user credentials are
|
267
|
+
# are being used.
|
268
|
+
# <tt>:charset</tt>::
|
269
|
+
# Specifies the character set to be used by the connection. Refer to
|
270
|
+
# Firebird documentation for valid options.
|
271
|
+
class FirebirdAdapter < AbstractAdapter
|
272
|
+
TEMP_COLUMN_NAME = 'AR$TEMP_COLUMN'
|
273
|
+
|
274
|
+
@@boolean_domain = { :name => "d_boolean", :type => "smallint", :true => 1, :false => 0 }
|
275
|
+
cattr_accessor :boolean_domain
|
276
|
+
|
277
|
+
def initialize(connection, logger, connection_params = nil)
|
278
|
+
super(connection, logger)
|
279
|
+
@connection_params = connection_params
|
280
|
+
end
|
281
|
+
|
282
|
+
def adapter_name # :nodoc:
|
283
|
+
'Firebird'
|
284
|
+
end
|
285
|
+
|
286
|
+
def supports_migrations? # :nodoc:
|
287
|
+
true
|
288
|
+
end
|
289
|
+
|
290
|
+
def native_database_types # :nodoc:
|
291
|
+
{
|
292
|
+
:primary_key => "BIGINT NOT NULL PRIMARY KEY",
|
293
|
+
:string => { :name => "varchar", :limit => 255 },
|
294
|
+
:text => { :name => "blob sub_type text" },
|
295
|
+
:integer => { :name => "bigint" },
|
296
|
+
:decimal => { :name => "decimal" },
|
297
|
+
:numeric => { :name => "numeric" },
|
298
|
+
:float => { :name => "float" },
|
299
|
+
:datetime => { :name => "timestamp" },
|
300
|
+
:timestamp => { :name => "timestamp" },
|
301
|
+
:time => { :name => "time" },
|
302
|
+
:date => { :name => "date" },
|
303
|
+
:binary => { :name => "blob sub_type 0" },
|
304
|
+
:boolean => boolean_domain
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns true for Firebird adapter (since Firebird requires primary key
|
309
|
+
# values to be pre-fetched before insert). See also #next_sequence_value.
|
310
|
+
def prefetch_primary_key?(table_name = nil)
|
311
|
+
true
|
312
|
+
end
|
313
|
+
|
314
|
+
def default_sequence_name(table_name, primary_key = nil) # :nodoc:
|
315
|
+
"#{table_name}_seq"
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
# QUOTING ==================================================
|
320
|
+
|
321
|
+
def quote(value, column = nil) # :nodoc:
|
322
|
+
if [Time, DateTime].include?(value.class)
|
323
|
+
"CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
|
324
|
+
else
|
325
|
+
super
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def quote_string(string) # :nodoc:
|
330
|
+
string.gsub(/'/, "''")
|
331
|
+
end
|
332
|
+
|
333
|
+
def quote_column_name(column_name) # :nodoc:
|
334
|
+
%Q("#{ar_to_fb_case(column_name.to_s)}")
|
335
|
+
end
|
336
|
+
|
337
|
+
def quoted_true # :nodoc:
|
338
|
+
quote(boolean_domain[:true])
|
339
|
+
end
|
340
|
+
|
341
|
+
def quoted_false # :nodoc:
|
342
|
+
quote(boolean_domain[:false])
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
# CONNECTION MANAGEMENT ====================================
|
347
|
+
|
348
|
+
def active? # :nodoc:
|
349
|
+
not @connection.closed?
|
350
|
+
end
|
351
|
+
|
352
|
+
def disconnect! # :nodoc:
|
353
|
+
@connection.close rescue nil
|
354
|
+
end
|
355
|
+
|
356
|
+
def reconnect! # :nodoc:
|
357
|
+
disconnect!
|
358
|
+
@connection = @connection.database.connect(*@connection_params)
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
# DATABASE STATEMENTS ======================================
|
363
|
+
|
364
|
+
def select_all(sql, name = nil) # :nodoc:
|
365
|
+
select(sql, name)
|
366
|
+
end
|
367
|
+
|
368
|
+
def select_one(sql, name = nil) # :nodoc:
|
369
|
+
select(sql, name).first
|
370
|
+
end
|
371
|
+
|
372
|
+
def execute(sql, name = nil, &block) # :nodoc:
|
373
|
+
log(sql, name) do
|
374
|
+
if @transaction
|
375
|
+
@connection.execute(sql, @transaction, &block)
|
376
|
+
else
|
377
|
+
@connection.execute_immediate(sql, &block)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
|
383
|
+
execute(sql, name)
|
384
|
+
id_value
|
385
|
+
end
|
386
|
+
|
387
|
+
alias_method :update, :execute
|
388
|
+
alias_method :delete, :execute
|
389
|
+
|
390
|
+
def begin_db_transaction() # :nodoc:
|
391
|
+
@transaction = @connection.start_transaction
|
392
|
+
end
|
393
|
+
|
394
|
+
def commit_db_transaction() # :nodoc:
|
395
|
+
@transaction.commit
|
396
|
+
ensure
|
397
|
+
@transaction = nil
|
398
|
+
end
|
399
|
+
|
400
|
+
def rollback_db_transaction() # :nodoc:
|
401
|
+
@transaction.rollback
|
402
|
+
ensure
|
403
|
+
@transaction = nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def add_limit_offset!(sql, options) # :nodoc:
|
407
|
+
if options[:limit]
|
408
|
+
limit_string = "FIRST #{options[:limit]}"
|
409
|
+
limit_string << " SKIP #{options[:offset]}" if options[:offset]
|
410
|
+
sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
415
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
416
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
417
|
+
def next_sequence_value(sequence_name)
|
418
|
+
FireRuby::Generator.new(sequence_name, @connection).next(1)
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
# SCHEMA STATEMENTS ========================================
|
423
|
+
|
424
|
+
def current_database # :nodoc:
|
425
|
+
file = @connection.database.file.split(':').last
|
426
|
+
File.basename(file, '.*')
|
427
|
+
end
|
428
|
+
|
429
|
+
def recreate_database! # :nodoc:
|
430
|
+
sql = "SELECT rdb$character_set_name FROM rdb$database"
|
431
|
+
charset = execute(sql).to_a.first[0].rstrip
|
432
|
+
disconnect!
|
433
|
+
@connection.database.drop(*@connection_params)
|
434
|
+
FireRuby::Database.create(@connection.database.file,
|
435
|
+
@connection_params[0], @connection_params[1], 4096, charset)
|
436
|
+
end
|
437
|
+
|
438
|
+
def tables(name = nil) # :nodoc:
|
439
|
+
sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0"
|
440
|
+
execute(sql, name).collect { |row| row[0].rstrip.downcase }
|
441
|
+
end
|
442
|
+
|
443
|
+
def indexes(table_name, name = nil) # :nodoc:
|
444
|
+
index_metadata(table_name, false, name).inject([]) do |indexes, row|
|
445
|
+
if indexes.empty? or indexes.last.name != row[0]
|
446
|
+
indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, [])
|
447
|
+
end
|
448
|
+
indexes.last.columns << row[2].rstrip.downcase
|
449
|
+
indexes
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def columns(table_name, name = nil) # :nodoc:
|
454
|
+
sql = <<-end_sql
|
455
|
+
SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
|
456
|
+
f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
457
|
+
COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
|
458
|
+
COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
|
459
|
+
FROM rdb$relation_fields r
|
460
|
+
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
461
|
+
WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
|
462
|
+
ORDER BY r.rdb$field_position
|
463
|
+
end_sql
|
464
|
+
execute(sql, name).collect do |field|
|
465
|
+
field_values = field.values.collect do |value|
|
466
|
+
case value
|
467
|
+
when String then value.rstrip
|
468
|
+
when FireRuby::Blob then value.to_s
|
469
|
+
else value
|
470
|
+
end
|
471
|
+
end
|
472
|
+
FirebirdColumn.new(*field_values)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
def create_table(name, options = {}) # :nodoc:
|
477
|
+
begin
|
478
|
+
super
|
479
|
+
rescue StatementInvalid
|
480
|
+
raise unless non_existent_domain_error?
|
481
|
+
create_boolean_domain
|
482
|
+
super
|
483
|
+
end
|
484
|
+
unless options[:id] == false or options[:sequence] == false
|
485
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
486
|
+
create_sequence(sequence_name)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def drop_table(name, options = {}) # :nodoc:
|
491
|
+
super(name)
|
492
|
+
unless options[:sequence] == false
|
493
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
494
|
+
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def add_column(table_name, column_name, type, options = {}) # :nodoc:
|
499
|
+
super
|
500
|
+
rescue StatementInvalid
|
501
|
+
raise unless non_existent_domain_error?
|
502
|
+
create_boolean_domain
|
503
|
+
super
|
504
|
+
end
|
505
|
+
|
506
|
+
def change_column(table_name, column_name, type, options = {}) # :nodoc:
|
507
|
+
change_column_type(table_name, column_name, type, options)
|
508
|
+
change_column_position(table_name, column_name, options[:position]) if options.include?(:position)
|
509
|
+
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
510
|
+
end
|
511
|
+
|
512
|
+
def change_column_default(table_name, column_name, default) # :nodoc:
|
513
|
+
table_name = table_name.to_s.upcase
|
514
|
+
sql = <<-end_sql
|
515
|
+
UPDATE rdb$relation_fields f1
|
516
|
+
SET f1.rdb$default_source =
|
517
|
+
(SELECT f2.rdb$default_source FROM rdb$relation_fields f2
|
518
|
+
WHERE f2.rdb$relation_name = '#{table_name}'
|
519
|
+
AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'),
|
520
|
+
f1.rdb$default_value =
|
521
|
+
(SELECT f2.rdb$default_value FROM rdb$relation_fields f2
|
522
|
+
WHERE f2.rdb$relation_name = '#{table_name}'
|
523
|
+
AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}')
|
524
|
+
WHERE f1.rdb$relation_name = '#{table_name}'
|
525
|
+
AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}'
|
526
|
+
end_sql
|
527
|
+
transaction do
|
528
|
+
add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
|
529
|
+
execute sql
|
530
|
+
remove_column(table_name, TEMP_COLUMN_NAME)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
535
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}"
|
536
|
+
end
|
537
|
+
|
538
|
+
def remove_index(table_name, options) #:nodoc:
|
539
|
+
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
|
540
|
+
end
|
541
|
+
|
542
|
+
def rename_table(name, new_name) # :nodoc:
|
543
|
+
if table_has_constraints_or_dependencies?(name)
|
544
|
+
raise ActiveRecordError,
|
545
|
+
"Table #{name} includes constraints or dependencies that are not supported by " <<
|
546
|
+
"the Firebird rename_table migration. Try explicitly removing the constraints/" <<
|
547
|
+
"dependencies first, or manually renaming the table."
|
548
|
+
end
|
549
|
+
|
550
|
+
transaction do
|
551
|
+
copy_table(name, new_name)
|
552
|
+
copy_table_indexes(name, new_name)
|
553
|
+
end
|
554
|
+
begin
|
555
|
+
copy_table_data(name, new_name)
|
556
|
+
copy_sequence_value(name, new_name)
|
557
|
+
rescue
|
558
|
+
drop_table(new_name)
|
559
|
+
raise
|
560
|
+
end
|
561
|
+
drop_table(name)
|
562
|
+
end
|
563
|
+
|
564
|
+
def dump_schema_information # :nodoc:
|
565
|
+
super << ";\n"
|
566
|
+
end
|
567
|
+
|
568
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
|
569
|
+
case type
|
570
|
+
when :integer then integer_sql_type(limit)
|
571
|
+
when :float then float_sql_type(limit)
|
572
|
+
when :string then super(type, limit, precision, scale)
|
573
|
+
else super(type, limit, precision, scale)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
private
|
578
|
+
def integer_sql_type(limit)
|
579
|
+
case limit
|
580
|
+
when (1..2) then 'smallint'
|
581
|
+
when (3..4) then 'integer'
|
582
|
+
else 'bigint'
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def float_sql_type(limit)
|
587
|
+
limit.to_i <= 4 ? 'float' : 'double precision'
|
588
|
+
end
|
589
|
+
|
590
|
+
def select(sql, name = nil)
|
591
|
+
execute(sql, name).collect do |row|
|
592
|
+
hashed_row = {}
|
593
|
+
row.each do |column, value|
|
594
|
+
value = value.to_s if FireRuby::Blob === value
|
595
|
+
hashed_row[fb_to_ar_case(column)] = value
|
596
|
+
end
|
597
|
+
hashed_row
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
def primary_key(table_name)
|
602
|
+
if pk_row = index_metadata(table_name, true).to_a.first
|
603
|
+
pk_row[2].rstrip.downcase
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
def index_metadata(table_name, pk, name = nil)
|
608
|
+
sql = <<-end_sql
|
609
|
+
SELECT i.rdb$index_name, i.rdb$unique_flag, s.rdb$field_name
|
610
|
+
FROM rdb$indices i
|
611
|
+
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
612
|
+
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
613
|
+
WHERE i.rdb$relation_name = '#{table_name.to_s.upcase}'
|
614
|
+
end_sql
|
615
|
+
if pk
|
616
|
+
sql << "AND c.rdb$constraint_type = 'PRIMARY KEY'\n"
|
617
|
+
else
|
618
|
+
sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
|
619
|
+
end
|
620
|
+
sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
|
621
|
+
execute sql, name
|
622
|
+
end
|
623
|
+
|
624
|
+
def change_column_type(table_name, column_name, type, options = {})
|
625
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
|
626
|
+
execute sql
|
627
|
+
rescue StatementInvalid
|
628
|
+
raise unless non_existent_domain_error?
|
629
|
+
create_boolean_domain
|
630
|
+
execute sql
|
631
|
+
end
|
632
|
+
|
633
|
+
def change_column_position(table_name, column_name, position)
|
634
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} POSITION #{position}"
|
635
|
+
end
|
636
|
+
|
637
|
+
def copy_table(from, to)
|
638
|
+
table_opts = {}
|
639
|
+
if pk = primary_key(from)
|
640
|
+
table_opts[:primary_key] = pk
|
641
|
+
else
|
642
|
+
table_opts[:id] = false
|
643
|
+
end
|
644
|
+
create_table(to, table_opts) do |table|
|
645
|
+
from_columns = columns(from).reject { |col| col.name == table_opts[:primary_key] }
|
646
|
+
from_columns.each do |column|
|
647
|
+
col_opts = [:limit, :default, :null].inject({}) { |opts, opt| opts.merge(opt => column.send(opt)) }
|
648
|
+
table.column column.name, column.type, col_opts
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
def copy_table_indexes(from, to)
|
654
|
+
indexes(from).each do |index|
|
655
|
+
unless index.name[from.to_s]
|
656
|
+
raise ActiveRecordError,
|
657
|
+
"Cannot rename index #{index.name}, because the index name does not include " <<
|
658
|
+
"the original table name (#{from}). Try explicitly removing the index on the " <<
|
659
|
+
"original table and re-adding it on the new (renamed) table."
|
660
|
+
end
|
661
|
+
options = {}
|
662
|
+
options[:name] = index.name.gsub(from.to_s, to.to_s)
|
663
|
+
options[:unique] = index.unique
|
664
|
+
add_index(to, index.columns, options)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def copy_table_data(from, to)
|
669
|
+
execute "INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}"
|
670
|
+
end
|
671
|
+
|
672
|
+
def copy_sequence_value(from, to)
|
673
|
+
sequence_value = FireRuby::Generator.new(default_sequence_name(from), @connection).last
|
674
|
+
execute "SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}"
|
675
|
+
end
|
676
|
+
|
677
|
+
def sequence_exists?(sequence_name)
|
678
|
+
FireRuby::Generator.exists?(sequence_name, @connection)
|
679
|
+
end
|
680
|
+
|
681
|
+
def create_sequence(sequence_name)
|
682
|
+
FireRuby::Generator.create(sequence_name.to_s, @connection)
|
683
|
+
end
|
684
|
+
|
685
|
+
def drop_sequence(sequence_name)
|
686
|
+
FireRuby::Generator.new(sequence_name.to_s, @connection).drop
|
687
|
+
end
|
688
|
+
|
689
|
+
def create_boolean_domain
|
690
|
+
sql = <<-end_sql
|
691
|
+
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
692
|
+
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
693
|
+
end_sql
|
694
|
+
execute sql rescue nil
|
695
|
+
end
|
696
|
+
|
697
|
+
def table_has_constraints_or_dependencies?(table_name)
|
698
|
+
table_name = table_name.to_s.upcase
|
699
|
+
sql = <<-end_sql
|
700
|
+
SELECT 1 FROM rdb$relation_constraints
|
701
|
+
WHERE rdb$relation_name = '#{table_name}'
|
702
|
+
AND rdb$constraint_type IN ('UNIQUE', 'FOREIGN KEY', 'CHECK')
|
703
|
+
UNION
|
704
|
+
SELECT 1 FROM rdb$dependencies
|
705
|
+
WHERE rdb$depended_on_name = '#{table_name}'
|
706
|
+
AND rdb$depended_on_type = 0
|
707
|
+
end_sql
|
708
|
+
!select(sql).empty?
|
709
|
+
end
|
710
|
+
|
711
|
+
def non_existent_domain_error?
|
712
|
+
$!.message.include? FireRuby::NON_EXISTENT_DOMAIN_ERROR
|
713
|
+
end
|
714
|
+
|
715
|
+
# Maps uppercase Firebird column names to lowercase for ActiveRecord;
|
716
|
+
# mixed-case columns retain their original case.
|
717
|
+
def fb_to_ar_case(column_name)
|
718
|
+
column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
|
719
|
+
end
|
720
|
+
|
721
|
+
# Maps lowercase ActiveRecord column names to uppercase for Fierbird;
|
722
|
+
# mixed-case columns retain their original case.
|
723
|
+
def ar_to_fb_case(column_name)
|
724
|
+
column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
|
725
|
+
end
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|