activerecord 1.15.6 → 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 +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- 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 +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- 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/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -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/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -1,96 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Acts #:nodoc:
|
3
|
-
module Tree #:nodoc:
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
# Specify this act if you want to model a tree structure by providing a parent association and a children
|
9
|
-
# association. This act requires that you have a foreign key column, which by default is called parent_id.
|
10
|
-
#
|
11
|
-
# class Category < ActiveRecord::Base
|
12
|
-
# acts_as_tree :order => "name"
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# Example:
|
16
|
-
# root
|
17
|
-
# \_ child1
|
18
|
-
# \_ subchild1
|
19
|
-
# \_ subchild2
|
20
|
-
#
|
21
|
-
# root = Category.create("name" => "root")
|
22
|
-
# child1 = root.children.create("name" => "child1")
|
23
|
-
# subchild1 = child1.children.create("name" => "subchild1")
|
24
|
-
#
|
25
|
-
# root.parent # => nil
|
26
|
-
# child1.parent # => root
|
27
|
-
# root.children # => [child1]
|
28
|
-
# root.children.first.children.first # => subchild1
|
29
|
-
#
|
30
|
-
# In addition to the parent and children associations, the following instance methods are added to the class
|
31
|
-
# after specifying the act:
|
32
|
-
# * siblings : Returns all the children of the parent, excluding the current node ([ subchild2 ] when called from subchild1)
|
33
|
-
# * self_and_siblings : Returns all the children of the parent, including the current node ([ subchild1, subchild2 ] when called from subchild1)
|
34
|
-
# * ancestors : Returns all the ancestors of the current node ([child1, root] when called from subchild2)
|
35
|
-
# * root : Returns the root of the current node (root when called from subchild2)
|
36
|
-
module ClassMethods
|
37
|
-
# Configuration options are:
|
38
|
-
#
|
39
|
-
# * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: parent_id)
|
40
|
-
# * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
|
41
|
-
# * <tt>counter_cache</tt> - keeps a count in a children_count column if set to true (default: false).
|
42
|
-
def acts_as_tree(options = {})
|
43
|
-
configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
|
44
|
-
configuration.update(options) if options.is_a?(Hash)
|
45
|
-
|
46
|
-
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
|
47
|
-
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
|
48
|
-
|
49
|
-
class_eval <<-EOV
|
50
|
-
include ActiveRecord::Acts::Tree::InstanceMethods
|
51
|
-
|
52
|
-
def self.roots
|
53
|
-
find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.root
|
57
|
-
find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
58
|
-
end
|
59
|
-
EOV
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
module InstanceMethods
|
64
|
-
# Returns list of ancestors, starting from parent until root.
|
65
|
-
#
|
66
|
-
# subchild1.ancestors # => [child1, root]
|
67
|
-
def ancestors
|
68
|
-
node, nodes = self, []
|
69
|
-
nodes << node = node.parent while node.parent
|
70
|
-
nodes
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the root node of the tree.
|
74
|
-
def root
|
75
|
-
node = self
|
76
|
-
node = node.parent while node.parent
|
77
|
-
node
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns all siblings of the current node.
|
81
|
-
#
|
82
|
-
# subchild1.siblings # => [subchild2]
|
83
|
-
def siblings
|
84
|
-
self_and_siblings - [self]
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns all siblings and a reference to the current node.
|
88
|
-
#
|
89
|
-
# subchild1.self_and_siblings # => [subchild1, subchild2]
|
90
|
-
def self_and_siblings
|
91
|
-
parent ? parent.children : self.class.roots
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,228 +0,0 @@
|
|
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
|
@@ -1,728 +0,0 @@
|
|
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
|