activerecord-jdbc-adapter-onsite 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Appraisals +16 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +45 -0
- data/History.txt +488 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +214 -0
- data/Rakefile +62 -0
- data/activerecord-jdbc-adapter.gemspec +23 -0
- data/bench/bench_attributes.rb +13 -0
- data/bench/bench_attributes_new.rb +14 -0
- data/bench/bench_create.rb +12 -0
- data/bench/bench_find_all.rb +12 -0
- data/bench/bench_find_all_mt.rb +25 -0
- data/bench/bench_model.rb +85 -0
- data/bench/bench_new.rb +12 -0
- data/bench/bench_new_valid.rb +12 -0
- data/bench/bench_valid.rb +13 -0
- data/gemfiles/rails23.gemfile +10 -0
- data/gemfiles/rails23.gemfile.lock +38 -0
- data/gemfiles/rails30.gemfile +9 -0
- data/gemfiles/rails30.gemfile.lock +33 -0
- data/gemfiles/rails31.gemfile +9 -0
- data/gemfiles/rails31.gemfile.lock +35 -0
- data/gemfiles/rails32.gemfile +9 -0
- data/gemfiles/rails32.gemfile.lock +35 -0
- data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
- data/lib/activerecord-jdbc-adapter.rb +8 -0
- data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
- data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +15 -0
- data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/mssql_compiler.rb +46 -0
- data/lib/arel/visitors/compat.rb +13 -0
- data/lib/arel/visitors/db2.rb +17 -0
- data/lib/arel/visitors/derby.rb +32 -0
- data/lib/arel/visitors/firebird.rb +24 -0
- data/lib/arel/visitors/hsqldb.rb +26 -0
- data/lib/arel/visitors/sql_server.rb +46 -0
- data/lib/arjdbc.rb +24 -0
- data/lib/arjdbc/db2.rb +2 -0
- data/lib/arjdbc/db2/adapter.rb +541 -0
- data/lib/arjdbc/derby.rb +7 -0
- data/lib/arjdbc/derby/adapter.rb +358 -0
- data/lib/arjdbc/derby/connection_methods.rb +19 -0
- data/lib/arjdbc/discover.rb +92 -0
- data/lib/arjdbc/firebird.rb +2 -0
- data/lib/arjdbc/firebird/adapter.rb +140 -0
- data/lib/arjdbc/h2.rb +4 -0
- data/lib/arjdbc/h2/adapter.rb +54 -0
- data/lib/arjdbc/h2/connection_methods.rb +13 -0
- data/lib/arjdbc/hsqldb.rb +4 -0
- data/lib/arjdbc/hsqldb/adapter.rb +184 -0
- data/lib/arjdbc/hsqldb/connection_methods.rb +15 -0
- data/lib/arjdbc/informix.rb +3 -0
- data/lib/arjdbc/informix/adapter.rb +142 -0
- data/lib/arjdbc/informix/connection_methods.rb +11 -0
- data/lib/arjdbc/jdbc.rb +2 -0
- data/lib/arjdbc/jdbc/adapter.rb +356 -0
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/callbacks.rb +44 -0
- data/lib/arjdbc/jdbc/column.rb +47 -0
- data/lib/arjdbc/jdbc/compatibility.rb +51 -0
- data/lib/arjdbc/jdbc/connection.rb +134 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +16 -0
- data/lib/arjdbc/jdbc/core_ext.rb +24 -0
- data/lib/arjdbc/jdbc/discover.rb +18 -0
- data/lib/arjdbc/jdbc/driver.rb +35 -0
- data/lib/arjdbc/jdbc/extension.rb +47 -0
- data/lib/arjdbc/jdbc/java.rb +14 -0
- data/lib/arjdbc/jdbc/jdbc.rake +131 -0
- data/lib/arjdbc/jdbc/missing_functionality_helper.rb +88 -0
- data/lib/arjdbc/jdbc/quoted_primary_key.rb +28 -0
- data/lib/arjdbc/jdbc/railtie.rb +9 -0
- data/lib/arjdbc/jdbc/rake_tasks.rb +10 -0
- data/lib/arjdbc/jdbc/require_driver.rb +16 -0
- data/lib/arjdbc/jdbc/type_converter.rb +126 -0
- data/lib/arjdbc/mimer.rb +2 -0
- data/lib/arjdbc/mimer/adapter.rb +142 -0
- data/lib/arjdbc/mssql.rb +4 -0
- data/lib/arjdbc/mssql/adapter.rb +477 -0
- data/lib/arjdbc/mssql/connection_methods.rb +31 -0
- data/lib/arjdbc/mssql/limit_helpers.rb +101 -0
- data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
- data/lib/arjdbc/mssql/tsql_helper.rb +61 -0
- data/lib/arjdbc/mysql.rb +4 -0
- data/lib/arjdbc/mysql/adapter.rb +505 -0
- data/lib/arjdbc/mysql/connection_methods.rb +28 -0
- data/lib/arjdbc/oracle.rb +3 -0
- data/lib/arjdbc/oracle/adapter.rb +432 -0
- data/lib/arjdbc/oracle/connection_methods.rb +12 -0
- data/lib/arjdbc/postgresql.rb +4 -0
- data/lib/arjdbc/postgresql/adapter.rb +861 -0
- data/lib/arjdbc/postgresql/connection_methods.rb +23 -0
- data/lib/arjdbc/sqlite3.rb +4 -0
- data/lib/arjdbc/sqlite3/adapter.rb +389 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +35 -0
- data/lib/arjdbc/sybase.rb +2 -0
- data/lib/arjdbc/sybase/adapter.rb +46 -0
- data/lib/arjdbc/version.rb +8 -0
- data/lib/generators/jdbc/USAGE +10 -0
- data/lib/generators/jdbc/jdbc_generator.rb +9 -0
- data/lib/jdbc_adapter.rb +2 -0
- data/lib/jdbc_adapter/rake_tasks.rb +3 -0
- data/lib/jdbc_adapter/version.rb +3 -0
- data/lib/pg.rb +26 -0
- data/pom.xml +57 -0
- data/rails_generators/jdbc_generator.rb +15 -0
- data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
- data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
- data/rakelib/bundler_ext.rb +11 -0
- data/rakelib/compile.rake +23 -0
- data/rakelib/db.rake +39 -0
- data/rakelib/rails.rake +41 -0
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +69 -0
- data/src/java/arjdbc/derby/DerbyModule.java +324 -0
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +70 -0
- data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +68 -0
- data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +36 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1346 -0
- data/src/java/arjdbc/jdbc/SQLBlock.java +48 -0
- data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +127 -0
- data/src/java/arjdbc/mysql/MySQLModule.java +134 -0
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +161 -0
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
- data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +82 -0
- data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +126 -0
- data/test/abstract_db_create.rb +135 -0
- data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
- data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
- data/test/activerecord/jall.sh +7 -0
- data/test/activerecord/jtest.sh +3 -0
- data/test/db/db2.rb +11 -0
- data/test/db/derby.rb +12 -0
- data/test/db/h2.rb +11 -0
- data/test/db/hsqldb.rb +13 -0
- data/test/db/informix.rb +11 -0
- data/test/db/jdbc.rb +12 -0
- data/test/db/jndi_config.rb +40 -0
- data/test/db/logger.rb +3 -0
- data/test/db/mssql.rb +9 -0
- data/test/db/mysql.rb +10 -0
- data/test/db/oracle.rb +34 -0
- data/test/db/postgres.rb +18 -0
- data/test/db/sqlite3.rb +11 -0
- data/test/db2_reset_column_information_test.rb +8 -0
- data/test/db2_simple_test.rb +66 -0
- data/test/derby_migration_test.rb +68 -0
- data/test/derby_multibyte_test.rb +12 -0
- data/test/derby_reset_column_information_test.rb +8 -0
- data/test/derby_row_locking_test.rb +9 -0
- data/test/derby_simple_test.rb +139 -0
- data/test/generic_jdbc_connection_test.rb +29 -0
- data/test/h2_change_column_test.rb +68 -0
- data/test/h2_simple_test.rb +41 -0
- data/test/has_many_through.rb +79 -0
- data/test/helper.rb +108 -0
- data/test/hsqldb_simple_test.rb +6 -0
- data/test/informix_simple_test.rb +48 -0
- data/test/jdbc_common.rb +28 -0
- data/test/jndi_callbacks_test.rb +36 -0
- data/test/jndi_test.rb +25 -0
- data/test/manualTestDatabase.rb +191 -0
- data/test/models/add_not_null_column_to_table.rb +9 -0
- data/test/models/auto_id.rb +15 -0
- data/test/models/custom_pk_name.rb +14 -0
- data/test/models/data_types.rb +30 -0
- data/test/models/entry.rb +40 -0
- data/test/models/mixed_case.rb +22 -0
- data/test/models/reserved_word.rb +15 -0
- data/test/models/string_id.rb +17 -0
- data/test/models/thing.rb +16 -0
- data/test/models/validates_uniqueness_of_string.rb +19 -0
- data/test/mssql_db_create_test.rb +26 -0
- data/test/mssql_identity_insert_test.rb +19 -0
- data/test/mssql_ignore_system_views_test.rb +27 -0
- data/test/mssql_legacy_types_test.rb +58 -0
- data/test/mssql_limit_offset_test.rb +136 -0
- data/test/mssql_multibyte_test.rb +18 -0
- data/test/mssql_null_test.rb +14 -0
- data/test/mssql_reset_column_information_test.rb +8 -0
- data/test/mssql_row_locking_sql_test.rb +159 -0
- data/test/mssql_row_locking_test.rb +9 -0
- data/test/mssql_simple_test.rb +55 -0
- data/test/mysql_db_create_test.rb +27 -0
- data/test/mysql_index_length_test.rb +58 -0
- data/test/mysql_info_test.rb +123 -0
- data/test/mysql_multibyte_test.rb +10 -0
- data/test/mysql_nonstandard_primary_key_test.rb +42 -0
- data/test/mysql_reset_column_information_test.rb +8 -0
- data/test/mysql_simple_test.rb +125 -0
- data/test/oracle_reset_column_information_test.rb +8 -0
- data/test/oracle_simple_test.rb +18 -0
- data/test/oracle_specific_test.rb +83 -0
- data/test/postgres_db_create_test.rb +32 -0
- data/test/postgres_drop_db_test.rb +16 -0
- data/test/postgres_information_schema_leak_test.rb +29 -0
- data/test/postgres_mixed_case_test.rb +29 -0
- data/test/postgres_native_type_mapping_test.rb +93 -0
- data/test/postgres_nonseq_pkey_test.rb +38 -0
- data/test/postgres_reserved_test.rb +22 -0
- data/test/postgres_reset_column_information_test.rb +8 -0
- data/test/postgres_schema_search_path_test.rb +48 -0
- data/test/postgres_simple_test.rb +168 -0
- data/test/postgres_table_alias_length_test.rb +15 -0
- data/test/postgres_type_conversion_test.rb +34 -0
- data/test/row_locking.rb +90 -0
- data/test/simple.rb +731 -0
- data/test/sqlite3_reset_column_information_test.rb +8 -0
- data/test/sqlite3_simple_test.rb +316 -0
- data/test/sybase_jtds_simple_test.rb +28 -0
- data/test/sybase_reset_column_information_test.rb +8 -0
- metadata +288 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module ArJdbc
|
|
2
|
+
module MissingFunctionalityHelper
|
|
3
|
+
#Taken from SQLite adapter
|
|
4
|
+
|
|
5
|
+
def alter_table(table_name, options = {}) #:nodoc:
|
|
6
|
+
table_name = table_name.to_s.downcase
|
|
7
|
+
altered_table_name = "altered_#{table_name}"
|
|
8
|
+
caller = lambda {|definition| yield definition if block_given?}
|
|
9
|
+
|
|
10
|
+
transaction do
|
|
11
|
+
# A temporary table might improve performance here, but
|
|
12
|
+
# it doesn't seem to maintain indices across the whole move.
|
|
13
|
+
move_table(table_name, altered_table_name,
|
|
14
|
+
options)
|
|
15
|
+
move_table(altered_table_name, table_name, &caller)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def move_table(from, to, options = {}, &block) #:nodoc:
|
|
20
|
+
copy_table(from, to, options, &block)
|
|
21
|
+
drop_table(from)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def copy_table(from, to, options = {}) #:nodoc:
|
|
25
|
+
options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
|
|
26
|
+
create_table(to, options) do |definition|
|
|
27
|
+
@definition = definition
|
|
28
|
+
columns(from).each do |column|
|
|
29
|
+
column_name = options[:rename] ?
|
|
30
|
+
(options[:rename][column.name] ||
|
|
31
|
+
options[:rename][column.name.to_sym] ||
|
|
32
|
+
column.name) : column.name
|
|
33
|
+
|
|
34
|
+
@definition.column(column_name, column.type,
|
|
35
|
+
:limit => column.limit, :default => column.default,
|
|
36
|
+
:precision => column.precision, :scale => column.scale,
|
|
37
|
+
:null => column.null)
|
|
38
|
+
end
|
|
39
|
+
@definition.primary_key(primary_key(from)) if primary_key(from)
|
|
40
|
+
yield @definition if block_given?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
|
44
|
+
copy_table_contents(from, to,
|
|
45
|
+
@definition.columns.map {|column| column.name},
|
|
46
|
+
options[:rename] || {})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
|
50
|
+
indexes(from).each do |index|
|
|
51
|
+
name = index.name.downcase
|
|
52
|
+
if to == "altered_#{from}"
|
|
53
|
+
name = "temp_#{name}"
|
|
54
|
+
elsif from == "altered_#{to}"
|
|
55
|
+
name = name[5..-1]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
to_column_names = columns(to).map(&:name)
|
|
59
|
+
columns = index.columns.map {|c| rename[c] || c }.select do |column|
|
|
60
|
+
to_column_names.include?(column)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless columns.empty?
|
|
64
|
+
# index name can't be the same
|
|
65
|
+
opts = { :name => name.gsub(/(_?)(#{from})_/, "\\1#{to}_") }
|
|
66
|
+
opts[:unique] = true if index.unique
|
|
67
|
+
add_index(to, columns, opts)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
|
73
|
+
column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
|
|
74
|
+
rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
|
|
75
|
+
from_columns = columns(from).collect {|col| col.name}
|
|
76
|
+
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
|
77
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
|
78
|
+
|
|
79
|
+
quoted_to = quote_table_name(to)
|
|
80
|
+
execute("SELECT * FROM #{quote_table_name(from)}").each do |row|
|
|
81
|
+
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
|
82
|
+
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
|
83
|
+
sql << ')'
|
|
84
|
+
execute sql
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module ArJdbc
|
|
2
|
+
module QuotedPrimaryKeyExtension
|
|
3
|
+
def self.extended(base)
|
|
4
|
+
# Rails 3 method Rails 2 method
|
|
5
|
+
meth = [:arel_attributes_values, :attributes_with_quotes].detect do |m|
|
|
6
|
+
base.private_instance_methods.include?(m.to_s)
|
|
7
|
+
end
|
|
8
|
+
pk_hash_key = "self.class.primary_key"
|
|
9
|
+
pk_hash_value = '"?"'
|
|
10
|
+
if meth == :arel_attributes_values
|
|
11
|
+
pk_hash_key = "self.class.arel_table[#{pk_hash_key}]"
|
|
12
|
+
pk_hash_value = "Arel::SqlLiteral.new(#{pk_hash_value})"
|
|
13
|
+
end
|
|
14
|
+
if meth
|
|
15
|
+
base.module_eval <<-PK, __FILE__, __LINE__
|
|
16
|
+
alias :#{meth}_pre_pk :#{meth}
|
|
17
|
+
def #{meth}(include_primary_key = true, *args) #:nodoc:
|
|
18
|
+
aq = #{meth}_pre_pk(include_primary_key, *args)
|
|
19
|
+
if connection.is_a?(ArJdbc::Oracle) || connection.is_a?(ArJdbc::Mimer)
|
|
20
|
+
aq[#{pk_hash_key}] = #{pk_hash_value} if include_primary_key && aq[#{pk_hash_key}].nil?
|
|
21
|
+
end
|
|
22
|
+
aq
|
|
23
|
+
end
|
|
24
|
+
PK
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
if defined?(Rake.application) && Rake.application && ENV["SKIP_AR_JDBC_RAKE_REDEFINES"].nil?
|
|
2
|
+
jdbc_rakefile = File.dirname(__FILE__) + "/jdbc.rake"
|
|
3
|
+
if Rake.application.lookup("db:create")
|
|
4
|
+
# rails tasks already defined; load the override tasks now
|
|
5
|
+
load jdbc_rakefile
|
|
6
|
+
else
|
|
7
|
+
# rails tasks not loaded yet; load as an import
|
|
8
|
+
Rake.application.add_import(jdbc_rakefile)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Kernel
|
|
2
|
+
# load a JDBC driver library/gem, failing silently. If failed, trust
|
|
3
|
+
# that the driver jar is already present through some other means
|
|
4
|
+
def jdbc_require_driver(path, gem_name = nil)
|
|
5
|
+
gem_name ||= path.sub('/', '-')
|
|
6
|
+
2.times do
|
|
7
|
+
begin
|
|
8
|
+
require path
|
|
9
|
+
break
|
|
10
|
+
rescue LoadError
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
begin; gem gem_name; rescue LoadError; end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
# I want to use JDBC's DatabaseMetaData#getTypeInfo to choose the best native types to
|
|
4
|
+
# use for ActiveRecord's Adapter#native_database_types in a database-independent way,
|
|
5
|
+
# but apparently a database driver can return multiple types for a given
|
|
6
|
+
# java.sql.Types constant. So this type converter uses some heuristics to try to pick
|
|
7
|
+
# the best (most common) type to use. It's not great, it would be better to just
|
|
8
|
+
# delegate to each database's existin AR adapter's native_database_types method, but I
|
|
9
|
+
# wanted to try to do this in a way that didn't pull in all the other adapters as
|
|
10
|
+
# dependencies. Suggestions appreciated.
|
|
11
|
+
class JdbcTypeConverter
|
|
12
|
+
# The basic ActiveRecord types, mapped to an array of procs that are used to #select
|
|
13
|
+
# the best type. The procs are used as selectors in order until there is only one
|
|
14
|
+
# type left. If all the selectors are applied and there is still more than one
|
|
15
|
+
# type, an exception will be raised.
|
|
16
|
+
AR_TO_JDBC_TYPES = {
|
|
17
|
+
:string => [ lambda {|r| Jdbc::Types::VARCHAR == r['data_type'].to_i},
|
|
18
|
+
lambda {|r| r['type_name'] =~ /^varchar/i},
|
|
19
|
+
lambda {|r| r['type_name'] =~ /^varchar$/i},
|
|
20
|
+
lambda {|r| r['type_name'] =~ /varying/i}],
|
|
21
|
+
:text => [ lambda {|r| [Jdbc::Types::LONGVARCHAR, Jdbc::Types::CLOB].include?(r['data_type'].to_i)},
|
|
22
|
+
lambda {|r| r['type_name'] =~ /^text$/i}, # For Informix
|
|
23
|
+
lambda {|r| r['type_name'] =~ /sub_type 1$/i}, # For FireBird
|
|
24
|
+
lambda {|r| r['type_name'] =~ /^(text|clob)$/i},
|
|
25
|
+
lambda {|r| r['type_name'] =~ /^character large object$/i},
|
|
26
|
+
lambda {|r| r['sql_data_type'] == 2005}],
|
|
27
|
+
:integer => [ lambda {|r| Jdbc::Types::INTEGER == r['data_type'].to_i},
|
|
28
|
+
lambda {|r| r['type_name'] =~ /^integer$/i},
|
|
29
|
+
lambda {|r| r['type_name'] =~ /^int4$/i},
|
|
30
|
+
lambda {|r| r['type_name'] =~ /^int$/i}],
|
|
31
|
+
:decimal => [ lambda {|r| Jdbc::Types::DECIMAL == r['data_type'].to_i},
|
|
32
|
+
lambda {|r| r['type_name'] =~ /^decimal$/i},
|
|
33
|
+
lambda {|r| r['type_name'] =~ /^numeric$/i},
|
|
34
|
+
lambda {|r| r['type_name'] =~ /^number$/i},
|
|
35
|
+
lambda {|r| r['type_name'] =~ /^real$/i},
|
|
36
|
+
lambda {|r| r['precision'] == '38'},
|
|
37
|
+
lambda {|r| r['data_type'].to_i == Jdbc::Types::DECIMAL}],
|
|
38
|
+
:float => [ lambda {|r| [Jdbc::Types::FLOAT,Jdbc::Types::DOUBLE, Jdbc::Types::REAL].include?(r['data_type'].to_i)},
|
|
39
|
+
lambda {|r| r['data_type'].to_i == Jdbc::Types::REAL}, #Prefer REAL to DOUBLE for Postgresql
|
|
40
|
+
lambda {|r| r['type_name'] =~ /^float/i},
|
|
41
|
+
lambda {|r| r['type_name'] =~ /^double$/i},
|
|
42
|
+
lambda {|r| r['type_name'] =~ /^real$/i},
|
|
43
|
+
lambda {|r| r['precision'] == '15'}],
|
|
44
|
+
:datetime => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type'].to_i},
|
|
45
|
+
lambda {|r| r['type_name'] =~ /^datetime$/i},
|
|
46
|
+
lambda {|r| r['type_name'] =~ /^timestamp$/i},
|
|
47
|
+
lambda {|r| r['type_name'] =~ /^date/i},
|
|
48
|
+
lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
|
|
49
|
+
:timestamp => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type'].to_i},
|
|
50
|
+
lambda {|r| r['type_name'] =~ /^timestamp$/i},
|
|
51
|
+
lambda {|r| r['type_name'] =~ /^datetime/i},
|
|
52
|
+
lambda {|r| r['type_name'] =~ /^date/i},
|
|
53
|
+
lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
|
|
54
|
+
:time => [ lambda {|r| Jdbc::Types::TIME == r['data_type'].to_i},
|
|
55
|
+
lambda {|r| r['type_name'] =~ /^time$/i},
|
|
56
|
+
lambda {|r| r['type_name'] =~ /^datetime/i}, # For Informix
|
|
57
|
+
lambda {|r| r['type_name'] =~ /^date/i},
|
|
58
|
+
lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
|
|
59
|
+
:date => [ lambda {|r| Jdbc::Types::DATE == r['data_type'].to_i},
|
|
60
|
+
lambda {|r| r['type_name'] =~ /^date$/i},
|
|
61
|
+
lambda {|r| r['type_name'] =~ /^date/i},
|
|
62
|
+
lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver3
|
|
63
|
+
:binary => [ lambda {|r| [Jdbc::Types::LONGVARBINARY,Jdbc::Types::BINARY,Jdbc::Types::BLOB].include?(r['data_type'].to_i)},
|
|
64
|
+
lambda {|r| r['type_name'] =~ /^blob/i},
|
|
65
|
+
lambda {|r| r['type_name'] =~ /sub_type 0$/i}, # For FireBird
|
|
66
|
+
lambda {|r| r['type_name'] =~ /^varbinary$/i}, # We want this sucker for Mimer
|
|
67
|
+
lambda {|r| r['type_name'] =~ /^binary$/i}, ],
|
|
68
|
+
:boolean => [ lambda {|r| [Jdbc::Types::TINYINT].include?(r['data_type'].to_i)},
|
|
69
|
+
lambda {|r| r['type_name'] =~ /^bool/i},
|
|
70
|
+
lambda {|r| r['data_type'].to_i == Jdbc::Types::BIT},
|
|
71
|
+
lambda {|r| r['type_name'] =~ /^tinyint$/i},
|
|
72
|
+
lambda {|r| r['type_name'] =~ /^decimal$/i},
|
|
73
|
+
lambda {|r| r['type_name'] =~ /^integer$/i}]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def initialize(types)
|
|
77
|
+
@types = types
|
|
78
|
+
@types.each {|t| t['type_name'] ||= t['local_type_name']} # Sybase driver seems to want 'local_type_name'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def choose_best_types
|
|
82
|
+
type_map = {}
|
|
83
|
+
@types.each do |row|
|
|
84
|
+
name = row['type_name'].downcase
|
|
85
|
+
k = name.to_sym
|
|
86
|
+
type_map[k] = { :name => name }
|
|
87
|
+
set_limit_to_nonzero_precision(type_map[k], row)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
AR_TO_JDBC_TYPES.keys.each do |k|
|
|
91
|
+
typerow = choose_type(k)
|
|
92
|
+
type_map[k] = { :name => typerow['type_name'].downcase }
|
|
93
|
+
case k
|
|
94
|
+
when :integer, :string, :decimal
|
|
95
|
+
set_limit_to_nonzero_precision(type_map[k], typerow)
|
|
96
|
+
when :boolean
|
|
97
|
+
type_map[k][:limit] = 1
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
type_map
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def choose_type(ar_type)
|
|
104
|
+
procs = AR_TO_JDBC_TYPES[ar_type]
|
|
105
|
+
types = @types
|
|
106
|
+
procs.each do |p|
|
|
107
|
+
new_types = types.reject {|r| r["data_type"].to_i == Jdbc::Types::OTHER}
|
|
108
|
+
new_types = new_types.select(&p)
|
|
109
|
+
new_types = new_types.inject([]) do |typs,t|
|
|
110
|
+
typs << t unless typs.detect {|el| el['type_name'] == t['type_name']}
|
|
111
|
+
typs
|
|
112
|
+
end
|
|
113
|
+
return new_types.first if new_types.length == 1
|
|
114
|
+
types = new_types if new_types.length > 0
|
|
115
|
+
end
|
|
116
|
+
raise "unable to choose type for #{ar_type} from:\n#{types.collect{|t| t['type_name']}.inspect}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def set_limit_to_nonzero_precision(map, row)
|
|
120
|
+
if row['precision'] && row['precision'].to_i > 0
|
|
121
|
+
map[:limit] = row['precision'].to_i
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/arjdbc/mimer.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module ArJdbc
|
|
2
|
+
module Mimer
|
|
3
|
+
def self.extended(mod)
|
|
4
|
+
require 'arjdbc/jdbc/quoted_primary_key'
|
|
5
|
+
ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def modify_types(tp)
|
|
9
|
+
tp[:primary_key] = "INTEGER NOT NULL PRIMARY KEY"
|
|
10
|
+
tp[:boolean][:limit] = nil
|
|
11
|
+
tp[:string][:limit] = 255
|
|
12
|
+
tp[:binary] = {:name => "BINARY VARYING", :limit => 4096}
|
|
13
|
+
tp[:text] = {:name => "VARCHAR", :limit => 4096}
|
|
14
|
+
tp[:datetime] = { :name => "TIMESTAMP" }
|
|
15
|
+
tp[:timestamp] = { :name => "TIMESTAMP" }
|
|
16
|
+
tp[:time] = { :name => "TIMESTAMP" }
|
|
17
|
+
tp[:date] = { :name => "TIMESTAMP" }
|
|
18
|
+
tp
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default_sequence_name(table, column) #:nodoc:
|
|
22
|
+
"#{table}_seq"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_table(name, options = {}) #:nodoc:
|
|
26
|
+
super(name, options)
|
|
27
|
+
execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def drop_table(name, options = {}) #:nodoc:
|
|
31
|
+
super(name) rescue nil
|
|
32
|
+
execute "DROP SEQUENCE #{name}_seq" rescue nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
36
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
|
40
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
|
44
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) #:nodoc:
|
|
48
|
+
if pk.nil? # Who called us? What does the sql look like? No idea!
|
|
49
|
+
execute sql, name, binds
|
|
50
|
+
elsif id_value # Pre-assigned id
|
|
51
|
+
log(sql, name) { @connection.execute_insert sql,pk }
|
|
52
|
+
else # Assume the sql contains a bind-variable for the id
|
|
53
|
+
id_value = select_one("SELECT NEXT_VALUE OF #{sequence_name} AS val FROM MIMER.ONEROW")['val']
|
|
54
|
+
log(sql, name) {
|
|
55
|
+
execute_prepared_insert(sql,id_value)
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
id_value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def execute_prepared_insert(sql, id)
|
|
62
|
+
@stmts ||= {}
|
|
63
|
+
@stmts[sql] ||= @connection.ps(sql)
|
|
64
|
+
stmt = @stmts[sql]
|
|
65
|
+
stmt.setLong(1,id)
|
|
66
|
+
stmt.executeUpdate
|
|
67
|
+
id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def quote(value, column = nil) #:nodoc:
|
|
71
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
72
|
+
|
|
73
|
+
if String === value && column && column.type == :binary
|
|
74
|
+
return "X'#{quote_string(value.unpack("C*").collect {|v| v.to_s(16)}.join)}'"
|
|
75
|
+
end
|
|
76
|
+
case value
|
|
77
|
+
when String
|
|
78
|
+
%Q{'#{quote_string(value)}'}
|
|
79
|
+
when NilClass
|
|
80
|
+
'NULL'
|
|
81
|
+
when TrueClass
|
|
82
|
+
'1'
|
|
83
|
+
when FalseClass
|
|
84
|
+
'0'
|
|
85
|
+
when Numeric
|
|
86
|
+
value.to_s
|
|
87
|
+
when Date, Time
|
|
88
|
+
%Q{TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
|
|
89
|
+
else
|
|
90
|
+
%Q{'#{quote_string(value.to_yaml)}'}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def quoted_true
|
|
95
|
+
'1'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def quoted_false
|
|
99
|
+
'0'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def add_limit_offset!(sql, options) # :nodoc:
|
|
103
|
+
@limit = options[:limit]
|
|
104
|
+
@offset = options[:offset]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def select_all(sql, name = nil, binds = [])
|
|
108
|
+
@offset ||= 0
|
|
109
|
+
if !@limit || @limit == -1
|
|
110
|
+
range = @offset..-1
|
|
111
|
+
else
|
|
112
|
+
range = @offset...(@offset+@limit)
|
|
113
|
+
end
|
|
114
|
+
select(sql, name, binds)[range]
|
|
115
|
+
ensure
|
|
116
|
+
@limit = @offset = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def select_one(sql, name = nil)
|
|
120
|
+
@offset ||= 0
|
|
121
|
+
select(sql, name)[@offset]
|
|
122
|
+
ensure
|
|
123
|
+
@limit = @offset = nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def _execute(sql, name = nil)
|
|
127
|
+
if sql =~ /^select/i
|
|
128
|
+
@offset ||= 0
|
|
129
|
+
if !@limit || @limit == -1
|
|
130
|
+
range = @offset..-1
|
|
131
|
+
else
|
|
132
|
+
range = @offset...(@offset+@limit)
|
|
133
|
+
end
|
|
134
|
+
@connection.execute_query(sql)[range]
|
|
135
|
+
else
|
|
136
|
+
@connection.execute_update(sql)
|
|
137
|
+
end
|
|
138
|
+
ensure
|
|
139
|
+
@limit = @offset = nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/arjdbc/mssql.rb
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
require 'arjdbc/mssql/tsql_helper'
|
|
2
|
+
require 'arjdbc/mssql/limit_helpers'
|
|
3
|
+
require 'arjdbc/mssql/lock_helpers'
|
|
4
|
+
require 'strscan'
|
|
5
|
+
|
|
6
|
+
module ::ArJdbc
|
|
7
|
+
module MsSQL
|
|
8
|
+
include TSqlMethods
|
|
9
|
+
include LimitHelpers
|
|
10
|
+
|
|
11
|
+
def self.extended(mod)
|
|
12
|
+
unless defined?(@lob_callback_added)
|
|
13
|
+
ActiveRecord::Base.class_eval do
|
|
14
|
+
def after_save_with_mssql_lob
|
|
15
|
+
self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
|
|
16
|
+
value = self[c.name]
|
|
17
|
+
if coder = self.class.serialized_attributes[c.name]
|
|
18
|
+
if coder.respond_to?(:dump)
|
|
19
|
+
value = coder.dump(value)
|
|
20
|
+
else
|
|
21
|
+
value = value.to_yaml
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
next if value.nil? || (value == '')
|
|
25
|
+
|
|
26
|
+
connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ActiveRecord::Base.after_save :after_save_with_mssql_lob
|
|
32
|
+
@lob_callback_added = true
|
|
33
|
+
end
|
|
34
|
+
mod.add_version_specific_add_limit_offset
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.column_selector
|
|
38
|
+
[/sqlserver|tds|Microsoft SQL/i, lambda {|cfg,col| col.extend(::ArJdbc::MsSQL::Column)}]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.jdbc_connection_class
|
|
42
|
+
::ActiveRecord::ConnectionAdapters::MssqlJdbcConnection
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.arel2_visitors(config)
|
|
46
|
+
require 'arel/visitors/sql_server'
|
|
47
|
+
visitor_class = config[:sqlserver_version] == "2000" ? ::Arel::Visitors::SQLServer2000 : ::Arel::Visitors::SQLServer
|
|
48
|
+
{}.tap {|v| %w(mssql sqlserver jdbcmssql).each {|x| v[x] = visitor_class } }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def sqlserver_version
|
|
52
|
+
@sqlserver_version ||= select_value("select @@version")[/Microsoft SQL Server\s+(\d{4})/, 1]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def add_version_specific_add_limit_offset
|
|
56
|
+
config[:sqlserver_version] = version = sqlserver_version
|
|
57
|
+
if version == "2000"
|
|
58
|
+
extend LimitHelpers::SqlServer2000AddLimitOffset
|
|
59
|
+
else
|
|
60
|
+
extend LimitHelpers::SqlServerAddLimitOffset
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def modify_types(tp) #:nodoc:
|
|
65
|
+
super(tp)
|
|
66
|
+
tp[:string] = {:name => "NVARCHAR", :limit => 255}
|
|
67
|
+
if sqlserver_version == "2000"
|
|
68
|
+
tp[:text] = {:name => "NTEXT"}
|
|
69
|
+
else
|
|
70
|
+
tp[:text] = {:name => "NVARCHAR(MAX)"}
|
|
71
|
+
end
|
|
72
|
+
tp
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
|
76
|
+
# MSSQL's NVARCHAR(n | max) column supports either a number between 1 and
|
|
77
|
+
# 4000, or the word "MAX", which corresponds to 2**30-1 UCS-2 characters.
|
|
78
|
+
#
|
|
79
|
+
# It does not accept NVARCHAR(1073741823) here, so we have to change it
|
|
80
|
+
# to NVARCHAR(MAX), even though they are logically equivalent.
|
|
81
|
+
#
|
|
82
|
+
# MSSQL Server 2000 is skipped here because I don't know how it will behave.
|
|
83
|
+
#
|
|
84
|
+
# See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
|
|
85
|
+
if type.to_s == 'string' and limit == 1073741823 and sqlserver_version != "2000"
|
|
86
|
+
'NVARCHAR(MAX)'
|
|
87
|
+
elsif %w( boolean date datetime ).include?(type.to_s)
|
|
88
|
+
super(type) # cannot specify limit/precision/scale with these types
|
|
89
|
+
else
|
|
90
|
+
super
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
module Column
|
|
95
|
+
include LockHelpers::SqlServerAddLock
|
|
96
|
+
|
|
97
|
+
attr_accessor :identity, :is_special
|
|
98
|
+
|
|
99
|
+
def simplified_type(field_type)
|
|
100
|
+
case field_type
|
|
101
|
+
when /int|bigint|smallint|tinyint/i then :integer
|
|
102
|
+
when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
|
|
103
|
+
when /float|double|money|real|smallmoney/i then :decimal
|
|
104
|
+
when /datetime|smalldatetime/i then :datetime
|
|
105
|
+
when /timestamp/i then :timestamp
|
|
106
|
+
when /time/i then :time
|
|
107
|
+
when /date/i then :date
|
|
108
|
+
when /text|ntext|xml/i then :text
|
|
109
|
+
when /binary|image|varbinary/i then :binary
|
|
110
|
+
when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
|
|
111
|
+
when /bit/i then :boolean
|
|
112
|
+
when /uniqueidentifier/i then :string
|
|
113
|
+
else
|
|
114
|
+
super
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def default_value(value)
|
|
119
|
+
return $1 if value =~ /^\(N?'(.*)'\)$/
|
|
120
|
+
value
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def type_cast(value)
|
|
124
|
+
return nil if value.nil?
|
|
125
|
+
case type
|
|
126
|
+
when :integer then value.delete('()').to_i rescue unquote(value).to_i rescue value ? 1 : 0
|
|
127
|
+
when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
|
|
128
|
+
when :decimal then self.class.value_to_decimal(unquote(value))
|
|
129
|
+
when :datetime then cast_to_datetime(value)
|
|
130
|
+
when :timestamp then cast_to_time(value)
|
|
131
|
+
when :time then cast_to_time(value)
|
|
132
|
+
when :date then cast_to_date(value)
|
|
133
|
+
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or unquote(value)=="1"
|
|
134
|
+
when :binary then unquote value
|
|
135
|
+
else value
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def extract_limit(sql_type)
|
|
140
|
+
case sql_type
|
|
141
|
+
when /text|ntext|xml|binary|image|varbinary|bit/
|
|
142
|
+
nil
|
|
143
|
+
else
|
|
144
|
+
super
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def is_utf8?
|
|
149
|
+
sql_type =~ /nvarchar|ntext|nchar/i
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def unquote(value)
|
|
153
|
+
value.to_s.sub(/\A\([\(\']?/, "").sub(/[\'\)]?\)\Z/, "")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def cast_to_time(value)
|
|
157
|
+
return value if value.is_a?(Time)
|
|
158
|
+
DateTime.parse(value).to_time rescue nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def cast_to_date(value)
|
|
162
|
+
return value if value.is_a?(Date)
|
|
163
|
+
return Date.parse(value) rescue nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def cast_to_datetime(value)
|
|
167
|
+
if value.is_a?(Time)
|
|
168
|
+
if value.year != 0 and value.month != 0 and value.day != 0
|
|
169
|
+
return value
|
|
170
|
+
else
|
|
171
|
+
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
if value.is_a?(DateTime)
|
|
175
|
+
begin
|
|
176
|
+
# Attempt to convert back to a Time, but it could fail for dates significantly in the past/future.
|
|
177
|
+
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
|
178
|
+
rescue ArgumentError
|
|
179
|
+
return value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
|
|
184
|
+
|
|
185
|
+
return value.is_a?(Date) ? value : nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
|
189
|
+
# because of a SQL Server statement length policy.
|
|
190
|
+
def self.string_to_binary(value)
|
|
191
|
+
''
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def quote(value, column = nil)
|
|
197
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
198
|
+
|
|
199
|
+
case value
|
|
200
|
+
# SQL Server 2000 doesn't let you insert an integer into a NVARCHAR
|
|
201
|
+
# column, so we include Integer here.
|
|
202
|
+
when String, ActiveSupport::Multibyte::Chars, Integer
|
|
203
|
+
value = value.to_s
|
|
204
|
+
if column && column.type == :binary
|
|
205
|
+
"'#{quote_string(ArJdbc::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
|
|
206
|
+
elsif column && [:integer, :float].include?(column.type)
|
|
207
|
+
value = column.type == :integer ? value.to_i : value.to_f
|
|
208
|
+
value.to_s
|
|
209
|
+
elsif !column.respond_to?(:is_utf8?) || column.is_utf8?
|
|
210
|
+
"N'#{quote_string(value)}'" # ' (for ruby-mode)
|
|
211
|
+
else
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
when TrueClass then '1'
|
|
215
|
+
when FalseClass then '0'
|
|
216
|
+
else super
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def quote_string(string)
|
|
221
|
+
string.gsub(/\'/, "''")
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def quote_table_name(name)
|
|
225
|
+
quote_column_name(name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def quote_column_name(name)
|
|
229
|
+
"[#{name}]"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def quoted_true
|
|
233
|
+
quote true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def quoted_false
|
|
237
|
+
quote false
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def adapter_name #:nodoc:
|
|
241
|
+
'MsSQL'
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def change_order_direction(order)
|
|
245
|
+
order.split(",").collect do |fragment|
|
|
246
|
+
case fragment
|
|
247
|
+
when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
|
|
248
|
+
when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
|
|
249
|
+
else String.new(fragment).split(',').join(' DESC,') + ' DESC'
|
|
250
|
+
end
|
|
251
|
+
end.join(",")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def supports_ddl_transactions?
|
|
255
|
+
true
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def recreate_database(name)
|
|
259
|
+
drop_database(name)
|
|
260
|
+
create_database(name)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def drop_database(name)
|
|
264
|
+
execute "USE master"
|
|
265
|
+
execute "DROP DATABASE #{name}"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def create_database(name)
|
|
269
|
+
execute "CREATE DATABASE #{name}"
|
|
270
|
+
execute "USE #{name}"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def rename_table(name, new_name)
|
|
274
|
+
clear_cached_table(name)
|
|
275
|
+
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Adds a new column to the named table.
|
|
279
|
+
# See TableDefinition#column for details of the options you can use.
|
|
280
|
+
def add_column(table_name, column_name, type, options = {})
|
|
281
|
+
clear_cached_table(table_name)
|
|
282
|
+
add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
283
|
+
add_column_options!(add_column_sql, options)
|
|
284
|
+
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
|
|
285
|
+
# add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
|
|
286
|
+
execute(add_column_sql)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def rename_column(table, column, new_column_name)
|
|
290
|
+
clear_cached_table(table)
|
|
291
|
+
execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
295
|
+
clear_cached_table(table_name)
|
|
296
|
+
change_column_type(table_name, column_name, type, options)
|
|
297
|
+
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def change_column_type(table_name, column_name, type, options = {}) #:nodoc:
|
|
301
|
+
clear_cached_table(table_name)
|
|
302
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
303
|
+
if options.has_key?(:null)
|
|
304
|
+
sql += (options[:null] ? " NULL" : " NOT NULL")
|
|
305
|
+
end
|
|
306
|
+
execute(sql)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
|
310
|
+
clear_cached_table(table_name)
|
|
311
|
+
remove_default_constraint(table_name, column_name)
|
|
312
|
+
unless default.nil?
|
|
313
|
+
execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def remove_column(table_name, column_name)
|
|
318
|
+
clear_cached_table(table_name)
|
|
319
|
+
remove_check_constraints(table_name, column_name)
|
|
320
|
+
remove_default_constraint(table_name, column_name)
|
|
321
|
+
execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def remove_default_constraint(table_name, column_name)
|
|
325
|
+
clear_cached_table(table_name)
|
|
326
|
+
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"
|
|
327
|
+
defaults.each {|constraint|
|
|
328
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
|
|
329
|
+
}
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def remove_check_constraints(table_name, column_name)
|
|
333
|
+
clear_cached_table(table_name)
|
|
334
|
+
# TODO remove all constraints in single method
|
|
335
|
+
constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
|
|
336
|
+
constraints.each do |constraint|
|
|
337
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def remove_index(table_name, options = {})
|
|
342
|
+
execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def columns(table_name, name = nil)
|
|
346
|
+
# It's possible for table_name to be an empty string, or nil, if something attempts to issue SQL
|
|
347
|
+
# which doesn't involve a table. IE. "SELECT 1" or "SELECT * from someFunction()".
|
|
348
|
+
return [] if table_name.blank?
|
|
349
|
+
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
|
350
|
+
|
|
351
|
+
# Remove []'s from around the table name, valid in a select statement, but not when matching metadata.
|
|
352
|
+
table_name = table_name.gsub(/[\[\]]/, '')
|
|
353
|
+
|
|
354
|
+
return [] if table_name =~ /^information_schema\./i
|
|
355
|
+
@table_columns ||= {}
|
|
356
|
+
unless @table_columns[table_name]
|
|
357
|
+
@table_columns[table_name] = super
|
|
358
|
+
@table_columns[table_name].each do |col|
|
|
359
|
+
col.identity = true if col.sql_type =~ /identity/i
|
|
360
|
+
col.is_special = true if col.sql_type =~ /text|ntext|image|xml/i
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
@table_columns[table_name]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def _execute(sql, name = nil)
|
|
367
|
+
# Match the start of the sql to determine appropriate behaviour. Be aware of
|
|
368
|
+
# multi-line sql which might begin with 'create stored_proc' and contain 'insert into ...' lines.
|
|
369
|
+
# Possible improvements include ignoring comment blocks prior to the first statement.
|
|
370
|
+
if sql.lstrip =~ /\Ainsert/i
|
|
371
|
+
if query_requires_identity_insert?(sql)
|
|
372
|
+
table_name = get_table_name(sql)
|
|
373
|
+
with_identity_insert_enabled(table_name) do
|
|
374
|
+
id = @connection.execute_insert(sql)
|
|
375
|
+
end
|
|
376
|
+
else
|
|
377
|
+
@connection.execute_insert(sql)
|
|
378
|
+
end
|
|
379
|
+
elsif sql.lstrip =~ /\A(create|exec)/i
|
|
380
|
+
@connection.execute_update(sql)
|
|
381
|
+
elsif sql.lstrip =~ /\A\(?\s*(select|show)/i
|
|
382
|
+
repair_special_columns(sql)
|
|
383
|
+
@connection.execute_query(sql)
|
|
384
|
+
else
|
|
385
|
+
@connection.execute_update(sql)
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def select(sql, name = nil, binds = [])
|
|
390
|
+
sql = substitute_binds(sql, binds)
|
|
391
|
+
log(sql, name) do
|
|
392
|
+
@connection.execute_query(sql)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Turns IDENTITY_INSERT ON for table during execution of the block
|
|
397
|
+
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
|
398
|
+
# block has been executed without regard to its previous state
|
|
399
|
+
def with_identity_insert_enabled(table_name, &block)
|
|
400
|
+
set_identity_insert(table_name, true)
|
|
401
|
+
yield
|
|
402
|
+
ensure
|
|
403
|
+
set_identity_insert(table_name, false)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def set_identity_insert(table_name, enable = true)
|
|
407
|
+
execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
|
408
|
+
rescue Exception => e
|
|
409
|
+
raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def identity_column(table_name)
|
|
413
|
+
columns(table_name).each do |col|
|
|
414
|
+
return col.name if col.identity
|
|
415
|
+
end
|
|
416
|
+
return nil
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def query_requires_identity_insert?(sql)
|
|
420
|
+
table_name = get_table_name(sql)
|
|
421
|
+
id_column = identity_column(table_name)
|
|
422
|
+
if sql.strip =~ /insert into [^ ]+ ?\((.+?)\)/i
|
|
423
|
+
insert_columns = $1.split(/, */).map(&method(:unquote_column_name))
|
|
424
|
+
return table_name if insert_columns.include?(id_column)
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def unquote_column_name(name)
|
|
429
|
+
if name =~ /^\[.*\]$/
|
|
430
|
+
name[1..-2]
|
|
431
|
+
else
|
|
432
|
+
name
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def get_special_columns(table_name)
|
|
437
|
+
special = []
|
|
438
|
+
columns(table_name).each do |col|
|
|
439
|
+
special << col.name if col.is_special
|
|
440
|
+
end
|
|
441
|
+
special
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def repair_special_columns(sql)
|
|
445
|
+
special_cols = get_special_columns(get_table_name(sql))
|
|
446
|
+
for col in special_cols.to_a
|
|
447
|
+
sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
|
|
448
|
+
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
|
|
449
|
+
end
|
|
450
|
+
sql
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def determine_order_clause(sql)
|
|
454
|
+
return $1 if sql =~ /ORDER BY (.*)$/
|
|
455
|
+
table_name = get_table_name(sql)
|
|
456
|
+
"#{table_name}.#{determine_primary_key(table_name)}"
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def determine_primary_key(table_name)
|
|
460
|
+
primary_key = columns(table_name).detect { |column| column.primary || column.identity }
|
|
461
|
+
return primary_key.name if primary_key
|
|
462
|
+
# Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
|
|
463
|
+
columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
|
|
464
|
+
# Give up and provide something which is going to crash almost certainly
|
|
465
|
+
columns(table_name)[0].name
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def clear_cached_table(name)
|
|
469
|
+
(@table_columns ||= {}).delete(name.to_s)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def reset_column_information
|
|
473
|
+
@table_columns = nil
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|