activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +17 -27
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +49 -41
  10. data/Guardfile +9 -8
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +65 -42
  13. data/RUNNING_UNIT_TESTS.md +3 -0
  14. data/Rakefile +14 -16
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +25 -14
  17. data/appveyor.yml +22 -17
  18. data/docker-compose.ci.yml +7 -5
  19. data/guides/RELEASING.md +11 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +5 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -7
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +51 -14
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
  35. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  37. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
  41. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  42. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  43. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  44. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  45. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -3
  46. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
  81. data/lib/active_record/sqlserver_base.rb +11 -9
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +177 -56
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/active_schema_test_sqlserver.rb +55 -0
  88. data/test/cases/adapter_test_sqlserver.rb +258 -173
  89. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  90. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  91. data/test/cases/coerced_tests.rb +1421 -397
  92. data/test/cases/column_test_sqlserver.rb +321 -315
  93. data/test/cases/connection_test_sqlserver.rb +17 -20
  94. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  95. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  96. data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
  97. data/test/cases/fetch_test_sqlserver.rb +33 -21
  98. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  99. data/test/cases/helper_sqlserver.rb +15 -15
  100. data/test/cases/in_clause_test_sqlserver.rb +63 -0
  101. data/test/cases/index_test_sqlserver.rb +15 -15
  102. data/test/cases/json_test_sqlserver.rb +25 -25
  103. data/test/cases/lateral_test_sqlserver.rb +35 -0
  104. data/test/cases/migration_test_sqlserver.rb +74 -27
  105. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  106. data/test/cases/order_test_sqlserver.rb +59 -53
  107. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  108. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  109. data/test/cases/rake_test_sqlserver.rb +70 -45
  110. data/test/cases/schema_dumper_test_sqlserver.rb +124 -109
  111. data/test/cases/schema_test_sqlserver.rb +20 -26
  112. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  113. data/test/cases/showplan_test_sqlserver.rb +28 -35
  114. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  115. data/test/cases/transaction_test_sqlserver.rb +18 -20
  116. data/test/cases/trigger_test_sqlserver.rb +14 -13
  117. data/test/cases/utils_test_sqlserver.rb +70 -70
  118. data/test/cases/uuid_test_sqlserver.rb +13 -14
  119. data/test/debug.rb +8 -6
  120. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  121. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  122. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  123. data/test/models/sqlserver/booking.rb +3 -1
  124. data/test/models/sqlserver/composite_pk.rb +9 -0
  125. data/test/models/sqlserver/customers_view.rb +3 -1
  126. data/test/models/sqlserver/datatype.rb +2 -0
  127. data/test/models/sqlserver/datatype_migration.rb +2 -0
  128. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  129. data/test/models/sqlserver/edge_schema.rb +3 -3
  130. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  131. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  132. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  133. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  134. data/test/models/sqlserver/no_pk_data.rb +3 -1
  135. data/test/models/sqlserver/object_default.rb +3 -1
  136. data/test/models/sqlserver/quoted_table.rb +4 -2
  137. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  138. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  139. data/test/models/sqlserver/sst_memory.rb +3 -1
  140. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  141. data/test/models/sqlserver/string_default.rb +3 -1
  142. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  143. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  144. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  145. data/test/models/sqlserver/trigger.rb +4 -2
  146. data/test/models/sqlserver/trigger_history.rb +3 -1
  147. data/test/models/sqlserver/upper.rb +3 -1
  148. data/test/models/sqlserver/uppered.rb +3 -1
  149. data/test/models/sqlserver/uuid.rb +3 -1
  150. data/test/schema/sqlserver_specific_schema.rb +56 -21
  151. data/test/support/coerceable_test_sqlserver.rb +19 -13
  152. data/test/support/connection_reflection.rb +3 -2
  153. data/test/support/core_ext/query_cache.rb +4 -1
  154. data/test/support/load_schema_sqlserver.rb +5 -5
  155. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  156. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  157. data/test/support/minitest_sqlserver.rb +3 -1
  158. data/test/support/paths_sqlserver.rb +11 -11
  159. data/test/support/rake_helpers.rb +15 -10
  160. data/test/support/sql_counter_sqlserver.rb +16 -15
  161. data/test/support/test_in_memory_oltp.rb +9 -7
  162. metadata +47 -13
  163. data/.travis.yml +0 -25
  164. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
@@ -1,32 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module SchemaStatements
5
-
6
7
  def native_database_types
7
8
  @native_database_types ||= initialize_native_database_types.freeze
8
9
  end
9
10
 
10
- def create_table(table_name, comment: nil, **options)
11
+ def create_table(table_name, **options)
11
12
  res = super
12
13
  clear_cache!
13
14
  res
14
15
  end
15
16
 
16
- def drop_table(table_name, options = {})
17
+ def drop_table(table_name, **options)
17
18
  # Mimic CASCADE option as best we can.
18
19
  if options[:force] == :cascade
19
20
  execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
20
- fktable = fkdata['FKTABLE_NAME']
21
- fkcolmn = fkdata['FKCOLUMN_NAME']
22
- pktable = fkdata['PKTABLE_NAME']
23
- pkcolmn = fkdata['PKCOLUMN_NAME']
24
- remove_foreign_key fktable, name: fkdata['FK_NAME']
21
+ fktable = fkdata["FKTABLE_NAME"]
22
+ fkcolmn = fkdata["FKCOLUMN_NAME"]
23
+ pktable = fkdata["PKTABLE_NAME"]
24
+ pkcolmn = fkdata["PKCOLUMN_NAME"]
25
+ remove_foreign_key fktable, name: fkdata["FK_NAME"]
25
26
  do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
26
27
  end
27
28
  end
28
29
  if options[:if_exists] && @version_year < 2016
29
- execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
30
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
30
31
  else
31
32
  super
32
33
  end
@@ -47,11 +48,11 @@ module ActiveRecord
47
48
  orders = {}
48
49
  columns = []
49
50
 
50
- index[:index_keys].split(',').each do |column|
51
+ index[:index_keys].split(",").each do |column|
51
52
  column.strip!
52
53
 
53
- if column.ends_with?('(-)')
54
- column.gsub! '(-)', ''
54
+ if column.end_with?("(-)")
55
+ column.gsub! "(-)", ""
55
56
  orders[column] = :desc
56
57
  end
57
58
 
@@ -65,15 +66,15 @@ module ActiveRecord
65
66
 
66
67
  def columns(table_name)
67
68
  return [] if table_name.blank?
69
+
68
70
  column_definitions(table_name).map do |ci|
69
- sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity
71
+ sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :table_name
70
72
  sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
71
73
  new_column(
72
74
  ci[:name],
73
75
  ci[:default_value],
74
76
  sql_type_metadata,
75
77
  ci[:null],
76
- ci[:table_name],
77
78
  ci[:default_function],
78
79
  ci[:collation],
79
80
  nil,
@@ -82,16 +83,16 @@ module ActiveRecord
82
83
  end
83
84
  end
84
85
 
85
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
86
- SQLServerColumn.new(
86
+ def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
87
+ SQLServer::Column.new(
87
88
  name,
88
89
  default,
89
90
  sql_type_metadata,
90
- null, table_name,
91
+ null,
91
92
  default_function,
92
- collation,
93
- comment,
94
- sqlserver_options
93
+ collation: collation,
94
+ comment: comment,
95
+ **sqlserver_options
95
96
  )
96
97
  end
97
98
 
@@ -104,7 +105,7 @@ module ActiveRecord
104
105
  identifier = database_prefix_identifier(table_name)
105
106
  database = identifier.fully_qualified_database_quoted
106
107
  sql = %{
107
- SELECT KCU.COLUMN_NAME AS [name]
108
+ SELECT #{lowercase_schema_reflection_sql('KCU.COLUMN_NAME')} AS [name]
108
109
  FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
109
110
  LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
110
111
  ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
@@ -116,12 +117,12 @@ module ActiveRecord
116
117
  AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
117
118
  AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
118
119
  ORDER BY KCU.ORDINAL_POSITION ASC
119
- }.gsub(/[[:space:]]/, ' ')
120
+ }.gsub(/[[:space:]]/, " ")
120
121
  binds = []
121
122
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
122
- binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
123
- binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
124
- sp_executesql(sql, 'SCHEMA', binds).map { |r| r['name'] }
123
+ binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
124
+ binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
125
+ sp_executesql(sql, "SCHEMA", binds).map { |r| r["name"] }
125
126
  end
126
127
 
127
128
  def rename_table(table_name, new_name)
@@ -129,8 +130,10 @@ module ActiveRecord
129
130
  rename_table_indexes(table_name, new_name)
130
131
  end
131
132
 
132
- def remove_column(table_name, column_name, type = nil, options = {})
133
- raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
133
+ def remove_column(table_name, column_name, type = nil, **options)
134
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array
135
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
136
+
134
137
  remove_check_constraints(table_name, column_name)
135
138
  remove_default_constraint(table_name, column_name)
136
139
  remove_indexes(table_name, column_name)
@@ -143,18 +146,20 @@ module ActiveRecord
143
146
  column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
144
147
  without_constraints = options.key?(:default) || options.key?(:limit)
145
148
  default = if !options.key?(:default) && column_object
146
- column_object.default
147
- else
148
- options[:default]
149
- end
149
+ column_object.default
150
+ else
151
+ options[:default]
152
+ end
150
153
  if without_constraints || (column_object && column_object.type != type.to_sym)
151
154
  remove_default_constraint(table_name, column_name)
152
155
  indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
153
156
  remove_indexes(table_name, column_name)
154
157
  end
155
158
  sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
156
- sql_commands << "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])}"
157
- sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
159
+ alter_command = "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])}"
160
+ alter_command += " COLLATE #{options[:collation]}" if options[:collation].present?
161
+ alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
162
+ sql_commands << alter_command
158
163
  if without_constraints
159
164
  default = quote_default_expression(default, column_object || column_for(table_name, column_name))
160
165
  sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{default} FOR #{quote_column_name(column_name)}"
@@ -171,6 +176,7 @@ module ActiveRecord
171
176
  clear_cache!
172
177
  column = column_for(table_name, column_name)
173
178
  return unless column
179
+
174
180
  remove_default_constraint(table_name, column_name)
175
181
  default = extract_new_default_value(default_or_changes)
176
182
  do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
@@ -180,15 +186,16 @@ module ActiveRecord
180
186
  def rename_column(table_name, column_name, new_column_name)
181
187
  clear_cache!
182
188
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
183
- execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
189
+ execute_procedure :sp_rename, identifier.quoted, new_column_name, "COLUMN"
184
190
  rename_column_indexes(table_name, column_name, new_column_name)
185
191
  clear_cache!
186
192
  end
187
193
 
188
194
  def rename_index(table_name, old_name, new_name)
189
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length
195
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" if new_name.length > index_name_length
196
+
190
197
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
191
- execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
198
+ execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
192
199
  end
193
200
 
194
201
  def remove_index!(table_name, index_name)
@@ -200,13 +207,13 @@ module ActiveRecord
200
207
  fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
201
208
  fk_info.map do |row|
202
209
  from_table = identifier.object
203
- to_table = row['PKTABLE_NAME']
210
+ to_table = row["PKTABLE_NAME"]
204
211
  options = {
205
- name: row['FK_NAME'],
206
- column: row['FKCOLUMN_NAME'],
207
- primary_key: row['PKCOLUMN_NAME'],
208
- on_update: extract_foreign_key_action('update', row['FK_NAME']),
209
- on_delete: extract_foreign_key_action('delete', row['FK_NAME'])
212
+ name: row["FK_NAME"],
213
+ column: row["FKCOLUMN_NAME"],
214
+ primary_key: row["PKCOLUMN_NAME"],
215
+ on_update: extract_foreign_key_action("update", row["FK_NAME"]),
216
+ on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
210
217
  }
211
218
  ForeignKeyDefinition.new from_table, to_table, options
212
219
  end
@@ -214,8 +221,8 @@ module ActiveRecord
214
221
 
215
222
  def extract_foreign_key_action(action, fk_name)
216
223
  case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
217
- when 'CASCADE' then :cascade
218
- when 'SET_NULL' then :nullify
224
+ when "CASCADE" then :cascade
225
+ when "SET_NULL" then :nullify
219
226
  end
220
227
  end
221
228
 
@@ -223,21 +230,21 @@ module ActiveRecord
223
230
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
224
231
  limit = nil unless type_limitable
225
232
  case type.to_s
226
- when 'integer'
233
+ when "integer"
227
234
  case limit
228
- when 1 then 'tinyint'
229
- when 2 then 'smallint'
230
- when 3..4, nil then 'integer'
231
- when 5..8 then 'bigint'
235
+ when 1 then "tinyint"
236
+ when 2 then "smallint"
237
+ when 3..4, nil then "integer"
238
+ when 5..8 then "bigint"
232
239
  else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
233
240
  end
234
- when 'datetime2'
241
+ when "datetime2"
235
242
  column_type_sql = super
236
243
  if precision
237
244
  if (0..7) === precision
238
245
  column_type_sql << "(#{precision})"
239
246
  else
240
- raise(ActiveRecordError, "The dattime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
247
+ raise(ActiveRecordError, "The datetime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
241
248
  end
242
249
  end
243
250
  column_type_sql
@@ -247,11 +254,11 @@ module ActiveRecord
247
254
  end
248
255
 
249
256
  def columns_for_distinct(columns, orders)
250
- order_columns = orders.reject(&:blank?).map{ |s|
251
- s = s.to_sql unless s.is_a?(String)
252
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
253
- .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
254
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
257
+ order_columns = orders.reject(&:blank?).map { |s|
258
+ s = s.to_sql unless s.is_a?(String)
259
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
260
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
261
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
255
262
 
256
263
  (order_columns << super).join(", ")
257
264
  end
@@ -268,7 +275,7 @@ module ActiveRecord
268
275
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
269
276
  end
270
277
  sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
271
- sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
278
+ sql += " NOT NULL" if !allow_null.nil? && allow_null == false
272
279
  do_execute sql
273
280
  end
274
281
 
@@ -276,25 +283,45 @@ module ActiveRecord
276
283
  SQLServer::SchemaDumper.create(self, options)
277
284
  end
278
285
 
286
+ def create_schema(schema_name, authorization = nil)
287
+ sql = "CREATE SCHEMA [#{schema_name}]"
288
+ sql += " AUTHORIZATION [#{authorization}]" if authorization
289
+
290
+ execute sql
291
+ end
292
+
293
+ def change_table_schema(schema_name, table_name)
294
+ execute "ALTER SCHEMA [#{schema_name}] TRANSFER [#{table_name}]"
295
+ end
296
+
297
+ def drop_schema(schema_name)
298
+ execute "DROP SCHEMA [#{schema_name}]"
299
+ end
300
+
279
301
  private
280
302
 
281
303
  def data_source_sql(name = nil, type: nil)
282
304
  scope = quoted_scope name, type: type
305
+
283
306
  table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
307
+ database = scope[:database].present? ? "#{scope[:database]}." : ""
308
+ table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
309
+
284
310
  sql = "SELECT #{table_name}"
285
- sql << ' FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)'
286
- sql << ' WHERE TABLE_CATALOG = DB_NAME()'
287
- sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
288
- sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
289
- sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
290
- sql << " ORDER BY #{table_name}"
311
+ sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
312
+ sql += " WHERE TABLE_CATALOG = #{table_catalog}"
313
+ sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
314
+ sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
315
+ sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
316
+ sql += " ORDER BY #{table_name}"
291
317
  sql
292
318
  end
293
319
 
294
320
  def quoted_scope(name = nil, type: nil)
295
321
  identifier = SQLServer::Utils.extract_identifiers(name)
296
322
  {}.tap do |scope|
297
- scope[:schema] = identifier.schema || 'dbo'
323
+ scope[:database] = identifier.database if identifier.database
324
+ scope[:schema] = identifier.schema || "dbo"
298
325
  scope[:name] = identifier.object if identifier.object
299
326
  scope[:type] = type if type
300
327
  end
@@ -304,37 +331,37 @@ module ActiveRecord
304
331
 
305
332
  def initialize_native_database_types
306
333
  {
307
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
308
- primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
309
- integer: { name: 'int', limit: 4 },
310
- bigint: { name: 'bigint' },
311
- boolean: { name: 'bit' },
312
- decimal: { name: 'decimal' },
313
- money: { name: 'money' },
314
- smallmoney: { name: 'smallmoney' },
315
- float: { name: 'float' },
316
- real: { name: 'real' },
317
- date: { name: 'date' },
318
- datetime: { name: 'datetime' },
319
- datetime2: { name: 'datetime2' },
320
- datetimeoffset: { name: 'datetimeoffset' },
321
- smalldatetime: { name: 'smalldatetime' },
322
- timestamp: { name: 'datetime' },
323
- time: { name: 'time' },
324
- char: { name: 'char' },
325
- varchar: { name: 'varchar', limit: 8000 },
326
- varchar_max: { name: 'varchar(max)' },
327
- text_basic: { name: 'text' },
328
- nchar: { name: 'nchar' },
329
- string: { name: 'nvarchar', limit: 4000 },
330
- text: { name: 'nvarchar(max)' },
331
- ntext: { name: 'ntext' },
332
- binary_basic: { name: 'binary' },
333
- varbinary: { name: 'varbinary', limit: 8000 },
334
- binary: { name: 'varbinary(max)' },
335
- uuid: { name: 'uniqueidentifier' },
336
- ss_timestamp: { name: 'timestamp' },
337
- json: { name: 'nvarchar(max)' }
334
+ primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
335
+ primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
336
+ integer: { name: "int", limit: 4 },
337
+ bigint: { name: "bigint" },
338
+ boolean: { name: "bit" },
339
+ decimal: { name: "decimal" },
340
+ money: { name: "money" },
341
+ smallmoney: { name: "smallmoney" },
342
+ float: { name: "float" },
343
+ real: { name: "real" },
344
+ date: { name: "date" },
345
+ datetime: { name: "datetime" },
346
+ datetime2: { name: "datetime2" },
347
+ datetimeoffset: { name: "datetimeoffset" },
348
+ smalldatetime: { name: "smalldatetime" },
349
+ timestamp: { name: "datetime" },
350
+ time: { name: "time" },
351
+ char: { name: "char" },
352
+ varchar: { name: "varchar", limit: 8000 },
353
+ varchar_max: { name: "varchar(max)" },
354
+ text_basic: { name: "text" },
355
+ nchar: { name: "nchar" },
356
+ string: { name: "nvarchar", limit: 4000 },
357
+ text: { name: "nvarchar(max)" },
358
+ ntext: { name: "ntext" },
359
+ binary_basic: { name: "binary" },
360
+ varbinary: { name: "varbinary", limit: 8000 },
361
+ binary: { name: "varbinary(max)" },
362
+ uuid: { name: "uniqueidentifier" },
363
+ ss_timestamp: { name: "timestamp" },
364
+ json: { name: "nvarchar(max)" }
338
365
  }
339
366
  end
340
367
 
@@ -343,61 +370,15 @@ module ActiveRecord
343
370
  database = identifier.fully_qualified_database_quoted
344
371
  view_exists = view_exists?(table_name)
345
372
  view_tblnm = view_table_name(table_name) if view_exists
346
- sql = %{
347
- SELECT DISTINCT
348
- #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
349
- #{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
350
- columns.DATA_TYPE AS type,
351
- columns.COLUMN_DEFAULT AS default_value,
352
- columns.NUMERIC_SCALE AS numeric_scale,
353
- columns.NUMERIC_PRECISION AS numeric_precision,
354
- columns.DATETIME_PRECISION AS datetime_precision,
355
- columns.COLLATION_NAME AS [collation],
356
- columns.ordinal_position,
357
- CASE
358
- WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
359
- ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
360
- END AS [length],
361
- CASE
362
- WHEN columns.IS_NULLABLE = 'YES' THEN 1
363
- ELSE NULL
364
- END AS [is_nullable],
365
- CASE
366
- WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
367
- ELSE NULL
368
- END AS [is_primary],
369
- c.is_identity AS [is_identity]
370
- FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
371
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
372
- ON TC.TABLE_NAME = columns.TABLE_NAME
373
- AND TC.TABLE_SCHEMA = columns.TABLE_SCHEMA
374
- AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
375
- LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
376
- ON KCU.COLUMN_NAME = columns.COLUMN_NAME
377
- AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
378
- AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
379
- AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
380
- INNER JOIN #{database}.sys.schemas AS s
381
- ON s.name = columns.TABLE_SCHEMA
382
- AND s.schema_id = s.schema_id
383
- INNER JOIN #{database}.sys.objects AS o
384
- ON s.schema_id = o.schema_id
385
- AND o.is_ms_shipped = 0
386
- AND o.type IN ('U', 'V')
387
- AND o.name = columns.TABLE_NAME
388
- INNER JOIN #{database}.sys.columns AS c
389
- ON o.object_id = c.object_id
390
- AND c.name = columns.COLUMN_NAME
391
- WHERE columns.TABLE_NAME = #{prepared_statements ? '@0' : quote(identifier.object)}
392
- AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
393
- ORDER BY columns.ordinal_position
394
- }.gsub(/[ \t\r\n]+/, ' ').strip
373
+
374
+ sql = column_definitions_sql(database, identifier)
375
+
395
376
  binds = []
396
377
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
397
- binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
398
- binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
399
- results = sp_executesql(sql, 'SCHEMA', binds)
400
- results.map do |ci|
378
+ binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
379
+ binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
380
+ results = sp_executesql(sql, "SCHEMA", binds)
381
+ columns = results.map do |ci|
401
382
  ci = ci.symbolize_keys
402
383
  ci[:_type] = ci[:type]
403
384
  ci[:table_name] = view_tblnm || table_name
@@ -427,7 +408,7 @@ module ActiveRecord
427
408
  WHERE
428
409
  c.TABLE_NAME = '#{view_tblnm}'
429
410
  AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
430
- }.squish, 'SCHEMA'
411
+ }.squish, "SCHEMA"
431
412
  end
432
413
  case default
433
414
  when nil
@@ -446,7 +427,7 @@ module ActiveRecord
446
427
  else ci[:type]
447
428
  end
448
429
  value = default.match(/\A\((.*)\)\Z/m)[1]
449
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA')
430
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
450
431
  [value, nil]
451
432
  end
452
433
  end
@@ -456,10 +437,92 @@ module ActiveRecord
456
437
  ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
457
438
  ci
458
439
  end
440
+
441
+ # Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
442
+ # I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
443
+ # Raise error if the method return an empty array
444
+ columns.tap do |result|
445
+ raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if result.empty?
446
+ end
447
+ end
448
+
449
+ def column_definitions_sql(database, identifier)
450
+ object_name = prepared_statements ? "@0" : quote(identifier.object)
451
+ schema_name = if identifier.schema.blank?
452
+ "schema_name()"
453
+ else
454
+ prepared_statements ? "@1" : quote(identifier.schema)
455
+ end
456
+
457
+ %{
458
+ SELECT
459
+ #{lowercase_schema_reflection_sql('o.name')} AS [table_name],
460
+ #{lowercase_schema_reflection_sql('c.name')} AS [name],
461
+ t.name AS [type],
462
+ d.definition AS [default_value],
463
+ CASE
464
+ WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint')
465
+ THEN c.scale
466
+ END AS [numeric_scale],
467
+ CASE
468
+ WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint', 'real', 'float')
469
+ THEN c.precision
470
+ END AS [numeric_precision],
471
+ CASE
472
+ WHEN t.name IN ('date', 'datetime', 'datetime2', 'datetimeoffset', 'smalldatetime', 'time')
473
+ THEN c.scale
474
+ END AS [datetime_precision],
475
+ c.collation_name AS [collation],
476
+ ROW_NUMBER() OVER (ORDER BY c.column_id) AS [ordinal_position],
477
+ CASE
478
+ WHEN t.name IN ('nchar', 'nvarchar') AND c.max_length > 0
479
+ THEN c.max_length / 2
480
+ ELSE c.max_length
481
+ END AS [length],
482
+ CASE c.is_nullable
483
+ WHEN 1
484
+ THEN 1
485
+ END AS [is_nullable],
486
+ CASE
487
+ WHEN ic.object_id IS NOT NULL
488
+ THEN 1
489
+ END AS [is_primary],
490
+ c.is_identity AS [is_identity]
491
+ FROM #{database}.sys.columns c
492
+ INNER JOIN #{database}.sys.objects o
493
+ ON c.object_id = o.object_id
494
+ INNER JOIN #{database}.sys.schemas s
495
+ ON o.schema_id = s.schema_id
496
+ INNER JOIN #{database}.sys.types t
497
+ ON c.system_type_id = t.system_type_id
498
+ AND c.user_type_id = t.user_type_id
499
+ LEFT OUTER JOIN #{database}.sys.default_constraints d
500
+ ON c.object_id = d.parent_object_id
501
+ AND c.default_object_id = d.object_id
502
+ LEFT OUTER JOIN #{database}.sys.key_constraints k
503
+ ON c.object_id = k.parent_object_id
504
+ AND k.type = 'PK'
505
+ LEFT OUTER JOIN #{database}.sys.index_columns ic
506
+ ON k.parent_object_id = ic.object_id
507
+ AND k.unique_index_id = ic.index_id
508
+ AND c.column_id = ic.column_id
509
+ WHERE
510
+ o.name = #{object_name}
511
+ AND s.name = #{schema_name}
512
+ ORDER BY
513
+ c.column_id
514
+ }.gsub(/[ \t\r\n]+/, " ").strip
515
+ end
516
+
517
+ def remove_columns_for_alter(table_name, *column_names, **options)
518
+ first, *rest = column_names
519
+
520
+ # return an array like this [DROP COLUMN col_1, col_2, col_3]. Abstract adapter joins fragments with ", "
521
+ [remove_column_for_alter(table_name, first)] + rest.map { |column_name| quote_column_name(column_name) }
459
522
  end
460
523
 
461
524
  def remove_check_constraints(table_name, column_name)
462
- constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", 'SCHEMA'
525
+ constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", "SCHEMA"
463
526
  constraints.each do |constraint|
464
527
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
465
528
  end
@@ -467,8 +530,8 @@ module ActiveRecord
467
530
 
468
531
  def remove_default_constraint(table_name, column_name)
469
532
  # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
470
- execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
471
- row['constraint_type'] == "DEFAULT on column #{column_name}"
533
+ execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
534
+ row["constraint_type"] == "DEFAULT on column #{column_name}"
472
535
  end.each do |row|
473
536
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
474
537
  end
@@ -482,15 +545,20 @@ module ActiveRecord
482
545
 
483
546
  # === SQLServer Specific (Misc Helpers) ========================= #
484
547
 
548
+ # Parses just the table name from the SQL. Table name does not include database/schema/etc.
485
549
  def get_table_name(sql)
486
- tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
550
+ tn = get_raw_table_name(sql)
551
+ SQLServer::Utils.extract_identifiers(tn).object
552
+ end
553
+
554
+ # Parses the raw table name that is used in the SQL. Table name could include database/schema/etc.
555
+ def get_raw_table_name(sql)
556
+ case sql
557
+ when /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
487
558
  Regexp.last_match[3] || Regexp.last_match[4]
488
- elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
559
+ when /FROM\s+([^\(\s]+)\s*/i
489
560
  Regexp.last_match[1]
490
- else
491
- nil
492
561
  end
493
- SQLServer::Utils.extract_identifiers(tn).object
494
562
  end
495
563
 
496
564
  def default_constraint_name(table_name, column_name)
@@ -505,22 +573,22 @@ module ActiveRecord
505
573
 
506
574
  def view_table_name(table_name)
507
575
  view_info = view_information(table_name)
508
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
576
+ view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
509
577
  end
510
578
 
511
579
  def view_information(table_name)
512
580
  @view_information ||= {}
513
581
  @view_information[table_name] ||= begin
514
582
  identifier = SQLServer::Utils.extract_identifiers(table_name)
515
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
583
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
516
584
  if view_info
517
585
  view_info = view_info.with_indifferent_access
518
586
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
519
587
  view_info[:VIEW_DEFINITION] = begin
520
- select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
521
- rescue
522
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
523
- nil
588
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
589
+ rescue
590
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
591
+ nil
524
592
  end
525
593
  end
526
594
  end
@@ -531,14 +599,14 @@ module ActiveRecord
531
599
  def views_real_column_name(table_name, column_name)
532
600
  view_definition = view_information(table_name)[:VIEW_DEFINITION]
533
601
  return column_name unless view_definition
602
+
534
603
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
535
604
  match_data ? match_data[1] : column_name
536
605
  end
537
606
 
538
- def create_table_definition(*args)
539
- SQLServer::TableDefinition.new(*args)
607
+ def create_table_definition(*args, **options)
608
+ SQLServer::TableDefinition.new(self, *args, **options)
540
609
  end
541
-
542
610
  end
543
611
  end
544
612
  end