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,861 +0,0 @@
|
|
1
|
-
# Requires FrontBase Ruby bindings (gem install ruby-frontbase)
|
2
|
-
|
3
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
-
|
5
|
-
FB_TRACE = false
|
6
|
-
|
7
|
-
module ActiveRecord
|
8
|
-
|
9
|
-
class Base
|
10
|
-
class << self
|
11
|
-
# Establishes a connection to the database that's used by all Active Record objects.
|
12
|
-
def frontbase_connection(config) # :nodoc:
|
13
|
-
# FrontBase only supports one unnamed sequence per table
|
14
|
-
define_attr_method(:set_sequence_name, :sequence_name, &Proc.new {|*args| nil})
|
15
|
-
|
16
|
-
config = config.symbolize_keys
|
17
|
-
database = config[:database]
|
18
|
-
port = config[:port]
|
19
|
-
host = config[:host]
|
20
|
-
username = config[:username]
|
21
|
-
password = config[:password]
|
22
|
-
dbpassword = config[:dbpassword]
|
23
|
-
session_name = config[:session_name]
|
24
|
-
|
25
|
-
dbpassword = '' if dbpassword.nil?
|
26
|
-
|
27
|
-
# Turn off colorization since it makes tail/less output difficult
|
28
|
-
self.colorize_logging = false
|
29
|
-
|
30
|
-
require_library_or_gem 'frontbase' unless self.class.const_defined? :FBSQL_Connect
|
31
|
-
|
32
|
-
# Check bindings version
|
33
|
-
version = "0.0.0"
|
34
|
-
version = FBSQL_Connect::FB_BINDINGS_VERSION if defined? FBSQL_Connect::FB_BINDINGS_VERSION
|
35
|
-
|
36
|
-
if ActiveRecord::ConnectionAdapters::FrontBaseAdapter.compare_versions(version,"1.0.0") == -1
|
37
|
-
raise AdapterNotFound,
|
38
|
-
'The FrontBase adapter requires ruby-frontbase version 1.0.0 or greater; you appear ' <<
|
39
|
-
"to be running an older version (#{version}) -- please update ruby-frontbase (gem install ruby-frontbase)."
|
40
|
-
end
|
41
|
-
connection = FBSQL_Connect.connect(host, port, database, username, password, dbpassword, session_name)
|
42
|
-
ConnectionAdapters::FrontBaseAdapter.new(connection, logger, [host, port, database, username, password, dbpassword, session_name], config)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
module ConnectionAdapters
|
48
|
-
|
49
|
-
# From EOF Documentation....
|
50
|
-
# buffer should have space for EOUniqueBinaryKeyLength (12) bytes.
|
51
|
-
# Assigns a world-wide unique ID made up of:
|
52
|
-
# < Sequence [2], ProcessID [2] , Time [4], IP Addr [4] >
|
53
|
-
|
54
|
-
class TwelveByteKey < String #:nodoc:
|
55
|
-
@@mutex = Mutex.new
|
56
|
-
@@sequence_number = rand(65536)
|
57
|
-
@@key_cached_pid_component = nil
|
58
|
-
@@key_cached_ip_component = nil
|
59
|
-
|
60
|
-
def initialize(string = nil)
|
61
|
-
# Generate a unique key
|
62
|
-
if string.nil?
|
63
|
-
new_key = replace('_' * 12)
|
64
|
-
|
65
|
-
new_key[0..1] = self.class.key_sequence_component
|
66
|
-
new_key[2..3] = self.class.key_pid_component
|
67
|
-
new_key[4..7] = self.class.key_time_component
|
68
|
-
new_key[8..11] = self.class.key_ip_component
|
69
|
-
new_key
|
70
|
-
else
|
71
|
-
if string.size == 24
|
72
|
-
string.gsub!(/[[:xdigit:]]{2}/) { |x| x.hex.chr }
|
73
|
-
end
|
74
|
-
raise "string is not 12 bytes long" unless string.size == 12
|
75
|
-
super(string)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def inspect
|
80
|
-
unpack("H*").first.upcase
|
81
|
-
end
|
82
|
-
|
83
|
-
alias_method :to_s, :inspect
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
class << self
|
88
|
-
def key_sequence_component
|
89
|
-
seq = nil
|
90
|
-
@@mutex.synchronize do
|
91
|
-
seq = @@sequence_number
|
92
|
-
@@sequence_number = (@@sequence_number + 1) % 65536
|
93
|
-
end
|
94
|
-
|
95
|
-
sequence_component = "__"
|
96
|
-
sequence_component[0] = seq >> 8
|
97
|
-
sequence_component[1] = seq
|
98
|
-
sequence_component
|
99
|
-
end
|
100
|
-
|
101
|
-
def key_pid_component
|
102
|
-
if @@key_cached_pid_component.nil?
|
103
|
-
@@mutex.synchronize do
|
104
|
-
pid = $$
|
105
|
-
pid_component = "__"
|
106
|
-
pid_component[0] = pid >> 8
|
107
|
-
pid_component[1] = pid
|
108
|
-
@@key_cached_pid_component = pid_component
|
109
|
-
end
|
110
|
-
end
|
111
|
-
@@key_cached_pid_component
|
112
|
-
end
|
113
|
-
|
114
|
-
def key_time_component
|
115
|
-
time = Time.new.to_i
|
116
|
-
time_component = "____"
|
117
|
-
time_component[0] = (time & 0xFF000000) >> 24
|
118
|
-
time_component[1] = (time & 0x00FF0000) >> 16
|
119
|
-
time_component[2] = (time & 0x0000FF00) >> 8
|
120
|
-
time_component[3] = (time & 0x000000FF)
|
121
|
-
time_component
|
122
|
-
end
|
123
|
-
|
124
|
-
def key_ip_component
|
125
|
-
if @@key_cached_ip_component.nil?
|
126
|
-
@@mutex.synchronize do
|
127
|
-
old_lookup_flag = BasicSocket.do_not_reverse_lookup
|
128
|
-
BasicSocket.do_not_reverse_lookup = true
|
129
|
-
udpsocket = UDPSocket.new
|
130
|
-
udpsocket.connect("17.112.152.32",1)
|
131
|
-
ip_string = udpsocket.addr[3]
|
132
|
-
BasicSocket.do_not_reverse_lookup = old_lookup_flag
|
133
|
-
packed = Socket.pack_sockaddr_in(0,ip_string)
|
134
|
-
addr_subset = packed[4..7]
|
135
|
-
ip = addr_subset[0] << 24 | addr_subset[1] << 16 | addr_subset[2] << 8 | addr_subset[3]
|
136
|
-
ip_component = "____"
|
137
|
-
ip_component[0] = (ip & 0xFF000000) >> 24
|
138
|
-
ip_component[1] = (ip & 0x00FF0000) >> 16
|
139
|
-
ip_component[2] = (ip & 0x0000FF00) >> 8
|
140
|
-
ip_component[3] = (ip & 0x000000FF)
|
141
|
-
@@key_cached_ip_component = ip_component
|
142
|
-
end
|
143
|
-
end
|
144
|
-
@@key_cached_ip_component
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
class FrontBaseColumn < Column #:nodoc:
|
150
|
-
attr_reader :fb_autogen
|
151
|
-
|
152
|
-
def initialize(base, name, type, typename, limit, precision, scale, default, nullable)
|
153
|
-
|
154
|
-
@base = base
|
155
|
-
@name = name
|
156
|
-
@type = simplified_type(type,typename,limit)
|
157
|
-
@limit = limit
|
158
|
-
@precision = precision
|
159
|
-
@scale = scale
|
160
|
-
@default = default
|
161
|
-
@null = nullable == "YES"
|
162
|
-
@text = [:string, :text].include? @type
|
163
|
-
@number = [:float, :integer, :decimal].include? @type
|
164
|
-
@fb_autogen = false
|
165
|
-
|
166
|
-
if @default
|
167
|
-
@default.gsub!(/^'(.*)'$/,'\1') if @text
|
168
|
-
@fb_autogen = @default.include?("SELECT UNIQUE FROM")
|
169
|
-
case @type
|
170
|
-
when :boolean
|
171
|
-
@default = @default == "TRUE"
|
172
|
-
when :binary
|
173
|
-
if @default != "X''"
|
174
|
-
buffer = ""
|
175
|
-
@default.scan(/../) { |h| buffer << h.hex.chr }
|
176
|
-
@default = buffer
|
177
|
-
else
|
178
|
-
@default = ""
|
179
|
-
end
|
180
|
-
else
|
181
|
-
@default = type_cast(@default)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Casts value (which is a String) to an appropriate instance.
|
187
|
-
def type_cast(value)
|
188
|
-
if type == :twelvebytekey
|
189
|
-
ActiveRecord::ConnectionAdapters::TwelveByteKey.new(value)
|
190
|
-
else
|
191
|
-
super(value)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def type_cast_code(var_name)
|
196
|
-
if type == :twelvebytekey
|
197
|
-
"ActiveRecord::ConnectionAdapters::TwelveByteKey.new(#{var_name})"
|
198
|
-
else
|
199
|
-
super(var_name)
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
private
|
204
|
-
def simplified_type(field_type, type_name,limit)
|
205
|
-
ret_type = :string
|
206
|
-
puts "typecode: [#{field_type}] [#{type_name}]" if FB_TRACE
|
207
|
-
|
208
|
-
# 12 byte primary keys are a special case that Apple's EOF
|
209
|
-
# used heavily. Optimize for this case
|
210
|
-
if field_type == 11 && limit == 96
|
211
|
-
ret_type = :twelvebytekey # BIT(96)
|
212
|
-
else
|
213
|
-
ret_type = case field_type
|
214
|
-
when 1 then :boolean # BOOLEAN
|
215
|
-
when 2 then :integer # INTEGER
|
216
|
-
when 4 then :float # FLOAT
|
217
|
-
when 10 then :string # CHARACTER VARYING
|
218
|
-
when 11 then :bitfield # BIT
|
219
|
-
when 13 then :date # DATE
|
220
|
-
when 14 then :time # TIME
|
221
|
-
when 16 then :timestamp # TIMESTAMP
|
222
|
-
when 20 then :text # CLOB
|
223
|
-
when 21 then :binary # BLOB
|
224
|
-
when 22 then :integer # TINYINT
|
225
|
-
else
|
226
|
-
puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]"
|
227
|
-
end
|
228
|
-
end
|
229
|
-
puts "ret_type: #{ret_type.inspect}" if FB_TRACE
|
230
|
-
ret_type
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
class FrontBaseAdapter < AbstractAdapter
|
235
|
-
|
236
|
-
class << self
|
237
|
-
def compare_versions(v1, v2)
|
238
|
-
v1_seg = v1.split(".")
|
239
|
-
v2_seg = v2.split(".")
|
240
|
-
0.upto([v1_seg.length,v2_seg.length].min) do |i|
|
241
|
-
step = (v1_seg[i].to_i <=> v2_seg[i].to_i)
|
242
|
-
return step unless step == 0
|
243
|
-
end
|
244
|
-
return v1_seg.length <=> v2_seg.length
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def initialize(connection, logger, connection_options, config)
|
249
|
-
super(connection, logger)
|
250
|
-
@connection_options, @config = connection_options, config
|
251
|
-
@transaction_mode = :pessimistic
|
252
|
-
|
253
|
-
# Start out in auto-commit mode
|
254
|
-
self.rollback_db_transaction
|
255
|
-
|
256
|
-
# threaded_connections_test.rb will fail unless we set the session
|
257
|
-
# to optimistic locking mode
|
258
|
-
# set_pessimistic_transactions
|
259
|
-
# execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
|
260
|
-
end
|
261
|
-
|
262
|
-
# Returns the human-readable name of the adapter. Use mixed case - one
|
263
|
-
# can always use downcase if needed.
|
264
|
-
def adapter_name #:nodoc:
|
265
|
-
'FrontBase'
|
266
|
-
end
|
267
|
-
|
268
|
-
# Does this adapter support migrations? Backend specific, as the
|
269
|
-
# abstract adapter always returns +false+.
|
270
|
-
def supports_migrations? #:nodoc:
|
271
|
-
true
|
272
|
-
end
|
273
|
-
|
274
|
-
def native_database_types #:nodoc:
|
275
|
-
{
|
276
|
-
:primary_key => "INTEGER DEFAULT UNIQUE PRIMARY KEY",
|
277
|
-
:string => { :name => "VARCHAR", :limit => 255 },
|
278
|
-
:text => { :name => "CLOB" },
|
279
|
-
:integer => { :name => "INTEGER" },
|
280
|
-
:float => { :name => "FLOAT" },
|
281
|
-
:decimal => { :name => "DECIMAL" },
|
282
|
-
:datetime => { :name => "TIMESTAMP" },
|
283
|
-
:timestamp => { :name => "TIMESTAMP" },
|
284
|
-
:time => { :name => "TIME" },
|
285
|
-
:date => { :name => "DATE" },
|
286
|
-
:binary => { :name => "BLOB" },
|
287
|
-
:boolean => { :name => "BOOLEAN" },
|
288
|
-
:twelvebytekey => { :name => "BYTE", :limit => 12}
|
289
|
-
}
|
290
|
-
end
|
291
|
-
|
292
|
-
|
293
|
-
# QUOTING ==================================================
|
294
|
-
|
295
|
-
# Quotes the column value to help prevent
|
296
|
-
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
297
|
-
def quote(value, column = nil)
|
298
|
-
return value.quoted_id if value.respond_to?(:quoted_id)
|
299
|
-
|
300
|
-
retvalue = "<INVALID>"
|
301
|
-
|
302
|
-
puts "quote(#{value.inspect}(#{value.class}),#{column.type.inspect})" if FB_TRACE
|
303
|
-
# If a column was passed in, use column type information
|
304
|
-
unless value.nil?
|
305
|
-
if column
|
306
|
-
retvalue = case column.type
|
307
|
-
when :string
|
308
|
-
if value.kind_of?(String)
|
309
|
-
"'#{quote_string(value.to_s)}'" # ' (for ruby-mode)
|
310
|
-
else
|
311
|
-
"'#{quote_string(value.to_yaml)}'"
|
312
|
-
end
|
313
|
-
when :integer
|
314
|
-
if value.kind_of?(TrueClass)
|
315
|
-
'1'
|
316
|
-
elsif value.kind_of?(FalseClass)
|
317
|
-
'0'
|
318
|
-
else
|
319
|
-
value.to_i.to_s
|
320
|
-
end
|
321
|
-
when :float
|
322
|
-
value.to_f.to_s
|
323
|
-
when :decimal
|
324
|
-
value.to_d.to_s("F")
|
325
|
-
when :datetime, :timestamp
|
326
|
-
"TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
327
|
-
when :time
|
328
|
-
"TIME '#{value.strftime("%H:%M:%S")}'"
|
329
|
-
when :date
|
330
|
-
"DATE '#{value.strftime("%Y-%m-%d")}'"
|
331
|
-
when :twelvebytekey
|
332
|
-
value = value.to_s.unpack("H*").first unless value.kind_of?(TwelveByteKey)
|
333
|
-
"X'#{value.to_s}'"
|
334
|
-
when :boolean
|
335
|
-
value = quoted_true if value.kind_of?(TrueClass)
|
336
|
-
value = quoted_false if value.kind_of?(FalseClass)
|
337
|
-
value
|
338
|
-
when :binary
|
339
|
-
blob_handle = @connection.create_blob(value.to_s)
|
340
|
-
puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE
|
341
|
-
blob_handle.handle
|
342
|
-
when :text
|
343
|
-
if value.kind_of?(String)
|
344
|
-
clobdata = value.to_s # ' (for ruby-mode)
|
345
|
-
else
|
346
|
-
clobdata = value.to_yaml
|
347
|
-
end
|
348
|
-
clob_handle = @connection.create_clob(clobdata)
|
349
|
-
puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE
|
350
|
-
clob_handle.handle
|
351
|
-
else
|
352
|
-
raise "*** UNKNOWN TYPE: #{column.type.inspect}"
|
353
|
-
end # case
|
354
|
-
# Since we don't have column type info, make a best guess based
|
355
|
-
# on the Ruby class of the value
|
356
|
-
else
|
357
|
-
retvalue = case value
|
358
|
-
when ActiveRecord::ConnectionAdapters::TwelveByteKey
|
359
|
-
s = value.unpack("H*").first
|
360
|
-
"X'#{s}'"
|
361
|
-
when String
|
362
|
-
if column && column.type == :binary
|
363
|
-
s = value.unpack("H*").first
|
364
|
-
"X'#{s}'"
|
365
|
-
elsif column && [:integer, :float, :decimal].include?(column.type)
|
366
|
-
value.to_s
|
367
|
-
else
|
368
|
-
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
369
|
-
end
|
370
|
-
when NilClass
|
371
|
-
"NULL"
|
372
|
-
when TrueClass
|
373
|
-
(column && column.type == :integer ? '1' : quoted_true)
|
374
|
-
when FalseClass
|
375
|
-
(column && column.type == :integer ? '0' : quoted_false)
|
376
|
-
when Float, Fixnum, Bignum, BigDecimal
|
377
|
-
value.to_s
|
378
|
-
when Time, Date, DateTime
|
379
|
-
if column
|
380
|
-
case column.type
|
381
|
-
when :date
|
382
|
-
"DATE '#{value.strftime("%Y-%m-%d")}'"
|
383
|
-
when :time
|
384
|
-
"TIME '#{value.strftime("%H:%M:%S")}'"
|
385
|
-
when :timestamp
|
386
|
-
"TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
387
|
-
else
|
388
|
-
raise NotImplementedError, "Unknown column type!"
|
389
|
-
end # case
|
390
|
-
else # Column wasn't passed in, so try to guess the right type
|
391
|
-
if value.kind_of? Date
|
392
|
-
"DATE '#{value.strftime("%Y-%m-%d")}'"
|
393
|
-
else
|
394
|
-
if [:hour, :min, :sec].all? {|part| value.send(:part).zero? }
|
395
|
-
"TIME '#{value.strftime("%H:%M:%S")}'"
|
396
|
-
else
|
397
|
-
"TIMESTAMP '#{quoted_date(value)}'"
|
398
|
-
end
|
399
|
-
end
|
400
|
-
end #if column
|
401
|
-
else
|
402
|
-
"'#{quote_string(value.to_yaml)}'"
|
403
|
-
end #case
|
404
|
-
end
|
405
|
-
else
|
406
|
-
retvalue = "NULL"
|
407
|
-
end
|
408
|
-
|
409
|
-
retvalue
|
410
|
-
end # def
|
411
|
-
|
412
|
-
# Quotes a string, escaping any ' (single quote) characters.
|
413
|
-
def quote_string(s)
|
414
|
-
s.gsub(/'/, "''") # ' (for ruby-mode)
|
415
|
-
end
|
416
|
-
|
417
|
-
def quote_column_name(name) #:nodoc:
|
418
|
-
%( "#{name}" )
|
419
|
-
end
|
420
|
-
|
421
|
-
def quoted_true
|
422
|
-
"true"
|
423
|
-
end
|
424
|
-
|
425
|
-
def quoted_false
|
426
|
-
"false"
|
427
|
-
end
|
428
|
-
|
429
|
-
|
430
|
-
# CONNECTION MANAGEMENT ====================================
|
431
|
-
|
432
|
-
def active?
|
433
|
-
true if @connection.status == 1
|
434
|
-
rescue => e
|
435
|
-
false
|
436
|
-
end
|
437
|
-
|
438
|
-
def reconnect!
|
439
|
-
@connection.close rescue nil
|
440
|
-
@connection = FBSQL_Connect.connect(*@connection_options.first(7))
|
441
|
-
end
|
442
|
-
|
443
|
-
# Close this connection
|
444
|
-
def disconnect!
|
445
|
-
@connection.close rescue nil
|
446
|
-
@active = false
|
447
|
-
end
|
448
|
-
|
449
|
-
# DATABASE STATEMENTS ======================================
|
450
|
-
|
451
|
-
# Returns an array of record hashes with the column names as keys and
|
452
|
-
# column values as values.
|
453
|
-
def select_all(sql, name = nil) #:nodoc:
|
454
|
-
fbsql = cleanup_fb_sql(sql)
|
455
|
-
return_value = []
|
456
|
-
fbresult = execute(sql, name)
|
457
|
-
puts "select_all SQL -> #{fbsql}" if FB_TRACE
|
458
|
-
columns = fbresult.columns
|
459
|
-
|
460
|
-
fbresult.each do |row|
|
461
|
-
puts "SQL <- #{row.inspect}" if FB_TRACE
|
462
|
-
hashed_row = {}
|
463
|
-
colnum = 0
|
464
|
-
row.each do |col|
|
465
|
-
hashed_row[columns[colnum]] = col
|
466
|
-
if col.kind_of?(FBSQL_LOB)
|
467
|
-
hashed_row[columns[colnum]] = col.read
|
468
|
-
end
|
469
|
-
colnum += 1
|
470
|
-
end
|
471
|
-
puts "raw row: #{hashed_row.inspect}" if FB_TRACE
|
472
|
-
return_value << hashed_row
|
473
|
-
end
|
474
|
-
return_value
|
475
|
-
end
|
476
|
-
|
477
|
-
def select_one(sql, name = nil) #:nodoc:
|
478
|
-
fbsql = cleanup_fb_sql(sql)
|
479
|
-
return_value = []
|
480
|
-
fbresult = execute(fbsql, name)
|
481
|
-
puts "SQL -> #{fbsql}" if FB_TRACE
|
482
|
-
columns = fbresult.columns
|
483
|
-
|
484
|
-
fbresult.each do |row|
|
485
|
-
puts "SQL <- #{row.inspect}" if FB_TRACE
|
486
|
-
hashed_row = {}
|
487
|
-
colnum = 0
|
488
|
-
row.each do |col|
|
489
|
-
hashed_row[columns[colnum]] = col
|
490
|
-
if col.kind_of?(FBSQL_LOB)
|
491
|
-
hashed_row[columns[colnum]] = col.read
|
492
|
-
end
|
493
|
-
colnum += 1
|
494
|
-
end
|
495
|
-
return_value << hashed_row
|
496
|
-
break
|
497
|
-
end
|
498
|
-
fbresult.clear
|
499
|
-
return_value.first
|
500
|
-
end
|
501
|
-
|
502
|
-
def query(sql, name = nil) #:nodoc:
|
503
|
-
fbsql = cleanup_fb_sql(sql)
|
504
|
-
puts "SQL(query) -> #{fbsql}" if FB_TRACE
|
505
|
-
log(fbsql, name) { @connection.query(fbsql) }
|
506
|
-
rescue => e
|
507
|
-
puts "FB Exception: #{e.inspect}" if FB_TRACE
|
508
|
-
raise e
|
509
|
-
end
|
510
|
-
|
511
|
-
def execute(sql, name = nil) #:nodoc:
|
512
|
-
fbsql = cleanup_fb_sql(sql)
|
513
|
-
puts "SQL(execute) -> #{fbsql}" if FB_TRACE
|
514
|
-
log(fbsql, name) { @connection.query(fbsql) }
|
515
|
-
rescue ActiveRecord::StatementInvalid => e
|
516
|
-
if e.message.scan(/Table name - \w* - exists/).empty?
|
517
|
-
puts "FB Exception: #{e.inspect}" if FB_TRACE
|
518
|
-
raise e
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
# Returns the last auto-generated ID from the affected table.
|
523
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
524
|
-
puts "SQL -> #{sql.inspect}" if FB_TRACE
|
525
|
-
execute(sql, name)
|
526
|
-
id_value || pk
|
527
|
-
end
|
528
|
-
|
529
|
-
# Executes the update statement and returns the number of rows affected.
|
530
|
-
def update(sql, name = nil) #:nodoc:
|
531
|
-
puts "SQL -> #{sql.inspect}" if FB_TRACE
|
532
|
-
execute(sql, name).num_rows
|
533
|
-
end
|
534
|
-
|
535
|
-
alias_method :delete, :update #:nodoc:
|
536
|
-
|
537
|
-
def set_pessimistic_transactions
|
538
|
-
if @transaction_mode == :optimistic
|
539
|
-
execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE"
|
540
|
-
@transaction_mode = :pessimistic
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
def set_optimistic_transactions
|
545
|
-
if @transaction_mode == :pessimistic
|
546
|
-
execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
|
547
|
-
@transaction_mode = :optimistic
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
def begin_db_transaction #:nodoc:
|
552
|
-
execute "SET COMMIT FALSE" rescue nil
|
553
|
-
end
|
554
|
-
|
555
|
-
def commit_db_transaction #:nodoc:
|
556
|
-
execute "COMMIT"
|
557
|
-
ensure
|
558
|
-
execute "SET COMMIT TRUE"
|
559
|
-
end
|
560
|
-
|
561
|
-
def rollback_db_transaction #:nodoc:
|
562
|
-
execute "ROLLBACK"
|
563
|
-
ensure
|
564
|
-
execute "SET COMMIT TRUE"
|
565
|
-
end
|
566
|
-
|
567
|
-
def add_limit_offset!(sql, options) #:nodoc:
|
568
|
-
if limit = options[:limit]
|
569
|
-
offset = options[:offset] || 0
|
570
|
-
|
571
|
-
# Here is the full syntax FrontBase supports:
|
572
|
-
# (from gclem@frontbase.com)
|
573
|
-
#
|
574
|
-
# TOP <limit - unsigned integer>
|
575
|
-
# TOP ( <offset expr>, <limit expr>)
|
576
|
-
|
577
|
-
# "TOP 0" is not allowed, so we have
|
578
|
-
# to use a cheap trick.
|
579
|
-
if limit.zero?
|
580
|
-
case sql
|
581
|
-
when /WHERE/i
|
582
|
-
sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ')
|
583
|
-
when /ORDER\s+BY/i
|
584
|
-
sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY')
|
585
|
-
else
|
586
|
-
sql << 'WHERE 0 = 1'
|
587
|
-
end
|
588
|
-
else
|
589
|
-
if offset.zero?
|
590
|
-
sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ")
|
591
|
-
else
|
592
|
-
sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ")
|
593
|
-
end
|
594
|
-
end
|
595
|
-
end
|
596
|
-
end
|
597
|
-
|
598
|
-
def prefetch_primary_key?(table_name = nil)
|
599
|
-
true
|
600
|
-
end
|
601
|
-
|
602
|
-
# Returns the next sequence value from a sequence generator. Not generally
|
603
|
-
# called directly; used by ActiveRecord to get the next primary key value
|
604
|
-
# when inserting a new database record (see #prefetch_primary_key?).
|
605
|
-
def next_sequence_value(sequence_name)
|
606
|
-
unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value")
|
607
|
-
# The test cases cannot handle a zero primary key
|
608
|
-
unique.zero? ? select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") : unique
|
609
|
-
end
|
610
|
-
|
611
|
-
def default_sequence_name(table, column)
|
612
|
-
table
|
613
|
-
end
|
614
|
-
|
615
|
-
# Set the sequence to the max value of the table's column.
|
616
|
-
def reset_sequence!(table, column, sequence = nil)
|
617
|
-
klasses = classes_for_table_name(table)
|
618
|
-
klass = klasses.nil? ? nil : klasses.first
|
619
|
-
pk = klass.primary_key unless klass.nil?
|
620
|
-
if pk && klass.columns_hash[pk].type == :integer
|
621
|
-
execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
|
-
def classes_for_table_name(table)
|
626
|
-
ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
|
627
|
-
end
|
628
|
-
|
629
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
630
|
-
klasses = classes_for_table_name(table)
|
631
|
-
klass = klasses.nil? ? nil : klasses.first
|
632
|
-
pk = klass.primary_key unless klass.nil?
|
633
|
-
if pk && klass.columns_hash[pk].type == :integer
|
634
|
-
mpk = select_value("SELECT MAX(#{pk}) FROM #{table}")
|
635
|
-
execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
|
636
|
-
end
|
637
|
-
end
|
638
|
-
|
639
|
-
# SCHEMA STATEMENTS ========================================
|
640
|
-
|
641
|
-
def structure_dump #:nodoc:
|
642
|
-
select_all("SHOW TABLES").inject('') do |structure, table|
|
643
|
-
structure << select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] << ";\n\n"
|
644
|
-
end
|
645
|
-
end
|
646
|
-
|
647
|
-
def recreate_database(name) #:nodoc:
|
648
|
-
drop_database(name)
|
649
|
-
create_database(name)
|
650
|
-
end
|
651
|
-
|
652
|
-
def create_database(name) #:nodoc:
|
653
|
-
execute "CREATE DATABASE #{name}"
|
654
|
-
end
|
655
|
-
|
656
|
-
def drop_database(name) #:nodoc:
|
657
|
-
execute "DROP DATABASE #{name}"
|
658
|
-
end
|
659
|
-
|
660
|
-
def current_database
|
661
|
-
select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase
|
662
|
-
end
|
663
|
-
|
664
|
-
def tables(name = nil) #:nodoc:
|
665
|
-
select_values(<<-SQL, nil)
|
666
|
-
SELECT "TABLE_NAME"
|
667
|
-
FROM INFORMATION_SCHEMA.TABLES AS T0,
|
668
|
-
INFORMATION_SCHEMA.SCHEMATA AS T1
|
669
|
-
WHERE T0.SCHEMA_PK = T1.SCHEMA_PK
|
670
|
-
AND "SCHEMA_NAME" = CURRENT_SCHEMA
|
671
|
-
SQL
|
672
|
-
end
|
673
|
-
|
674
|
-
def indexes(table_name, name = nil)#:nodoc:
|
675
|
-
indexes = []
|
676
|
-
current_index = nil
|
677
|
-
sql = <<-SQL
|
678
|
-
SELECT INDEX_NAME, T2.ORDINAL_POSITION, INDEX_COLUMN_COUNT, INDEX_TYPE,
|
679
|
-
"COLUMN_NAME", IS_NULLABLE
|
680
|
-
FROM INFORMATION_SCHEMA.TABLES AS T0,
|
681
|
-
INFORMATION_SCHEMA.INDEXES AS T1,
|
682
|
-
INFORMATION_SCHEMA.INDEX_COLUMN_USAGE AS T2,
|
683
|
-
INFORMATION_SCHEMA.COLUMNS AS T3
|
684
|
-
WHERE T0."TABLE_NAME" = '#{table_name}'
|
685
|
-
AND INDEX_TYPE <> 0
|
686
|
-
AND T0.TABLE_PK = T1.TABLE_PK
|
687
|
-
AND T0.TABLE_PK = T2.TABLE_PK
|
688
|
-
AND T0.TABLE_PK = T3.TABLE_PK
|
689
|
-
AND T1.INDEXES_PK = T2.INDEX_PK
|
690
|
-
AND T2.COLUMN_PK = T3.COLUMN_PK
|
691
|
-
ORDER BY INDEX_NAME, T2.ORDINAL_POSITION
|
692
|
-
SQL
|
693
|
-
|
694
|
-
columns = []
|
695
|
-
query(sql).each do |row|
|
696
|
-
index_name = row[0]
|
697
|
-
ord_position = row[1]
|
698
|
-
ndx_colcount = row[2]
|
699
|
-
index_type = row[3]
|
700
|
-
column_name = row[4]
|
701
|
-
|
702
|
-
is_unique = index_type == 1
|
703
|
-
|
704
|
-
columns << column_name
|
705
|
-
if ord_position == ndx_colcount
|
706
|
-
indexes << IndexDefinition.new(table_name, index_name, is_unique , columns)
|
707
|
-
columns = []
|
708
|
-
end
|
709
|
-
end
|
710
|
-
indexes
|
711
|
-
end
|
712
|
-
|
713
|
-
def columns(table_name, name = nil)#:nodoc:
|
714
|
-
sql = <<-SQL
|
715
|
-
SELECT "TABLE_NAME", "COLUMN_NAME", ORDINAL_POSITION, IS_NULLABLE, COLUMN_DEFAULT,
|
716
|
-
DATA_TYPE, DATA_TYPE_CODE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION,
|
717
|
-
NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, DATETIME_PRECISION_LEADING
|
718
|
-
FROM INFORMATION_SCHEMA.TABLES T0,
|
719
|
-
INFORMATION_SCHEMA.COLUMNS T1,
|
720
|
-
INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T3
|
721
|
-
WHERE "TABLE_NAME" = '#{table_name}'
|
722
|
-
AND T0.TABLE_PK = T1.TABLE_PK
|
723
|
-
AND T0.TABLE_PK = T3.TABLE_OR_DOMAIN_PK
|
724
|
-
AND T1.COLUMN_PK = T3.COLUMN_NAME_PK
|
725
|
-
ORDER BY T1.ORDINAL_POSITION
|
726
|
-
SQL
|
727
|
-
|
728
|
-
rawresults = query(sql,name)
|
729
|
-
columns = []
|
730
|
-
rawresults.each do |field|
|
731
|
-
args = [base = field[0],
|
732
|
-
name = field[1],
|
733
|
-
typecode = field[6],
|
734
|
-
typestring = field[5],
|
735
|
-
limit = field[7],
|
736
|
-
precision = field[8],
|
737
|
-
scale = field[9],
|
738
|
-
default = field[4],
|
739
|
-
nullable = field[3]]
|
740
|
-
columns << FrontBaseColumn.new(*args)
|
741
|
-
end
|
742
|
-
columns
|
743
|
-
end
|
744
|
-
|
745
|
-
def create_table(name, options = {})
|
746
|
-
table_definition = TableDefinition.new(self)
|
747
|
-
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
748
|
-
|
749
|
-
yield table_definition
|
750
|
-
|
751
|
-
if options[:force]
|
752
|
-
drop_table(name) rescue nil
|
753
|
-
end
|
754
|
-
|
755
|
-
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
756
|
-
create_sql << "#{name} ("
|
757
|
-
create_sql << table_definition.to_sql
|
758
|
-
create_sql << ") #{options[:options]}"
|
759
|
-
begin_db_transaction
|
760
|
-
execute create_sql
|
761
|
-
commit_db_transaction
|
762
|
-
rescue ActiveRecord::StatementInvalid => e
|
763
|
-
raise e unless e.message.match(/Table name - \w* - exists/)
|
764
|
-
end
|
765
|
-
|
766
|
-
def rename_table(name, new_name)
|
767
|
-
columns = columns(name)
|
768
|
-
pkcol = columns.find {|c| c.fb_autogen}
|
769
|
-
execute "ALTER TABLE NAME #{name} TO #{new_name}"
|
770
|
-
if pkcol
|
771
|
-
change_column_default(new_name,pkcol.name,"UNIQUE")
|
772
|
-
begin_db_transaction
|
773
|
-
mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}")
|
774
|
-
mpk = 0 if mpk.nil?
|
775
|
-
execute "SET UNIQUE=#{mpk} FOR #{new_name}"
|
776
|
-
commit_db_transaction
|
777
|
-
end
|
778
|
-
end
|
779
|
-
|
780
|
-
# Drops a table from the database.
|
781
|
-
def drop_table(name, options = {})
|
782
|
-
execute "DROP TABLE #{name} RESTRICT"
|
783
|
-
rescue ActiveRecord::StatementInvalid => e
|
784
|
-
raise e unless e.message.match(/Referenced TABLE - \w* - does not exist/)
|
785
|
-
end
|
786
|
-
|
787
|
-
# Adds a new column to the named table.
|
788
|
-
# See TableDefinition#column for details of the options you can use.
|
789
|
-
def add_column(table_name, column_name, type, options = {})
|
790
|
-
add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
|
791
|
-
options[:type] = type
|
792
|
-
add_column_options!(add_column_sql, options)
|
793
|
-
execute(add_column_sql)
|
794
|
-
end
|
795
|
-
|
796
|
-
def add_column_options!(sql, options) #:nodoc:
|
797
|
-
default_value = quote(options[:default], options[:column])
|
798
|
-
if options_include_default?(options)
|
799
|
-
if options[:type] == :boolean
|
800
|
-
default_value = options[:default] == 0 ? quoted_false : quoted_true
|
801
|
-
end
|
802
|
-
sql << " DEFAULT #{default_value}"
|
803
|
-
end
|
804
|
-
sql << " NOT NULL" if options[:null] == false
|
805
|
-
end
|
806
|
-
|
807
|
-
# Removes the column from the table definition.
|
808
|
-
# ===== Examples
|
809
|
-
# remove_column(:suppliers, :qualification)
|
810
|
-
def remove_column(table_name, column_name)
|
811
|
-
execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT"
|
812
|
-
end
|
813
|
-
|
814
|
-
def remove_index(table_name, options = {}) #:nodoc:
|
815
|
-
if options[:unique]
|
816
|
-
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT"
|
817
|
-
else
|
818
|
-
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
|
819
|
-
end
|
820
|
-
end
|
821
|
-
|
822
|
-
def change_column_default(table_name, column_name, default) #:nodoc:
|
823
|
-
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL"
|
824
|
-
end
|
825
|
-
|
826
|
-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
827
|
-
change_column_sql = %( ALTER COLUMN "#{table_name}"."#{column_name}" TO #{type_to_sql(type, options[:limit])} )
|
828
|
-
execute(change_column_sql)
|
829
|
-
change_column_sql = %( ALTER TABLE "#{table_name}" ALTER COLUMN "#{column_name}" )
|
830
|
-
|
831
|
-
if options_include_default?(options)
|
832
|
-
default_value = quote(options[:default], options[:column])
|
833
|
-
if type == :boolean
|
834
|
-
default_value = options[:default] == 0 ? quoted_false : quoted_true
|
835
|
-
end
|
836
|
-
change_column_sql << " SET DEFAULT #{default_value}"
|
837
|
-
end
|
838
|
-
|
839
|
-
execute(change_column_sql)
|
840
|
-
|
841
|
-
# change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
|
842
|
-
# add_column_options!(change_column_sql, options)
|
843
|
-
# execute(change_column_sql)
|
844
|
-
end
|
845
|
-
|
846
|
-
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
847
|
-
execute %( ALTER COLUMN NAME "#{table_name}"."#{column_name}" TO "#{new_column_name}" )
|
848
|
-
end
|
849
|
-
|
850
|
-
private
|
851
|
-
|
852
|
-
# Clean up sql to make it something FrontBase can digest
|
853
|
-
def cleanup_fb_sql(sql) #:nodoc:
|
854
|
-
# Turn non-standard != into standard <>
|
855
|
-
cleansql = sql.gsub("!=", "<>")
|
856
|
-
# Strip blank lines and comments
|
857
|
-
cleansql.split("\n").reject { |line| line.match(/^(?:\s*|--.*)$/) } * "\n"
|
858
|
-
end
|
859
|
-
end
|
860
|
-
end
|
861
|
-
end
|