activerecord 1.13.2 → 1.14.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 +452 -10
- data/RUNNING_UNIT_TESTS +1 -1
- data/lib/active_record.rb +5 -2
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/tree.rb +29 -25
- data/lib/active_record/aggregations.rb +3 -2
- data/lib/active_record/associations.rb +783 -337
- data/lib/active_record/associations/association_collection.rb +7 -12
- data/lib/active_record/associations/association_proxy.rb +62 -24
- data/lib/active_record/associations/belongs_to_association.rb +27 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
- data/lib/active_record/associations/has_many_association.rb +61 -56
- data/lib/active_record/associations/has_many_through_association.rb +144 -0
- data/lib/active_record/associations/has_one_association.rb +22 -16
- data/lib/active_record/base.rb +482 -182
- data/lib/active_record/calculations.rb +225 -0
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
- data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
- data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
- data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
- data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
- data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
- data/lib/active_record/fixtures.rb +42 -17
- data/lib/active_record/locking.rb +36 -15
- data/lib/active_record/migration.rb +111 -8
- data/lib/active_record/observer.rb +25 -1
- data/lib/active_record/reflection.rb +103 -41
- data/lib/active_record/schema.rb +2 -2
- data/lib/active_record/schema_dumper.rb +55 -18
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/validations.rb +65 -40
- data/lib/active_record/vendor/db2.rb +10 -5
- data/lib/active_record/vendor/simple.rb +693 -702
- data/lib/active_record/version.rb +2 -2
- data/rakefile +4 -4
- data/test/aaa_create_tables_test.rb +25 -6
- data/test/abstract_unit.rb +39 -1
- data/test/adapter_test.rb +31 -4
- data/test/associations_cascaded_eager_loading_test.rb +106 -0
- data/test/associations_go_eager_test.rb +85 -16
- data/test/associations_join_model_test.rb +338 -0
- data/test/associations_test.rb +129 -50
- data/test/base_test.rb +204 -49
- data/test/binary_test.rb +1 -1
- data/test/calculations_test.rb +169 -0
- data/test/callbacks_test.rb +5 -23
- data/test/class_inheritable_attributes_test.rb +1 -1
- data/test/column_alias_test.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -0
- data/test/connections/native_openbase/connection.rb +22 -0
- data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
- data/test/connections/native_sybase/connection.rb +24 -0
- data/test/defaults_test.rb +18 -0
- data/test/deprecated_associations_test.rb +2 -2
- data/test/deprecated_finder_test.rb +0 -6
- data/test/finder_test.rb +26 -23
- data/test/fixtures/accounts.yml +10 -0
- data/test/fixtures/author.rb +31 -6
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories_posts.yml +4 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +11 -0
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +17 -5
- data/test/fixtures/company_in_module.rb +19 -5
- data/test/fixtures/db_definitions/db2.drop.sql +3 -0
- data/test/fixtures/db_definitions/db2.sql +121 -100
- data/test/fixtures/db_definitions/db22.sql +2 -2
- data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
- data/test/fixtures/db_definitions/firebird.sql +26 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
- data/test/fixtures/db_definitions/mysql.sql +21 -1
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +282 -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/{oci.drop.sql → oracle.drop.sql} +6 -0
- data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
- data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
- data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +22 -1
- data/test/fixtures/db_definitions/schema.rb +32 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlite.sql +18 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +23 -3
- data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developers.yml +6 -1
- data/test/fixtures/developers_projects.yml +4 -0
- data/test/fixtures/funny_jokes.yml +14 -0
- data/test/fixtures/joke.rb +6 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mixin.rb +1 -1
- data/test/fixtures/person.rb +4 -1
- data/test/fixtures/post.rb +26 -1
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/tag.rb +5 -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/tasks.yml +2 -2
- data/test/fixtures/topic.rb +2 -2
- data/test/fixtures/topics.yml +1 -0
- data/test/fixtures_test.rb +47 -13
- data/test/inheritance_test.rb +2 -2
- data/test/locking_test.rb +15 -1
- data/test/method_scoping_test.rb +248 -13
- data/test/migration_test.rb +68 -11
- data/test/mixin_nested_set_test.rb +1 -1
- data/test/modules_test.rb +6 -1
- data/test/readonly_test.rb +1 -1
- data/test/reflection_test.rb +63 -9
- data/test/schema_dumper_test.rb +41 -0
- data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
- data/test/threaded_connections_test.rb +10 -0
- data/test/unconnected_test.rb +12 -5
- data/test/validations_test.rb +197 -10
- metadata +295 -260
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -75,6 +75,11 @@ module ActiveRecord
|
|
75
75
|
configure_connection
|
76
76
|
end
|
77
77
|
end
|
78
|
+
|
79
|
+
def disconnect!
|
80
|
+
# Both postgres and postgres-pr respond to :close
|
81
|
+
@connection.close rescue nil
|
82
|
+
end
|
78
83
|
|
79
84
|
def native_database_types
|
80
85
|
{
|
@@ -96,6 +101,9 @@ module ActiveRecord
|
|
96
101
|
true
|
97
102
|
end
|
98
103
|
|
104
|
+
def table_alias_length
|
105
|
+
63
|
106
|
+
end
|
99
107
|
|
100
108
|
# QUOTING ==================================================
|
101
109
|
|
@@ -296,19 +304,23 @@ module ActiveRecord
|
|
296
304
|
end
|
297
305
|
|
298
306
|
def add_column(table_name, column_name, type, options = {})
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET DEFAULT '#{options[:default]}'"
|
303
|
-
end
|
304
|
-
if options[:null] == false
|
305
|
-
sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
|
306
|
-
end
|
307
|
-
sql_commands.each { |cmd| execute(cmd) }
|
307
|
+
execute("ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}")
|
308
|
+
execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL") if options[:null] == false
|
309
|
+
change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
|
308
310
|
end
|
309
311
|
|
310
312
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
311
|
-
|
313
|
+
begin
|
314
|
+
execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
|
315
|
+
rescue ActiveRecord::StatementInvalid
|
316
|
+
# This is PG7, so we use a more arcane way of doing it.
|
317
|
+
begin_db_transaction
|
318
|
+
add_column(table_name, "#{column_name}_ar_tmp", type, options)
|
319
|
+
execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
|
320
|
+
remove_column(table_name, column_name)
|
321
|
+
rename_column(table_name, "#{column_name}_ar_tmp", column_name)
|
322
|
+
commit_db_transaction
|
323
|
+
end
|
312
324
|
change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
|
313
325
|
end
|
314
326
|
|
@@ -321,13 +333,7 @@ module ActiveRecord
|
|
321
333
|
end
|
322
334
|
|
323
335
|
def remove_index(table_name, options) #:nodoc:
|
324
|
-
|
325
|
-
index_name = options[:name]
|
326
|
-
else
|
327
|
-
index_name = "#{table_name}_#{options}_index"
|
328
|
-
end
|
329
|
-
|
330
|
-
execute "DROP INDEX #{index_name}"
|
336
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
331
337
|
end
|
332
338
|
|
333
339
|
|
@@ -470,10 +476,7 @@ module ActiveRecord
|
|
470
476
|
return $1 if value =~ /^'(.*)'::(bpchar|text|character varying)$/
|
471
477
|
|
472
478
|
# Numeric values
|
473
|
-
return value if value =~
|
474
|
-
|
475
|
-
# Date / Time magic values
|
476
|
-
return Time.now.to_s if value =~ /^now\(\)|^\('now'::text\)::(date|timestamp)/i
|
479
|
+
return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
|
477
480
|
|
478
481
|
# Fixed dates / times
|
479
482
|
return $1 if value =~ /^'(.+)'::(date|timestamp)/
|
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
36
36
|
|
37
37
|
# "Downgrade" deprecated sqlite API
|
38
38
|
if SQLite.const_defined?(:Version)
|
39
|
-
ConnectionAdapters::
|
39
|
+
ConnectionAdapters::SQLite2Adapter.new(db, logger)
|
40
40
|
else
|
41
41
|
ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
|
42
42
|
end
|
@@ -98,6 +98,10 @@ module ActiveRecord
|
|
98
98
|
def supports_migrations? #:nodoc:
|
99
99
|
true
|
100
100
|
end
|
101
|
+
|
102
|
+
def supports_count_distinct? #:nodoc:
|
103
|
+
false
|
104
|
+
end
|
101
105
|
|
102
106
|
def native_database_types #:nodoc:
|
103
107
|
{
|
@@ -130,7 +134,7 @@ module ActiveRecord
|
|
130
134
|
# DATABASE STATEMENTS ======================================
|
131
135
|
|
132
136
|
def execute(sql, name = nil) #:nodoc:
|
133
|
-
log(sql, name) { @connection.execute(sql) }
|
137
|
+
catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
|
134
138
|
end
|
135
139
|
|
136
140
|
def update(sql, name = nil) #:nodoc:
|
@@ -168,15 +172,15 @@ module ActiveRecord
|
|
168
172
|
|
169
173
|
|
170
174
|
def begin_db_transaction #:nodoc:
|
171
|
-
@connection.transaction
|
175
|
+
catch_schema_changes { @connection.transaction }
|
172
176
|
end
|
173
177
|
|
174
178
|
def commit_db_transaction #:nodoc:
|
175
|
-
@connection.commit
|
179
|
+
catch_schema_changes { @connection.commit }
|
176
180
|
end
|
177
181
|
|
178
182
|
def rollback_db_transaction #:nodoc:
|
179
|
-
@connection.rollback
|
183
|
+
catch_schema_changes { @connection.rollback }
|
180
184
|
end
|
181
185
|
|
182
186
|
|
@@ -189,9 +193,9 @@ module ActiveRecord
|
|
189
193
|
end
|
190
194
|
|
191
195
|
def columns(table_name, name = nil) #:nodoc:
|
192
|
-
table_structure(table_name).map
|
196
|
+
table_structure(table_name).map do |field|
|
193
197
|
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
|
194
|
-
|
198
|
+
end
|
195
199
|
end
|
196
200
|
|
197
201
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -326,9 +330,44 @@ module ActiveRecord
|
|
326
330
|
@connection.execute sql
|
327
331
|
end
|
328
332
|
end
|
333
|
+
|
334
|
+
def catch_schema_changes
|
335
|
+
return yield
|
336
|
+
rescue ActiveRecord::StatementInvalid => exception
|
337
|
+
if exception.message =~ /database schema has changed/
|
338
|
+
reconnect!
|
339
|
+
retry
|
340
|
+
else
|
341
|
+
raise
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
class SQLite2Adapter < SQLiteAdapter # :nodoc:
|
347
|
+
# SQLite 2 does not support COUNT(DISTINCT) queries:
|
348
|
+
#
|
349
|
+
# select COUNT(DISTINCT ArtistID) from CDs;
|
350
|
+
#
|
351
|
+
# In order to get the number of artists we execute the following statement
|
352
|
+
#
|
353
|
+
# SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
|
354
|
+
def execute(sql, name = nil) #:nodoc:
|
355
|
+
super(rewrite_count_distinct_queries(sql), name)
|
356
|
+
end
|
357
|
+
|
358
|
+
def rewrite_count_distinct_queries(sql)
|
359
|
+
if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
|
360
|
+
distinct_column = $1
|
361
|
+
distinct_query = $3
|
362
|
+
column_name = distinct_column.split('.').last
|
363
|
+
"SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
|
364
|
+
else
|
365
|
+
sql
|
366
|
+
end
|
367
|
+
end
|
329
368
|
end
|
330
369
|
|
331
|
-
class DeprecatedSQLiteAdapter <
|
370
|
+
class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
|
332
371
|
def insert(sql, name = nil, pk = nil, id_value = nil)
|
333
372
|
execute(sql, name = nil)
|
334
373
|
id_value || @connection.last_insert_rowid
|
@@ -27,6 +27,7 @@ module ActiveRecord
|
|
27
27
|
mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
|
28
28
|
username = config[:username] ? config[:username].to_s : 'sa'
|
29
29
|
password = config[:password] ? config[:password].to_s : ''
|
30
|
+
autocommit = config.key?(:autocommit) ? config[:autocommit] : true
|
30
31
|
if mode == "ODBC"
|
31
32
|
raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
|
32
33
|
dsn = config[:dsn]
|
@@ -38,8 +39,7 @@ module ActiveRecord
|
|
38
39
|
driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
|
39
40
|
end
|
40
41
|
conn = DBI.connect(driver_url, username, password)
|
41
|
-
|
42
|
-
conn["AutoCommit"] = true
|
42
|
+
conn["AutoCommit"] = autocommit
|
43
43
|
ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
|
44
44
|
end
|
45
45
|
end # class Base
|
@@ -48,8 +48,8 @@ module ActiveRecord
|
|
48
48
|
class ColumnWithIdentity < Column# :nodoc:
|
49
49
|
attr_reader :identity, :is_special, :scale
|
50
50
|
|
51
|
-
def initialize(name, default, sql_type = nil, is_identity = false, scale_value = 0)
|
52
|
-
super(name, default, sql_type)
|
51
|
+
def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
|
52
|
+
super(name, default, sql_type, null)
|
53
53
|
@identity = is_identity
|
54
54
|
@is_special = sql_type =~ /text|ntext|image/i ? true : false
|
55
55
|
@scale = scale_value
|
@@ -216,12 +216,18 @@ module ActiveRecord
|
|
216
216
|
|
217
217
|
# Reconnects to the database, returns false if no connection could be made.
|
218
218
|
def reconnect!
|
219
|
-
|
219
|
+
disconnect!
|
220
220
|
@connection = DBI.connect(*@connection_options)
|
221
221
|
rescue DBI::DatabaseError => e
|
222
222
|
@logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
|
223
223
|
false
|
224
224
|
end
|
225
|
+
|
226
|
+
# Disconnects from the database
|
227
|
+
|
228
|
+
def disconnect!
|
229
|
+
@connection.disconnect rescue nil
|
230
|
+
end
|
225
231
|
|
226
232
|
def select_all(sql, name = nil)
|
227
233
|
select(sql, name)
|
@@ -237,14 +243,20 @@ module ActiveRecord
|
|
237
243
|
return [] if table_name.blank?
|
238
244
|
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
239
245
|
table_name = table_name.split('.')[-1] unless table_name.nil?
|
240
|
-
sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
|
246
|
+
sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
|
241
247
|
# Comment out if you want to have the Columns select statment logged.
|
242
|
-
#
|
248
|
+
# Personally, I think it adds unnecessary bloat to the log.
|
243
249
|
# If you do comment it out, make sure to un-comment the "result" line that follows
|
244
250
|
result = log(sql, name) { @connection.select_all(sql) }
|
245
251
|
#result = @connection.select_all(sql)
|
246
252
|
columns = []
|
247
|
-
result.each
|
253
|
+
result.each do |field|
|
254
|
+
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
|
255
|
+
type = "#{field[:ColType]}(#{field[:Length]})"
|
256
|
+
is_identity = field[:IsIdentity] == 1
|
257
|
+
is_nullable = field[:IsNullable] == 'YES'
|
258
|
+
columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
|
259
|
+
end
|
248
260
|
columns
|
249
261
|
end
|
250
262
|
|
@@ -318,7 +330,7 @@ module ActiveRecord
|
|
318
330
|
def quote(value, column = nil)
|
319
331
|
case value
|
320
332
|
when String
|
321
|
-
if column && column.type == :binary
|
333
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
322
334
|
"'#{quote_string(column.class.string_to_binary(value))}'"
|
323
335
|
else
|
324
336
|
"'#{quote_string(value)}'"
|
@@ -375,7 +387,9 @@ module ActiveRecord
|
|
375
387
|
sql << " ) AS tmp2"
|
376
388
|
end
|
377
389
|
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
|
378
|
-
sql.sub!(/^\s*SELECT
|
390
|
+
sql.sub!(/^\s*SELECT([\s]*distinct)?/i) do
|
391
|
+
"SELECT#{$1} TOP #{options[:limit]}"
|
392
|
+
end unless options[:limit].nil?
|
379
393
|
end
|
380
394
|
end
|
381
395
|
|
@@ -391,6 +405,10 @@ module ActiveRecord
|
|
391
405
|
def create_database(name)
|
392
406
|
execute "CREATE DATABASE #{name}"
|
393
407
|
end
|
408
|
+
|
409
|
+
def current_database
|
410
|
+
@connection.select_one("select DB_NAME()")[0]
|
411
|
+
end
|
394
412
|
|
395
413
|
def tables(name = nil)
|
396
414
|
execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
|
@@ -454,7 +472,7 @@ module ActiveRecord
|
|
454
472
|
def type_to_sql(type, limit = nil) #:nodoc:
|
455
473
|
native = native_database_types[type]
|
456
474
|
# if there's no :limit in the default type definition, assume that type doesn't support limits
|
457
|
-
limit =
|
475
|
+
limit = limit || native[:limit]
|
458
476
|
column_type_sql = native[:name]
|
459
477
|
column_type_sql << "(#{limit})" if limit
|
460
478
|
column_type_sql
|
@@ -512,11 +530,13 @@ module ActiveRecord
|
|
512
530
|
end
|
513
531
|
|
514
532
|
def change_order_direction(order)
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
533
|
+
order.split(",").collect {|fragment|
|
534
|
+
case fragment
|
535
|
+
when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
|
536
|
+
when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
|
537
|
+
else String.new(fragment).split(',').join(' DESC,') + ' DESC'
|
538
|
+
end
|
539
|
+
}.join(",")
|
520
540
|
end
|
521
541
|
|
522
542
|
def get_special_columns(table_name)
|
@@ -0,0 +1,684 @@
|
|
1
|
+
# sybase_adaptor.rb
|
2
|
+
# Author: John Sheets <dev@metacasa.net>
|
3
|
+
# Date: 01 Mar 2006
|
4
|
+
#
|
5
|
+
# Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
|
6
|
+
#
|
7
|
+
# 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'sybsql'
|
14
|
+
|
15
|
+
module ActiveRecord
|
16
|
+
class Base
|
17
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
18
|
+
def self.sybase_connection(config) # :nodoc:
|
19
|
+
config = config.symbolize_keys
|
20
|
+
|
21
|
+
username = config[:username] ? config[:username].to_s : 'sa'
|
22
|
+
password = config[:password] ? config[:password].to_s : ''
|
23
|
+
|
24
|
+
if config.has_key?(:host)
|
25
|
+
host = config[:host]
|
26
|
+
else
|
27
|
+
raise ArgumentError, "No database server name specified. Missing argument: host."
|
28
|
+
end
|
29
|
+
|
30
|
+
if config.has_key?(:database)
|
31
|
+
database = config[:database]
|
32
|
+
else
|
33
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
34
|
+
end
|
35
|
+
|
36
|
+
ConnectionAdapters::SybaseAdapter.new(
|
37
|
+
SybSQL.new({'S' => host, 'U' => username, 'P' => password},
|
38
|
+
ConnectionAdapters::SybaseAdapterContext), database, logger)
|
39
|
+
end
|
40
|
+
end # class Base
|
41
|
+
|
42
|
+
module ConnectionAdapters
|
43
|
+
|
44
|
+
# ActiveRecord connection adapter for Sybase Open Client bindings
|
45
|
+
# (see http://raa.ruby-lang.org/project/sybase-ctlib).
|
46
|
+
#
|
47
|
+
# Options:
|
48
|
+
#
|
49
|
+
# * <tt>:host</tt> -- The name of the database server. No default, must be provided.
|
50
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
51
|
+
# * <tt>:username</tt> -- Defaults to sa.
|
52
|
+
# * <tt>:password</tt> -- Defaults to empty string.
|
53
|
+
#
|
54
|
+
# Usage Notes:
|
55
|
+
#
|
56
|
+
# * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead.
|
57
|
+
# * Table and column names are limited to 30 chars in Sybase 12.5
|
58
|
+
# * :binary columns not yet supported
|
59
|
+
# * :boolean columns use the BIT SQL type, which does not allow nulls or
|
60
|
+
# indexes. If a DEFAULT is not specified for ALTER TABLE commands, the
|
61
|
+
# column will be declared with DEFAULT 0 (false).
|
62
|
+
#
|
63
|
+
# Migrations:
|
64
|
+
#
|
65
|
+
# The Sybase adapter supports migrations, but for ALTER TABLE commands to
|
66
|
+
# work, the database must have the database option 'select into' set to
|
67
|
+
# 'true' with sp_dboption (see below). The sp_helpdb command lists the current
|
68
|
+
# options for all databases.
|
69
|
+
#
|
70
|
+
# 1> use mydb
|
71
|
+
# 2> go
|
72
|
+
# 1> master..sp_dboption mydb, "select into", true
|
73
|
+
# 2> go
|
74
|
+
# 1> checkpoint
|
75
|
+
# 2> go
|
76
|
+
class SybaseAdapter < AbstractAdapter # :nodoc:
|
77
|
+
class ColumnWithIdentity < Column
|
78
|
+
attr_reader :identity, :primary
|
79
|
+
|
80
|
+
def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
|
81
|
+
super(name, default, sql_type, nullable)
|
82
|
+
@default, @identity, @primary = type_cast(default), identity, primary
|
83
|
+
end
|
84
|
+
|
85
|
+
def simplified_type(field_type)
|
86
|
+
case field_type
|
87
|
+
when /int|bigint|smallint|tinyint/i then :integer
|
88
|
+
when /float|double|decimal|money|numeric|real|smallmoney/i then :float
|
89
|
+
when /text|ntext/i then :text
|
90
|
+
when /binary|image|varbinary/i then :binary
|
91
|
+
when /char|nchar|nvarchar|string|varchar/i then :string
|
92
|
+
when /bit/i then :boolean
|
93
|
+
when /datetime|smalldatetime/i then :datetime
|
94
|
+
else super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.string_to_binary(value)
|
99
|
+
"0x#{value.unpack("H*")[0]}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.binary_to_string(value)
|
103
|
+
# FIXME: sybase-ctlib uses separate sql method for binary columns.
|
104
|
+
value
|
105
|
+
end
|
106
|
+
end # class ColumnWithIdentity
|
107
|
+
|
108
|
+
# Sybase adapter
|
109
|
+
def initialize(connection, database, logger = nil)
|
110
|
+
super(connection, logger)
|
111
|
+
context = connection.context
|
112
|
+
context.init(logger)
|
113
|
+
@limit = @offset = 0
|
114
|
+
unless connection.sql_norow("USE #{database}")
|
115
|
+
raise "Cannot USE #{database}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def native_database_types
|
120
|
+
{
|
121
|
+
:primary_key => "numeric(9,0) IDENTITY PRIMARY KEY",
|
122
|
+
:string => { :name => "varchar", :limit => 255 },
|
123
|
+
:text => { :name => "text" },
|
124
|
+
:integer => { :name => "int" },
|
125
|
+
:float => { :name => "float", :limit => 8 },
|
126
|
+
:datetime => { :name => "datetime" },
|
127
|
+
:timestamp => { :name => "timestamp" },
|
128
|
+
:time => { :name => "time" },
|
129
|
+
:date => { :name => "datetime" },
|
130
|
+
:binary => { :name => "image"},
|
131
|
+
:boolean => { :name => "bit" }
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def adapter_name
|
136
|
+
'Sybase'
|
137
|
+
end
|
138
|
+
|
139
|
+
def active?
|
140
|
+
!(@connection.connection.nil? || @connection.connection_dead?)
|
141
|
+
end
|
142
|
+
|
143
|
+
def disconnect!
|
144
|
+
@connection.close rescue nil
|
145
|
+
end
|
146
|
+
|
147
|
+
def reconnect!
|
148
|
+
raise "Sybase Connection Adapter does not yet support reconnect!"
|
149
|
+
# disconnect!
|
150
|
+
# connect! # Not yet implemented
|
151
|
+
end
|
152
|
+
|
153
|
+
def table_alias_length
|
154
|
+
30
|
155
|
+
end
|
156
|
+
|
157
|
+
# Check for a limit statement and parse out the limit and
|
158
|
+
# offset if specified. Remove the limit from the sql statement
|
159
|
+
# and call select.
|
160
|
+
def select_all(sql, name = nil)
|
161
|
+
select(sql, name)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Remove limit clause from statement. This will almost always
|
165
|
+
# contain LIMIT 1 from the caller. set the rowcount to 1 before
|
166
|
+
# calling select.
|
167
|
+
def select_one(sql, name = nil)
|
168
|
+
result = select(sql, name)
|
169
|
+
result.nil? ? nil : result.first
|
170
|
+
end
|
171
|
+
|
172
|
+
def columns(table_name, name = nil)
|
173
|
+
table_structure(table_name).inject([]) do |columns, column|
|
174
|
+
name, default, type, nullable, identity, primary = column
|
175
|
+
columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
|
176
|
+
columns
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
181
|
+
begin
|
182
|
+
table_name = get_table_name(sql)
|
183
|
+
col = get_identity_column(table_name)
|
184
|
+
ii_enabled = false
|
185
|
+
|
186
|
+
if col != nil
|
187
|
+
if query_contains_identity_column(sql, col)
|
188
|
+
begin
|
189
|
+
execute enable_identity_insert(table_name, true)
|
190
|
+
ii_enabled = true
|
191
|
+
rescue Exception => e
|
192
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
log(sql, name) do
|
198
|
+
execute(sql, name)
|
199
|
+
ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"]
|
200
|
+
id_value || ident
|
201
|
+
end
|
202
|
+
ensure
|
203
|
+
if ii_enabled
|
204
|
+
begin
|
205
|
+
execute enable_identity_insert(table_name, false)
|
206
|
+
rescue Exception => e
|
207
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def execute(sql, name = nil)
|
214
|
+
log(sql, name) do
|
215
|
+
@connection.context.reset
|
216
|
+
@connection.set_rowcount(@limit || 0)
|
217
|
+
@limit = @offset = nil
|
218
|
+
@connection.sql_norow(sql)
|
219
|
+
if @connection.cmd_fail? or @connection.context.failed?
|
220
|
+
raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
# Return rows affected
|
224
|
+
@connection.results[0].row_count
|
225
|
+
end
|
226
|
+
|
227
|
+
alias_method :update, :execute
|
228
|
+
alias_method :delete, :execute
|
229
|
+
|
230
|
+
def begin_db_transaction() execute "BEGIN TRAN" end
|
231
|
+
def commit_db_transaction() execute "COMMIT TRAN" end
|
232
|
+
def rollback_db_transaction() execute "ROLLBACK TRAN" end
|
233
|
+
|
234
|
+
def tables(name = nil)
|
235
|
+
tables = []
|
236
|
+
select("select name from sysobjects where type='U'", name).each do |row|
|
237
|
+
tables << row['name']
|
238
|
+
end
|
239
|
+
tables
|
240
|
+
end
|
241
|
+
|
242
|
+
def indexes(table_name, name = nil)
|
243
|
+
indexes = []
|
244
|
+
select("exec sp_helpindex #{table_name}", name).each do |index|
|
245
|
+
unique = index["index_description"] =~ /unique/
|
246
|
+
primary = index["index_description"] =~ /^clustered/
|
247
|
+
if !primary
|
248
|
+
cols = index["index_keys"].split(", ").each { |col| col.strip! }
|
249
|
+
indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
indexes
|
253
|
+
end
|
254
|
+
|
255
|
+
def quoted_true
|
256
|
+
"1"
|
257
|
+
end
|
258
|
+
|
259
|
+
def quoted_false
|
260
|
+
"0"
|
261
|
+
end
|
262
|
+
|
263
|
+
def quote(value, column = nil)
|
264
|
+
case value
|
265
|
+
when String
|
266
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
267
|
+
"#{quote_string(column.class.string_to_binary(value))}"
|
268
|
+
elsif value =~ /^[+-]?[0-9]+$/o
|
269
|
+
value
|
270
|
+
else
|
271
|
+
"'#{quote_string(value)}'"
|
272
|
+
end
|
273
|
+
when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
|
274
|
+
when TrueClass then '1'
|
275
|
+
when FalseClass then '0'
|
276
|
+
when Float, Fixnum, Bignum then value.to_s
|
277
|
+
when Date then "'#{value.to_s}'"
|
278
|
+
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
279
|
+
else "'#{quote_string(value.to_yaml)}'"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def quote_column(type, value)
|
284
|
+
case type
|
285
|
+
when :boolean
|
286
|
+
case value
|
287
|
+
when String then value =~ /^[ty]/o ? 1 : 0
|
288
|
+
when true then 1
|
289
|
+
when false then 0
|
290
|
+
else value.to_i
|
291
|
+
end
|
292
|
+
when :integer then value.to_i
|
293
|
+
when :float then value.to_f
|
294
|
+
when :text, :string, :enum
|
295
|
+
case value
|
296
|
+
when String, Symbol, Fixnum, Float, Bignum, TrueClass, FalseClass
|
297
|
+
"'#{quote_string(value.to_s)}'"
|
298
|
+
else
|
299
|
+
"'#{quote_string(value.to_yaml)}'"
|
300
|
+
end
|
301
|
+
when :date, :datetime, :time
|
302
|
+
case value
|
303
|
+
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
304
|
+
when Date then "'#{value.to_s}'"
|
305
|
+
else "'#{quote_string(value)}'"
|
306
|
+
end
|
307
|
+
else "'#{quote_string(value.to_yaml)}'"
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def quote_string(s)
|
312
|
+
s.gsub(/'/, "''") # ' (for ruby-mode)
|
313
|
+
end
|
314
|
+
|
315
|
+
def quote_column_name(name)
|
316
|
+
"[#{name}]"
|
317
|
+
end
|
318
|
+
|
319
|
+
def add_limit_offset!(sql, options) # :nodoc:
|
320
|
+
@limit = options[:limit]
|
321
|
+
@offset = options[:offset]
|
322
|
+
if !normal_select?
|
323
|
+
# Use temp table to hack offset with Sybase
|
324
|
+
sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
|
325
|
+
elsif zero_limit?
|
326
|
+
# "SET ROWCOUNT 0" turns off limits, so we have
|
327
|
+
# to use a cheap trick.
|
328
|
+
if sql =~ /WHERE/i
|
329
|
+
sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
|
330
|
+
elsif sql =~ /ORDER\s+BY/i
|
331
|
+
sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
|
332
|
+
else
|
333
|
+
sql << 'WHERE 1 = 2'
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def supports_migrations? #:nodoc:
|
339
|
+
true
|
340
|
+
end
|
341
|
+
|
342
|
+
def rename_table(name, new_name)
|
343
|
+
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
344
|
+
end
|
345
|
+
|
346
|
+
def rename_column(table, column, new_column_name)
|
347
|
+
execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
|
348
|
+
end
|
349
|
+
|
350
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
351
|
+
sql_commands = ["ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"]
|
352
|
+
if options[:default]
|
353
|
+
remove_default_constraint(table_name, column_name)
|
354
|
+
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
|
355
|
+
end
|
356
|
+
sql_commands.each { |c| execute(c) }
|
357
|
+
end
|
358
|
+
|
359
|
+
def remove_column(table_name, column_name)
|
360
|
+
remove_default_constraint(table_name, column_name)
|
361
|
+
execute "ALTER TABLE #{table_name} DROP #{column_name}"
|
362
|
+
end
|
363
|
+
|
364
|
+
def remove_default_constraint(table_name, column_name)
|
365
|
+
defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
|
366
|
+
defaults.each {|constraint|
|
367
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
def remove_index(table_name, options = {})
|
372
|
+
execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
|
373
|
+
end
|
374
|
+
|
375
|
+
def add_column_options!(sql, options) #:nodoc:
|
376
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
377
|
+
|
378
|
+
if check_null_for_column?(options[:column], sql)
|
379
|
+
sql << (options[:null] == false ? " NOT NULL" : " NULL")
|
380
|
+
end
|
381
|
+
sql
|
382
|
+
end
|
383
|
+
|
384
|
+
private
|
385
|
+
def check_null_for_column?(col, sql)
|
386
|
+
# Sybase columns are NOT NULL by default, so explicitly set NULL
|
387
|
+
# if :null option is omitted. Disallow NULLs for boolean.
|
388
|
+
type = col.nil? ? "" : col[:type]
|
389
|
+
|
390
|
+
# Ignore :null if a primary key
|
391
|
+
return false if type =~ /PRIMARY KEY/i
|
392
|
+
|
393
|
+
# Ignore :null if a :boolean or BIT column
|
394
|
+
if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean
|
395
|
+
# If no default clause found on a boolean column, add one.
|
396
|
+
sql << " DEFAULT 0" if $1.nil?
|
397
|
+
return false
|
398
|
+
end
|
399
|
+
true
|
400
|
+
end
|
401
|
+
|
402
|
+
# Return the last value of the identity global value.
|
403
|
+
def last_insert_id
|
404
|
+
@connection.sql("SELECT @@IDENTITY")
|
405
|
+
unless @connection.cmd_fail?
|
406
|
+
id = @connection.top_row_result.rows.first.first
|
407
|
+
if id
|
408
|
+
id = id.to_i
|
409
|
+
id = nil if id == 0
|
410
|
+
end
|
411
|
+
else
|
412
|
+
id = nil
|
413
|
+
end
|
414
|
+
id
|
415
|
+
end
|
416
|
+
|
417
|
+
def affected_rows(name = nil)
|
418
|
+
@connection.sql("SELECT @@ROWCOUNT")
|
419
|
+
unless @connection.cmd_fail?
|
420
|
+
count = @connection.top_row_result.rows.first.first
|
421
|
+
count = count.to_i if count
|
422
|
+
else
|
423
|
+
0
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def normal_select?
|
428
|
+
# If limit is not set at all, we can ignore offset;
|
429
|
+
# If limit *is* set but offset is zero, use normal select
|
430
|
+
# with simple SET ROWCOUNT. Thus, only use the temp table
|
431
|
+
# if limit is set and offset > 0.
|
432
|
+
has_limit = !@limit.nil?
|
433
|
+
has_offset = !@offset.nil? && @offset > 0
|
434
|
+
!has_limit || !has_offset
|
435
|
+
end
|
436
|
+
|
437
|
+
def zero_limit?
|
438
|
+
!@limit.nil? && @limit == 0
|
439
|
+
end
|
440
|
+
|
441
|
+
# Select limit number of rows starting at optional offset.
|
442
|
+
def select(sql, name = nil)
|
443
|
+
@connection.context.reset
|
444
|
+
log(sql, name) do
|
445
|
+
if normal_select?
|
446
|
+
# If limit is not explicitly set, return all results.
|
447
|
+
@logger.debug "Setting row count to (#{@limit || 'off'})" if @logger
|
448
|
+
|
449
|
+
# Run a normal select
|
450
|
+
@connection.set_rowcount(@limit || 0)
|
451
|
+
@connection.sql(sql)
|
452
|
+
else
|
453
|
+
# Select into a temp table and prune results
|
454
|
+
@logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
|
455
|
+
@connection.set_rowcount(@limit + (@offset || 0))
|
456
|
+
@connection.sql_norow(sql) # Select into temp table
|
457
|
+
@logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
|
458
|
+
@connection.set_rowcount(@offset || 0)
|
459
|
+
@connection.sql_norow("delete from #artemp") # Delete leading rows
|
460
|
+
@connection.set_rowcount(0)
|
461
|
+
@connection.sql("select * from #artemp") # Return the rest
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
rows = []
|
466
|
+
if @connection.context.failed? or @connection.cmd_fail?
|
467
|
+
raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
|
468
|
+
else
|
469
|
+
results = @connection.top_row_result
|
470
|
+
if results && results.rows.length > 0
|
471
|
+
fields = fixup_column_names(results.columns)
|
472
|
+
results.rows.each do |row|
|
473
|
+
hashed_row = {}
|
474
|
+
row.zip(fields) { |cell, column| hashed_row[column] = cell }
|
475
|
+
rows << hashed_row
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
@connection.sql_norow("drop table #artemp") if !normal_select?
|
480
|
+
@limit = @offset = nil
|
481
|
+
return rows
|
482
|
+
end
|
483
|
+
|
484
|
+
def enable_identity_insert(table_name, enable = true)
|
485
|
+
if has_identity_column(table_name)
|
486
|
+
"SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def get_table_name(sql)
|
491
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
492
|
+
$1
|
493
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
494
|
+
$1
|
495
|
+
else
|
496
|
+
nil
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
def has_identity_column(table_name)
|
501
|
+
!get_identity_column(table_name).nil?
|
502
|
+
end
|
503
|
+
|
504
|
+
def get_identity_column(table_name)
|
505
|
+
@table_columns = {} unless @table_columns
|
506
|
+
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
|
507
|
+
@table_columns[table_name].each do |col|
|
508
|
+
return col.name if col.identity
|
509
|
+
end
|
510
|
+
|
511
|
+
return nil
|
512
|
+
end
|
513
|
+
|
514
|
+
def query_contains_identity_column(sql, col)
|
515
|
+
sql =~ /\[#{col}\]/
|
516
|
+
end
|
517
|
+
|
518
|
+
# Remove trailing _ from names.
|
519
|
+
def fixup_column_names(columns)
|
520
|
+
columns.map { |column| column.sub(/_$/, '') }
|
521
|
+
end
|
522
|
+
|
523
|
+
def table_structure(table_name)
|
524
|
+
sql = <<SQLTEXT
|
525
|
+
SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
|
526
|
+
col.status, obj.sysstat2, def.text
|
527
|
+
FROM sysobjects obj, syscolumns col, systypes type, syscomments def
|
528
|
+
WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
|
529
|
+
AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
|
530
|
+
SQLTEXT
|
531
|
+
log(sql, "Get Column Info ") do
|
532
|
+
@connection.set_rowcount(0)
|
533
|
+
@connection.sql(sql)
|
534
|
+
end
|
535
|
+
if @connection.context.failed?
|
536
|
+
raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
|
537
|
+
elsif !@connection.cmd_fail?
|
538
|
+
columns = []
|
539
|
+
results = @connection.top_row_result
|
540
|
+
results.rows.each do |row|
|
541
|
+
name, type, prec, scale, length, status, sysstat2, default = row
|
542
|
+
type = normalize_type(type, prec, scale, length)
|
543
|
+
default_value = nil
|
544
|
+
name.sub!(/_$/o, '')
|
545
|
+
if default =~ /DEFAULT\s+(.+)/o
|
546
|
+
default_value = $1.strip
|
547
|
+
default_value = default_value[1...-1] if default_value =~ /^['"]/o
|
548
|
+
end
|
549
|
+
nullable = (status & 8) == 8
|
550
|
+
identity = status >= 128
|
551
|
+
primary = (sysstat2 & 8) == 8
|
552
|
+
|
553
|
+
columns << [name, default_value, type, nullable, identity, primary]
|
554
|
+
end
|
555
|
+
columns
|
556
|
+
else
|
557
|
+
nil
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
def normalize_type(field_type, prec, scale, length)
|
562
|
+
if field_type =~ /numeric/i and (scale.nil? or scale == 0)
|
563
|
+
type = 'int'
|
564
|
+
elsif field_type =~ /money/i
|
565
|
+
type = 'numeric'
|
566
|
+
else
|
567
|
+
type = field_type
|
568
|
+
end
|
569
|
+
size = ''
|
570
|
+
if prec
|
571
|
+
size = "(#{prec})"
|
572
|
+
elsif length
|
573
|
+
size = "(#{length})"
|
574
|
+
end
|
575
|
+
return type + size
|
576
|
+
end
|
577
|
+
|
578
|
+
def default_value(value)
|
579
|
+
end
|
580
|
+
end # class SybaseAdapter
|
581
|
+
|
582
|
+
class SybaseAdapterContext < SybSQLContext
|
583
|
+
DEADLOCK = 1205
|
584
|
+
attr_reader :message
|
585
|
+
|
586
|
+
def init(logger = nil)
|
587
|
+
@deadlocked = false
|
588
|
+
@failed = false
|
589
|
+
@logger = logger
|
590
|
+
@message = nil
|
591
|
+
end
|
592
|
+
|
593
|
+
def srvmsgCB(con, msg)
|
594
|
+
# Do not log change of context messages.
|
595
|
+
if msg['severity'] == 10 or msg['severity'] == 0
|
596
|
+
return true
|
597
|
+
end
|
598
|
+
|
599
|
+
if msg['msgnumber'] == DEADLOCK
|
600
|
+
@deadlocked = true
|
601
|
+
else
|
602
|
+
@logger.info "SQL Command failed!" if @logger
|
603
|
+
@failed = true
|
604
|
+
end
|
605
|
+
|
606
|
+
if @logger
|
607
|
+
@logger.error "** SybSQLContext Server Message: **"
|
608
|
+
@logger.error " Message number #{msg['msgnumber']} Severity #{msg['severity']} State #{msg['state']} Line #{msg['line']}"
|
609
|
+
@logger.error " Server #{msg['srvname']}"
|
610
|
+
@logger.error " Procedure #{msg['proc']}"
|
611
|
+
@logger.error " Message String: #{msg['text']}"
|
612
|
+
end
|
613
|
+
|
614
|
+
@message = msg['text']
|
615
|
+
|
616
|
+
true
|
617
|
+
end
|
618
|
+
|
619
|
+
def deadlocked?
|
620
|
+
@deadlocked
|
621
|
+
end
|
622
|
+
|
623
|
+
def failed?
|
624
|
+
@failed
|
625
|
+
end
|
626
|
+
|
627
|
+
def reset
|
628
|
+
@deadlocked = false
|
629
|
+
@failed = false
|
630
|
+
@message = nil
|
631
|
+
end
|
632
|
+
|
633
|
+
def cltmsgCB(con, msg)
|
634
|
+
return true unless ( msg.kind_of?(Hash) )
|
635
|
+
unless ( msg[ "severity" ] ) then
|
636
|
+
return true
|
637
|
+
end
|
638
|
+
|
639
|
+
if @logger
|
640
|
+
@logger.error "** SybSQLContext Client-Message: **"
|
641
|
+
@logger.error " Message number: LAYER=#{msg[ 'layer' ]} ORIGIN=#{msg[ 'origin' ]} SEVERITY=#{msg[ 'severity' ]} NUMBER=#{msg[ 'number' ]}"
|
642
|
+
@logger.error " Message String: #{msg['msgstring']}"
|
643
|
+
@logger.error " OS Error: #{msg['osstring']}"
|
644
|
+
|
645
|
+
@message = msg['msgstring']
|
646
|
+
end
|
647
|
+
|
648
|
+
@failed = true
|
649
|
+
|
650
|
+
# Not retry , CS_CV_RETRY_FAIL( probability TimeOut )
|
651
|
+
if( msg[ 'severity' ] == "RETRY_FAIL" ) then
|
652
|
+
@timeout_p = true
|
653
|
+
return false
|
654
|
+
end
|
655
|
+
|
656
|
+
return true
|
657
|
+
end
|
658
|
+
end # class SybaseAdapterContext
|
659
|
+
|
660
|
+
end # module ConnectionAdapters
|
661
|
+
end # module ActiveRecord
|
662
|
+
|
663
|
+
|
664
|
+
# Allow identity inserts for fixtures.
|
665
|
+
require "active_record/fixtures"
|
666
|
+
class Fixtures
|
667
|
+
alias :original_insert_fixtures :insert_fixtures
|
668
|
+
|
669
|
+
def insert_fixtures
|
670
|
+
values.each do |fixture|
|
671
|
+
allow_identity_inserts table_name, true
|
672
|
+
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
673
|
+
allow_identity_inserts table_name, false
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
def allow_identity_inserts(table_name, enable)
|
678
|
+
@connection.execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" rescue nil
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
rescue LoadError => cannot_require_sybase
|
683
|
+
# Couldn't load sybase adapter
|
684
|
+
end
|