activerecord-jdbc-alt-adapter 52.4.0-java → 61.0.0-java

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.nvimlog +0 -0
  4. data/.travis.yml +63 -39
  5. data/Gemfile +11 -4
  6. data/README.md +55 -35
  7. data/Rakefile +1 -1
  8. data/Rakefile.jdbc +8 -1
  9. data/activerecord-jdbc-adapter.gemspec +6 -9
  10. data/activerecord-jdbc-alt-adapter.gemspec +9 -12
  11. data/lib/arel/visitors/postgresql_jdbc.rb +1 -1
  12. data/lib/arel/visitors/sqlserver.rb +49 -23
  13. data/lib/arjdbc/abstract/connection_management.rb +7 -0
  14. data/lib/arjdbc/abstract/core.rb +17 -23
  15. data/lib/arjdbc/abstract/database_statements.rb +30 -2
  16. data/lib/arjdbc/abstract/statement_cache.rb +2 -5
  17. data/lib/arjdbc/abstract/transaction_support.rb +22 -7
  18. data/lib/arjdbc/db2/column.rb +0 -39
  19. data/lib/arjdbc/derby/adapter.rb +1 -20
  20. data/lib/arjdbc/firebird/adapter.rb +0 -21
  21. data/lib/arjdbc/h2/adapter.rb +0 -15
  22. data/lib/arjdbc/hsqldb/adapter.rb +0 -14
  23. data/lib/arjdbc/informix/adapter.rb +0 -23
  24. data/lib/arjdbc/jdbc/adapter.rb +3 -1
  25. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  26. data/lib/arjdbc/jdbc/base_ext.rb +3 -1
  27. data/lib/arjdbc/jdbc/callbacks.rb +2 -0
  28. data/lib/arjdbc/jdbc/column.rb +2 -0
  29. data/lib/arjdbc/jdbc/connection.rb +2 -0
  30. data/lib/arjdbc/jdbc/connection_methods.rb +2 -0
  31. data/lib/arjdbc/jdbc/error.rb +2 -0
  32. data/lib/arjdbc/jdbc/extension.rb +2 -0
  33. data/lib/arjdbc/jdbc/java.rb +3 -1
  34. data/lib/arjdbc/jdbc/railtie.rb +3 -1
  35. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -1
  36. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -1
  37. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  38. data/lib/arjdbc/jdbc/type_converter.rb +2 -0
  39. data/lib/arjdbc/mssql.rb +3 -1
  40. data/lib/arjdbc/mssql/adapter.rb +114 -36
  41. data/lib/arjdbc/mssql/column.rb +19 -1
  42. data/lib/arjdbc/mssql/connection_methods.rb +10 -2
  43. data/lib/arjdbc/mssql/database_limits.rb +9 -0
  44. data/lib/arjdbc/mssql/database_statements.rb +44 -6
  45. data/lib/arjdbc/mssql/errors.rb +2 -0
  46. data/lib/arjdbc/mssql/explain_support.rb +3 -1
  47. data/lib/arjdbc/mssql/extensions/attribute_methods.rb +6 -2
  48. data/lib/arjdbc/mssql/extensions/calculations.rb +2 -0
  49. data/lib/arjdbc/mssql/quoting.rb +38 -0
  50. data/lib/arjdbc/mssql/schema_creation.rb +25 -3
  51. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  52. data/lib/arjdbc/mssql/schema_dumper.rb +2 -0
  53. data/lib/arjdbc/mssql/schema_statements.rb +92 -22
  54. data/lib/arjdbc/mssql/transaction.rb +2 -0
  55. data/lib/arjdbc/mssql/types.rb +2 -0
  56. data/lib/arjdbc/mssql/types/binary_types.rb +2 -0
  57. data/lib/arjdbc/mssql/types/date_and_time_types.rb +2 -0
  58. data/lib/arjdbc/mssql/types/deprecated_types.rb +2 -0
  59. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -0
  60. data/lib/arjdbc/mssql/types/string_types.rb +2 -0
  61. data/lib/arjdbc/mssql/utils.rb +2 -0
  62. data/lib/arjdbc/mysql/adapter.rb +59 -21
  63. data/lib/arjdbc/mysql/connection_methods.rb +6 -1
  64. data/lib/arjdbc/postgresql/adapter.rb +257 -219
  65. data/lib/arjdbc/postgresql/base/array_decoder.rb +2 -0
  66. data/lib/arjdbc/postgresql/base/array_encoder.rb +4 -2
  67. data/lib/arjdbc/postgresql/base/array_parser.rb +4 -2
  68. data/lib/arjdbc/postgresql/base/pgconn.rb +2 -0
  69. data/lib/arjdbc/postgresql/column.rb +6 -4
  70. data/lib/arjdbc/postgresql/connection_methods.rb +1 -0
  71. data/lib/arjdbc/postgresql/name.rb +2 -0
  72. data/lib/arjdbc/postgresql/oid_types.rb +7 -4
  73. data/lib/arjdbc/sqlite3/adapter.rb +266 -221
  74. data/lib/arjdbc/sqlite3/connection_methods.rb +26 -4
  75. data/lib/arjdbc/tasks/databases.rake +21 -13
  76. data/lib/arjdbc/tasks/mssql_database_tasks.rb +126 -25
  77. data/lib/arjdbc/util/quoted_cache.rb +3 -1
  78. data/lib/arjdbc/util/serialized_attributes.rb +3 -1
  79. data/lib/arjdbc/util/table_copier.rb +3 -1
  80. data/lib/arjdbc/version.rb +3 -1
  81. data/pom.xml +4 -4
  82. data/rakelib/01-tomcat.rake +2 -2
  83. data/rakelib/rails.rake +1 -1
  84. data/src/java/arjdbc/ArJdbcModule.java +5 -5
  85. data/src/java/arjdbc/jdbc/DriverWrapper.java +1 -9
  86. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +549 -691
  87. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +88 -0
  88. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +13 -23
  89. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +125 -53
  90. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +97 -103
  91. data/src/java/arjdbc/util/DateTimeUtils.java +12 -4
  92. metadata +10 -18
@@ -1,11 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  # MSSQL specific extensions to column definitions in a table.
4
6
  class MSSQLColumn < Column
7
+ attr_reader :table_name
8
+
5
9
  def initialize(name, raw_default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
10
+ @table_name = table_name
11
+
6
12
  default = extract_default(raw_default)
7
13
 
8
- super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
14
+ super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
9
15
  end
10
16
 
11
17
  def extract_default(value)
@@ -24,6 +30,18 @@ module ActiveRecord
24
30
  sql_type.downcase.include? 'identity'
25
31
  end
26
32
 
33
+ def ==(other)
34
+ other.is_a?(MSSQLColumn) &&
35
+ super &&
36
+ table_name == other.table_name
37
+ end
38
+ alias :eql? :==
39
+
40
+ def hash
41
+ MSSQLColumn.hash ^
42
+ super.hash ^
43
+ table_name.hash
44
+ end
27
45
  end
28
46
  end
29
47
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ArJdbc::ConnectionMethods.module_eval do
2
4
 
3
5
  # Default connection method for MS-SQL adapter (`adapter: mssql`),
@@ -11,6 +13,8 @@ ArJdbc::ConnectionMethods.module_eval do
11
13
  return sqlserver_connection(config)
12
14
  end
13
15
 
16
+ config = config.deep_dup
17
+
14
18
  config[:adapter_spec] ||= ::ArJdbc::MSSQL
15
19
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
16
20
 
@@ -31,7 +35,8 @@ ArJdbc::ConnectionMethods.module_eval do
31
35
  config[:connection_alive_sql] ||= 'SELECT 1'
32
36
 
33
37
  config[:url] ||= begin
34
- url = "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
38
+ url = ''.dup
39
+ url << "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
35
40
  # Instance is often a preferrable alternative to port when dynamic ports are used.
36
41
  # If instance is specified then port is essentially ignored.
37
42
  url << ";instance=#{config[:instance]}" if config[:instance]
@@ -52,6 +57,8 @@ ArJdbc::ConnectionMethods.module_eval do
52
57
 
53
58
  # @note Assumes SQLServer SQL-JDBC driver on the class-path.
54
59
  def sqlserver_connection(config)
60
+ config = config.deep_dup
61
+
55
62
  config[:adapter_spec] ||= ::ArJdbc::MSSQL
56
63
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
57
64
 
@@ -63,7 +70,8 @@ ArJdbc::ConnectionMethods.module_eval do
63
70
  config[:lock_timeout] ||= 5000
64
71
 
65
72
  config[:url] ||= begin
66
- url = "jdbc:sqlserver://#{config[:host]}"
73
+ url = ''.dup
74
+ url << "jdbc:sqlserver://#{config[:host]}"
67
75
  url << ( config[:port] ? ":#{config[:port]};" : ';' )
68
76
  url << "databaseName=#{config[:database]};" if config[:database]
69
77
  url << "instanceName=#{config[:instance]};" if config[:instance]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -6,6 +8,13 @@ module ActiveRecord
6
8
  # Returns the maximum number of elements in an IN (x,y,z) clause.
7
9
  # NOTE: Could not find a limit for IN in mssql but 10000 seems to work
8
10
  # with the active record tests
11
+ # FIXME: this method was deprecated in rails 6.1, and it seems the only
12
+ # code that used this method was the oracle visitor, the code was moved
13
+ # from rails to the adapter itself.
14
+ # https://github.com/rsim/oracle-enhanced/pull/2008/files
15
+ # related:
16
+ # https://github.com/rails/rails/pull/38946
17
+ # https://github.com/rails/rails/pull/39057
9
18
  def in_clause_length
10
19
  10_000
11
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -23,6 +25,15 @@ module ActiveRecord
23
25
  end
24
26
  alias_method :execute_procedure, :exec_proc # AR-SQLServer-Adapter naming
25
27
 
28
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
29
+ :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :with
30
+ ) # :nodoc:
31
+ private_constant :READ_QUERY
32
+
33
+ def write_query?(sql) # :nodoc:
34
+ !READ_QUERY.match?(sql)
35
+ end
36
+
26
37
  def execute(sql, name = nil)
27
38
  # with identity insert on block
28
39
  if insert_sql?(sql)
@@ -52,11 +63,6 @@ module ActiveRecord
52
63
  end
53
64
  end
54
65
 
55
- # Implements the truncate method.
56
- def truncate(table_name, name = nil)
57
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
58
- end
59
-
60
66
  # Not a rails method, own method to test different isolation
61
67
  # levels supported by the mssql adapter.
62
68
  def supports_transaction_isolation_level?(level)
@@ -95,8 +101,40 @@ module ActiveRecord
95
101
  end
96
102
  end
97
103
 
104
+ # Implements the truncate method.
105
+ def truncate(table_name, name = nil)
106
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
107
+ end
108
+
109
+ def truncate_tables(*table_names) # :nodoc:
110
+ return if table_names.empty?
111
+
112
+ disable_referential_integrity do
113
+ table_names.each do |table_name|
114
+ mssql_truncate(table_name)
115
+ end
116
+ end
117
+ end
118
+
98
119
  private
99
120
 
121
+ # It seems the truncate_tables is mostly used for testing
122
+ # this a workaround to the fact that SQL Server truncate tables
123
+ # referenced by a foreign key, it may not be required to reset
124
+ # the identity column too, more at:
125
+ # https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-ver15
126
+ # TODO: improve is with pure T-SQL, use statements
127
+ # such as TRY CATCH and reset identity with DBCC CHECKIDENT
128
+ def mssql_truncate(table_name)
129
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", 'Truncate Tables'
130
+ rescue => e
131
+ if e.message =~ /Cannot truncate table .* because it is being referenced by a FOREIGN KEY constraint/
132
+ execute "DELETE FROM #{quote_table_name(table_name)}", 'Truncate Tables with Delete'
133
+ else
134
+ raise
135
+ end
136
+ end
137
+
100
138
  # Overrides method in abstract class, combining the sqls with semicolon
101
139
  # affects disable_referential_integrity in mssql specially when multiple
102
140
  # tables are involved.
@@ -127,7 +165,7 @@ module ActiveRecord
127
165
  end
128
166
 
129
167
  def identity_column_name(table_name)
130
- for column in columns(table_name)
168
+ for column in schema_cache.columns(table_name)
131
169
  return column.name if column.identity?
132
170
  end
133
171
  nil
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  # Error raised when adapter determines the database could not acquire
3
5
  # a necessary lock before timing out
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string'
2
4
 
3
5
  module ActiveRecord
@@ -108,7 +110,7 @@ module ActiveRecord
108
110
  end
109
111
 
110
112
  def build_separator
111
- '+' << @widths.map {|w| '-' * (w + (cell_padding * 2))}.join('+') << '+'
113
+ '+'.dup << @widths.map {|w| '-' * (w + (cell_padding * 2))}.join('+') << '+'
112
114
  end
113
115
 
114
116
  def build_cells(items)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file contains extensions, overrides, and monkey patches to core parts
2
4
  # of active record to allow SQL Server work properly.
3
5
  #
@@ -15,10 +17,12 @@ module ActiveRecord
15
17
  # this behaviour, even the current comments for that method says that
16
18
  # it rejects primary key but it doesn't (maybe a rails bug?)
17
19
  def attributes_for_update(attribute_names)
18
- attribute_names.reject do |name|
20
+ attribute_names &= self.class.column_names
21
+
22
+ attribute_names.delete_if do |name|
19
23
  # It seems is only required to check if column in identity or not.
20
24
  # This allows to update rails custom primary keys
21
- next true if readonly_attribute?(name)
25
+ next true if self.class.readonly_attribute?(name)
22
26
 
23
27
  column = self.class.columns_hash[name]
24
28
  column && column.identity?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/relation'
2
4
  require 'active_record/version'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -64,6 +66,42 @@ module ActiveRecord
64
66
  # @see #quote in old adapter
65
67
  BLOB_VALUE_MARKER = "''"
66
68
 
69
+ def column_name_matcher
70
+ COLUMN_NAME
71
+ end
72
+
73
+ def column_name_with_order_matcher
74
+ COLUMN_NAME_WITH_ORDER
75
+ end
76
+
77
+ COLUMN_NAME = /
78
+ \A
79
+ (
80
+ (?:
81
+ # \[table_name\].\[column_name\] | function(one or no argument)
82
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
83
+ )
84
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
85
+ )
86
+ (?:\s*,\s*\g<1>)*
87
+ \z
88
+ /ix
89
+
90
+ COLUMN_NAME_WITH_ORDER = /
91
+ \A
92
+ (
93
+ (?:
94
+ # \[table_name\].\[column_name\] | function(one or no argument)
95
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
96
+ )
97
+ (?:\s+ASC|\s+DESC)?
98
+ )
99
+ (?:\s*,\s*\g<1>)*
100
+ \z
101
+ /ix
102
+
103
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
104
+
67
105
  private
68
106
 
69
107
  def time_with_db_timezone(value)
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
4
- class SchemaCreation < AbstractAdapter::SchemaCreation
6
+ class SchemaCreation < SchemaCreation
5
7
  private
6
8
 
7
9
  def visit_TableDefinition(o)
@@ -11,8 +13,28 @@ module ActiveRecord
11
13
  projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
12
14
  select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
13
15
  else
14
- o.instance_variable_set :@as, nil
15
- super
16
+ # o.instance_variable_set :@as, nil
17
+ # super
18
+ create_sql = +''
19
+
20
+ create_sql << "IF NOT EXISTS (SELECT 1 FROM sysobjects WHERE name='#{o.name}' and xtype='U') " if o.if_not_exists
21
+ create_sql << "CREATE#{table_modifier_in_create(o)} TABLE "
22
+ create_sql << "#{quote_table_name(o.name)} "
23
+
24
+ statements = o.columns.map { |c| accept c }
25
+ statements << accept(o.primary_keys) if o.primary_keys
26
+
27
+ if supports_indexes_in_create?
28
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
29
+ end
30
+
31
+ if supports_foreign_keys?
32
+ statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
33
+ end
34
+
35
+ create_sql << "(#{statements.join(', ')})" if statements.present?
36
+ add_table_options!(create_sql, o)
37
+ create_sql
16
38
  end
17
39
  end
18
40
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -81,6 +83,14 @@ module ActiveRecord
81
83
 
82
84
  super
83
85
  end
86
+
87
+ def timestamps(**options)
88
+ if !options.key?(:precision) && @conn.supports_datetime_with_precision?
89
+ options[:precision] = 7
90
+ end
91
+
92
+ super
93
+ end
84
94
  end
85
95
 
86
96
  class Table < ActiveRecord::ConnectionAdapters::Table
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
4
- module SchemaStatements
6
+ module SchemaStatements # :nodoc:
5
7
 
6
8
  NATIVE_DATABASE_TYPES = {
7
9
  # Logical Rails types to SQL Server types
@@ -41,23 +43,36 @@ module ActiveRecord
41
43
  NATIVE_DATABASE_TYPES
42
44
  end
43
45
 
44
- # Returns an array of Column objects for the table specified by +table_name+.
45
- # See the concrete implementation for details on the expected parameter values.
46
- # NOTE: This is ready, all implemented in the java part of adapter,
47
- # it uses MSSQLColumn, SqlTypeMetadata, etc.
48
- def columns(table_name)
49
- @connection.columns(table_name)
50
- rescue => e
51
- # raise translate_exception_class(e, nil)
52
- # FIXME: this breaks one arjdbc test but fixes activerecord tests
53
- # (table name alias). Also it behaves similarly to the CRuby adapter
54
- # which returns an empty array too. (postgres throws a exception)
55
- []
56
- end
57
-
58
46
  # Returns an array of indexes for the given table.
59
- def indexes(table_name, name = nil)
60
- @connection.indexes(table_name, name)
47
+ def indexes(table_name)
48
+ data = select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") rescue []
49
+
50
+ data.reduce([]) do |indexes, index|
51
+ index = index.with_indifferent_access
52
+
53
+ if index[:index_description] =~ /primary key/
54
+ indexes
55
+ else
56
+ name = index[:index_name]
57
+ unique = index[:index_description].to_s.match?(/unique/)
58
+ where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
59
+ orders = {}
60
+ columns = []
61
+
62
+ index[:index_keys].split(',').each do |column|
63
+ column.strip!
64
+
65
+ if column.ends_with?('(-)')
66
+ column.gsub! '(-)', ''
67
+ orders[column] = :desc
68
+ end
69
+
70
+ columns << column
71
+ end
72
+
73
+ indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders)
74
+ end
75
+ end
61
76
  end
62
77
 
63
78
  def primary_keys(table_name)
@@ -94,7 +109,17 @@ module ActiveRecord
94
109
  end
95
110
 
96
111
  def create_database(name, options = {})
97
- execute "CREATE DATABASE #{quote_database_name(name)}"
112
+ edition_options = create_db_edition_options(options)
113
+
114
+ if options[:collation] && edition_options.present?
115
+ execute "CREATE DATABASE #{quote_database_name(name)} COLLATE #{options[:collation]} (#{edition_options.join(', ')})"
116
+ elsif options[:collation]
117
+ execute "CREATE DATABASE #{quote_database_name(name)} COLLATE #{options[:collation]}"
118
+ elsif edition_options.present?
119
+ execute "CREATE DATABASE #{quote_database_name(name)} (#{edition_options.join(', ')})"
120
+ else
121
+ execute "CREATE DATABASE #{quote_database_name(name)}"
122
+ end
98
123
  end
99
124
 
100
125
  def recreate_database(name, options = {})
@@ -104,6 +129,9 @@ module ActiveRecord
104
129
 
105
130
  def remove_column(table_name, column_name, type = nil, options = {})
106
131
  raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
132
+
133
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
134
+
107
135
  remove_check_constraints(table_name, column_name)
108
136
  remove_default_constraint(table_name, column_name)
109
137
  remove_indexes(table_name, column_name)
@@ -124,7 +152,7 @@ module ActiveRecord
124
152
  end
125
153
  end
126
154
 
127
- if options[:if_exists] && @mssql_major_version < 13
155
+ if options[:if_exists] && mssql_major_version < 13
128
156
  # this is for sql server 2012 and 2014
129
157
  execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
130
158
  else
@@ -193,7 +221,7 @@ module ActiveRecord
193
221
  column_type_sql << "(#{precision})"
194
222
  else
195
223
  raise(
196
- ActiveRecordError,
224
+ ArgumentError,
197
225
  "No #{native[:name]} type has precision of #{precision}. The " \
198
226
  'allowed range of precision is from 0 to 7, even though the ' \
199
227
  'sql type precision is 7 this adapter will persist up to 6 ' \
@@ -220,6 +248,14 @@ module ActiveRecord
220
248
  (order_columns << super).join(', ')
221
249
  end
222
250
 
251
+ def add_timestamps(table_name, options = {})
252
+ if !options.key?(:precision) && supports_datetime_with_precision?
253
+ options[:precision] = 7
254
+ end
255
+
256
+ super
257
+ end
258
+
223
259
  def create_schema_dumper(options)
224
260
  MSSQL::SchemaDumper.create(self, options)
225
261
  end
@@ -299,13 +335,45 @@ module ActiveRecord
299
335
  execute(sql_alter.join(' '))
300
336
  end
301
337
 
338
+ def update_table_definition(table_name, base) #:nodoc:
339
+ MSSQL::Table.new(table_name, base)
340
+ end
341
+
302
342
  private
303
343
 
344
+ def schema_creation
345
+ MSSQL::SchemaCreation.new(self)
346
+ end
347
+
348
+ def create_table_definition(*args)
349
+ MSSQL::TableDefinition.new(self, *args)
350
+ end
351
+
352
+ def new_column_from_field(table_name, field)
353
+ field
354
+ end
355
+
356
+ def create_db_edition_options(options = {})
357
+ edition_config = options.select { |k, _v| k.match?('azure') }
358
+
359
+ edition_config.each_with_object([]) do |(key, value), output|
360
+ output << case key
361
+ when :azure_maxsize
362
+ "MAXSIZE = #{value}"
363
+ when :azure_edition
364
+ "EDITION = #{value}"
365
+ when :service_objective
366
+ "SERVICE_OBJECTIVE = #{value}"
367
+ end
368
+ end.compact
369
+ end
370
+
304
371
  def data_source_sql(name = nil, type: nil)
305
372
  scope = quoted_scope(name, type: type)
306
373
  table_name = 'TABLE_NAME'
307
374
 
308
- sql = "SELECT #{table_name}"
375
+ sql = ''.dup
376
+ sql << "SELECT #{table_name}"
309
377
  sql << ' FROM INFORMATION_SCHEMA.TABLES'
310
378
  sql << ' WHERE TABLE_CATALOG = DB_NAME()'
311
379
  sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
@@ -327,7 +395,9 @@ module ActiveRecord
327
395
  end
328
396
 
329
397
  def change_column_type(table_name, column_name, type, options = {})
330
- sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
398
+ sql = ''.dup
399
+
400
+ sql << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
331
401
  sql << (options[:null] ? " NULL" : " NOT NULL") if options.has_key?(:null)
332
402
  result = execute(sql)
333
403
  result