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.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +17 -27
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +49 -41
- data/Guardfile +9 -8
- data/MIT-LICENSE +1 -1
- data/README.md +65 -42
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +14 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +25 -14
- data/appveyor.yml +22 -17
- data/docker-compose.ci.yml +7 -5
- data/guides/RELEASING.md +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +51 -14
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -3
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
- data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
- data/lib/active_record/sqlserver_base.rb +11 -9
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +177 -56
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/active_schema_test_sqlserver.rb +55 -0
- data/test/cases/adapter_test_sqlserver.rb +258 -173
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +1421 -397
- data/test/cases/column_test_sqlserver.rb +321 -315
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
- data/test/cases/fetch_test_sqlserver.rb +33 -21
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +15 -15
- data/test/cases/in_clause_test_sqlserver.rb +63 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +74 -27
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +59 -53
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +70 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +124 -109
- data/test/cases/schema_test_sqlserver.rb +20 -26
- data/test/cases/scratchpad_test_sqlserver.rb +4 -4
- data/test/cases/showplan_test_sqlserver.rb +28 -35
- data/test/cases/specific_schema_test_sqlserver.rb +68 -65
- data/test/cases/transaction_test_sqlserver.rb +18 -20
- data/test/cases/trigger_test_sqlserver.rb +14 -13
- data/test/cases/utils_test_sqlserver.rb +70 -70
- data/test/cases/uuid_test_sqlserver.rb +13 -14
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +3 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
- data/test/models/sqlserver/booking.rb +3 -1
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/customers_view.rb +3 -1
- data/test/models/sqlserver/datatype.rb +2 -0
- data/test/models/sqlserver/datatype_migration.rb +2 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -1
- data/test/models/sqlserver/edge_schema.rb +3 -3
- data/test/models/sqlserver/fk_has_fk.rb +3 -1
- data/test/models/sqlserver/fk_has_pk.rb +3 -1
- data/test/models/sqlserver/natural_pk_data.rb +4 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
- data/test/models/sqlserver/no_pk_data.rb +3 -1
- data/test/models/sqlserver/object_default.rb +3 -1
- data/test/models/sqlserver/quoted_table.rb +4 -2
- data/test/models/sqlserver/quoted_view_1.rb +3 -1
- data/test/models/sqlserver/quoted_view_2.rb +3 -1
- data/test/models/sqlserver/sst_memory.rb +3 -1
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
- data/test/models/sqlserver/string_defaults_view.rb +3 -1
- data/test/models/sqlserver/tinyint_pk.rb +3 -1
- data/test/models/sqlserver/trigger.rb +4 -2
- data/test/models/sqlserver/trigger_history.rb +3 -1
- data/test/models/sqlserver/upper.rb +3 -1
- data/test/models/sqlserver/uppered.rb +3 -1
- data/test/models/sqlserver/uuid.rb +3 -1
- data/test/schema/sqlserver_specific_schema.rb +56 -21
- data/test/support/coerceable_test_sqlserver.rb +19 -13
- data/test/support/connection_reflection.rb +3 -2
- data/test/support/core_ext/query_cache.rb +4 -1
- data/test/support/load_schema_sqlserver.rb +5 -5
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +15 -10
- data/test/support/sql_counter_sqlserver.rb +16 -15
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +47 -13
- data/.travis.yml +0 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require
|
|
11
|
-
require
|
|
12
|
-
require
|
|
13
|
-
require
|
|
14
|
-
require
|
|
15
|
-
require
|
|
16
|
-
require
|
|
17
|
-
require
|
|
18
|
-
require
|
|
19
|
-
require
|
|
20
|
-
require
|
|
21
|
-
require
|
|
22
|
-
require
|
|
23
|
-
require
|
|
24
|
-
require
|
|
25
|
-
require
|
|
26
|
-
require
|
|
27
|
-
require
|
|
28
|
-
require
|
|
29
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "active_record"
|
|
5
|
+
require "arel_sqlserver"
|
|
6
|
+
require "active_record/connection_adapters/abstract_adapter"
|
|
7
|
+
require "active_record/connection_adapters/sqlserver/core_ext/active_record"
|
|
8
|
+
require "active_record/connection_adapters/sqlserver/core_ext/calculations"
|
|
9
|
+
require "active_record/connection_adapters/sqlserver/core_ext/explain"
|
|
10
|
+
require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
|
|
11
|
+
require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
|
|
12
|
+
require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
|
|
13
|
+
require "active_record/connection_adapters/sqlserver/core_ext/preloader"
|
|
14
|
+
require "active_record/connection_adapters/sqlserver/version"
|
|
15
|
+
require "active_record/connection_adapters/sqlserver/type"
|
|
16
|
+
require "active_record/connection_adapters/sqlserver/database_limits"
|
|
17
|
+
require "active_record/connection_adapters/sqlserver/database_statements"
|
|
18
|
+
require "active_record/connection_adapters/sqlserver/database_tasks"
|
|
19
|
+
require "active_record/connection_adapters/sqlserver/transaction"
|
|
20
|
+
require "active_record/connection_adapters/sqlserver/errors"
|
|
21
|
+
require "active_record/connection_adapters/sqlserver/schema_creation"
|
|
22
|
+
require "active_record/connection_adapters/sqlserver/schema_dumper"
|
|
23
|
+
require "active_record/connection_adapters/sqlserver/schema_statements"
|
|
24
|
+
require "active_record/connection_adapters/sqlserver/sql_type_metadata"
|
|
25
|
+
require "active_record/connection_adapters/sqlserver/showplan"
|
|
26
|
+
require "active_record/connection_adapters/sqlserver/table_definition"
|
|
27
|
+
require "active_record/connection_adapters/sqlserver/quoting"
|
|
28
|
+
require "active_record/connection_adapters/sqlserver/utils"
|
|
29
|
+
require "active_record/sqlserver_base"
|
|
30
|
+
require "active_record/connection_adapters/sqlserver_column"
|
|
31
|
+
require "active_record/tasks/sqlserver_database_tasks"
|
|
30
32
|
|
|
31
33
|
module ActiveRecord
|
|
32
34
|
module ConnectionAdapters
|
|
33
35
|
class SQLServerAdapter < AbstractAdapter
|
|
34
|
-
|
|
35
36
|
include SQLServer::Version,
|
|
36
37
|
SQLServer::Quoting,
|
|
37
38
|
SQLServer::DatabaseStatements,
|
|
@@ -40,7 +41,7 @@ module ActiveRecord
|
|
|
40
41
|
SQLServer::DatabaseLimits,
|
|
41
42
|
SQLServer::DatabaseTasks
|
|
42
43
|
|
|
43
|
-
ADAPTER_NAME =
|
|
44
|
+
ADAPTER_NAME = "SQLServer".freeze
|
|
44
45
|
|
|
45
46
|
# Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
|
|
46
47
|
DEFAULT_TIME_PRECISION = 7
|
|
@@ -53,17 +54,93 @@ module ActiveRecord
|
|
|
53
54
|
cattr_accessor :showplan_option, instance_accessor: false
|
|
54
55
|
cattr_accessor :lowercase_schema_reflection
|
|
55
56
|
|
|
56
|
-
self.cs_equality_operator =
|
|
57
|
+
self.cs_equality_operator = "COLLATE Latin1_General_CS_AS_WS"
|
|
57
58
|
self.use_output_inserted = true
|
|
58
59
|
self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
class << self
|
|
62
|
+
def new_client(config)
|
|
63
|
+
case config[:mode]
|
|
64
|
+
when :dblib
|
|
65
|
+
require "tiny_tds"
|
|
66
|
+
dblib_connect(config)
|
|
67
|
+
else
|
|
68
|
+
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def dblib_connect(config)
|
|
73
|
+
TinyTds::Client.new(
|
|
74
|
+
dataserver: config[:dataserver],
|
|
75
|
+
host: config[:host],
|
|
76
|
+
port: config[:port],
|
|
77
|
+
username: config[:username],
|
|
78
|
+
password: config[:password],
|
|
79
|
+
database: config[:database],
|
|
80
|
+
tds_version: config[:tds_version] || "7.3",
|
|
81
|
+
appname: config_appname(config),
|
|
82
|
+
login_timeout: config_login_timeout(config),
|
|
83
|
+
timeout: config_timeout(config),
|
|
84
|
+
encoding: config_encoding(config),
|
|
85
|
+
azure: config[:azure],
|
|
86
|
+
contained: config[:contained]
|
|
87
|
+
).tap do |client|
|
|
88
|
+
if config[:azure]
|
|
89
|
+
client.execute("SET ANSI_NULLS ON").do
|
|
90
|
+
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
|
91
|
+
client.execute("SET ANSI_PADDING ON").do
|
|
92
|
+
client.execute("SET ANSI_WARNINGS ON").do
|
|
93
|
+
else
|
|
94
|
+
client.execute("SET ANSI_DEFAULTS ON").do
|
|
95
|
+
end
|
|
96
|
+
client.execute("SET QUOTED_IDENTIFIER ON").do
|
|
97
|
+
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
|
98
|
+
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
|
99
|
+
client.execute("SET TEXTSIZE 2147483647").do
|
|
100
|
+
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
|
101
|
+
end
|
|
102
|
+
rescue TinyTds::Error => e
|
|
103
|
+
raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
|
|
104
|
+
raise e
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def config_appname(config)
|
|
108
|
+
if instance_methods.include?(:configure_application_name)
|
|
109
|
+
ActiveSupport::Deprecation.warn <<~MSG.squish
|
|
110
|
+
Configuring the application name used by TinyTDS by overriding the
|
|
111
|
+
`ActiveRecord::ConnectionAdapters::SQLServerAdapter#configure_application_name`
|
|
112
|
+
instance method is no longer supported. The application name should configured
|
|
113
|
+
using the `appname` setting in the `database.yml` file instead. Consult the
|
|
114
|
+
README for further information."
|
|
115
|
+
MSG
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
config[:appname] || rails_application_name
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def rails_application_name
|
|
122
|
+
Rails.application.class.name.split("::").first
|
|
123
|
+
rescue
|
|
124
|
+
nil # Might not be in a Rails context so we fallback to `nil`.
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def config_login_timeout(config)
|
|
128
|
+
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def config_timeout(config)
|
|
132
|
+
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def config_encoding(config)
|
|
136
|
+
config[:encoding].present? ? config[:encoding] : nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def initialize(connection, logger, _connection_options, config)
|
|
61
141
|
super(connection, logger, config)
|
|
62
|
-
# Our Responsibility
|
|
63
142
|
@connection_options = config
|
|
64
|
-
|
|
65
|
-
initialize_dateformatter
|
|
66
|
-
use_database
|
|
143
|
+
configure_connection
|
|
67
144
|
end
|
|
68
145
|
|
|
69
146
|
# === Abstract Adapter ========================================== #
|
|
@@ -80,6 +157,12 @@ module ActiveRecord
|
|
|
80
157
|
SQLServer::SchemaCreation.new self
|
|
81
158
|
end
|
|
82
159
|
|
|
160
|
+
def self.database_exists?(config)
|
|
161
|
+
!!ActiveRecord::Base.sqlserver_connection(config)
|
|
162
|
+
rescue ActiveRecord::NoDatabaseError
|
|
163
|
+
false
|
|
164
|
+
end
|
|
165
|
+
|
|
83
166
|
def supports_ddl_transactions?
|
|
84
167
|
true
|
|
85
168
|
end
|
|
@@ -144,10 +227,34 @@ module ActiveRecord
|
|
|
144
227
|
true
|
|
145
228
|
end
|
|
146
229
|
|
|
230
|
+
def supports_optimizer_hints?
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def supports_lazy_transactions?
|
|
235
|
+
true
|
|
236
|
+
end
|
|
237
|
+
|
|
147
238
|
def supports_in_memory_oltp?
|
|
148
239
|
@version_year >= 2014
|
|
149
240
|
end
|
|
150
241
|
|
|
242
|
+
def supports_insert_returning?
|
|
243
|
+
true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def supports_insert_on_duplicate_skip?
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def supports_insert_on_duplicate_update?
|
|
251
|
+
false
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def supports_insert_conflict_target?
|
|
255
|
+
false
|
|
256
|
+
end
|
|
257
|
+
|
|
151
258
|
def disable_referential_integrity
|
|
152
259
|
tables = tables_with_referential_integrity
|
|
153
260
|
tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
|
|
@@ -160,7 +267,8 @@ module ActiveRecord
|
|
|
160
267
|
|
|
161
268
|
def active?
|
|
162
269
|
return false unless @connection
|
|
163
|
-
|
|
270
|
+
|
|
271
|
+
raw_connection_do "SELECT 1"
|
|
164
272
|
true
|
|
165
273
|
rescue *connection_errors
|
|
166
274
|
false
|
|
@@ -190,13 +298,21 @@ module ActiveRecord
|
|
|
190
298
|
|
|
191
299
|
def reset!
|
|
192
300
|
reset_transaction
|
|
193
|
-
do_execute
|
|
301
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def configure_connection
|
|
305
|
+
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
|
306
|
+
@version_year = version_year
|
|
307
|
+
|
|
308
|
+
initialize_dateformatter
|
|
309
|
+
use_database
|
|
194
310
|
end
|
|
195
311
|
|
|
196
312
|
# === Abstract Adapter (Misc Support) =========================== #
|
|
197
313
|
|
|
198
314
|
def tables_with_referential_integrity
|
|
199
|
-
schemas_and_tables = select_rows
|
|
315
|
+
schemas_and_tables = select_rows <<~SQL.squish
|
|
200
316
|
SELECT DISTINCT s.name, o.name
|
|
201
317
|
FROM sys.foreign_keys i
|
|
202
318
|
INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
|
|
@@ -225,6 +341,7 @@ module ActiveRecord
|
|
|
225
341
|
|
|
226
342
|
def database_prefix_remote_server?
|
|
227
343
|
return false if database_prefix.blank?
|
|
344
|
+
|
|
228
345
|
name = SQLServer::Utils.extract_identifiers(database_prefix)
|
|
229
346
|
name.fully_qualified? && name.object.blank?
|
|
230
347
|
end
|
|
@@ -256,87 +373,115 @@ module ActiveRecord
|
|
|
256
373
|
result
|
|
257
374
|
end
|
|
258
375
|
|
|
376
|
+
def get_database_version # :nodoc:
|
|
377
|
+
version_year
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
class << self
|
|
381
|
+
protected
|
|
382
|
+
|
|
383
|
+
def initialize_type_map(m)
|
|
384
|
+
m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
|
|
385
|
+
|
|
386
|
+
# Exact Numerics
|
|
387
|
+
register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
|
|
388
|
+
m.alias_type "bigint", "bigint(8)"
|
|
389
|
+
register_class_with_limit m, "int(4)", SQLServer::Type::Integer
|
|
390
|
+
m.alias_type "integer", "int(4)"
|
|
391
|
+
m.alias_type "int", "int(4)"
|
|
392
|
+
register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
|
|
393
|
+
m.alias_type "smallint", "smallint(2)"
|
|
394
|
+
register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
|
|
395
|
+
m.alias_type "tinyint", "tinyint(1)"
|
|
396
|
+
m.register_type "bit", SQLServer::Type::Boolean.new
|
|
397
|
+
m.register_type %r{\Adecimal}i do |sql_type|
|
|
398
|
+
scale = extract_scale(sql_type)
|
|
399
|
+
precision = extract_precision(sql_type)
|
|
400
|
+
if scale == 0
|
|
401
|
+
SQLServer::Type::DecimalWithoutScale.new(precision: precision)
|
|
402
|
+
else
|
|
403
|
+
SQLServer::Type::Decimal.new(precision: precision, scale: scale)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
m.alias_type %r{\Anumeric}i, "decimal"
|
|
407
|
+
m.register_type "money", SQLServer::Type::Money.new
|
|
408
|
+
m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
|
|
409
|
+
|
|
410
|
+
# Approximate Numerics
|
|
411
|
+
m.register_type "float", SQLServer::Type::Float.new
|
|
412
|
+
m.register_type "real", SQLServer::Type::Real.new
|
|
413
|
+
|
|
414
|
+
# Date and Time
|
|
415
|
+
m.register_type "date", SQLServer::Type::Date.new
|
|
416
|
+
m.register_type %r{\Adatetime} do |sql_type|
|
|
417
|
+
precision = extract_precision(sql_type)
|
|
418
|
+
if precision
|
|
419
|
+
SQLServer::Type::DateTime2.new precision: precision
|
|
420
|
+
else
|
|
421
|
+
SQLServer::Type::DateTime.new
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
m.register_type %r{\Adatetimeoffset}i do |sql_type|
|
|
425
|
+
precision = extract_precision(sql_type)
|
|
426
|
+
SQLServer::Type::DateTimeOffset.new precision: precision
|
|
427
|
+
end
|
|
428
|
+
m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
|
|
429
|
+
m.register_type %r{\Atime}i do |sql_type|
|
|
430
|
+
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
|
431
|
+
SQLServer::Type::Time.new precision: precision
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Character Strings
|
|
435
|
+
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
|
436
|
+
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
|
437
|
+
m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
|
|
438
|
+
m.register_type "text", SQLServer::Type::Text.new
|
|
439
|
+
|
|
440
|
+
# Unicode Character Strings
|
|
441
|
+
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
|
442
|
+
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
|
443
|
+
m.alias_type "string", "nvarchar(4000)"
|
|
444
|
+
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
|
445
|
+
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
|
446
|
+
m.register_type "ntext", SQLServer::Type::UnicodeText.new
|
|
447
|
+
|
|
448
|
+
# Binary Strings
|
|
449
|
+
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
|
450
|
+
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
|
451
|
+
m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
|
|
452
|
+
|
|
453
|
+
# Other Data Types
|
|
454
|
+
m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
|
|
455
|
+
m.register_type "timestamp", SQLServer::Type::Timestamp.new
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
|
259
460
|
|
|
260
461
|
protected
|
|
261
462
|
|
|
262
463
|
# === Abstract Adapter (Misc Support) =========================== #
|
|
263
464
|
|
|
264
|
-
def
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
register_class_with_limit m, 'int(4)', SQLServer::Type::Integer
|
|
270
|
-
m.alias_type 'integer', 'int(4)'
|
|
271
|
-
m.alias_type 'int', 'int(4)'
|
|
272
|
-
register_class_with_limit m, 'smallint(2)', SQLServer::Type::SmallInteger
|
|
273
|
-
m.alias_type 'smallint', 'smallint(2)'
|
|
274
|
-
register_class_with_limit m, 'tinyint(1)', SQLServer::Type::TinyInteger
|
|
275
|
-
m.alias_type 'tinyint', 'tinyint(1)'
|
|
276
|
-
m.register_type 'bit', SQLServer::Type::Boolean.new
|
|
277
|
-
m.register_type %r{\Adecimal}i do |sql_type|
|
|
278
|
-
scale = extract_scale(sql_type)
|
|
279
|
-
precision = extract_precision(sql_type)
|
|
280
|
-
SQLServer::Type::Decimal.new precision: precision, scale: scale
|
|
281
|
-
end
|
|
282
|
-
m.alias_type %r{\Anumeric}i, 'decimal'
|
|
283
|
-
m.register_type 'money', SQLServer::Type::Money.new
|
|
284
|
-
m.register_type 'smallmoney', SQLServer::Type::SmallMoney.new
|
|
285
|
-
# Approximate Numerics
|
|
286
|
-
m.register_type 'float', SQLServer::Type::Float.new
|
|
287
|
-
m.register_type 'real', SQLServer::Type::Real.new
|
|
288
|
-
# Date and Time
|
|
289
|
-
m.register_type 'date', SQLServer::Type::Date.new
|
|
290
|
-
m.register_type %r{\Adatetime} do |sql_type|
|
|
291
|
-
precision = extract_precision(sql_type)
|
|
292
|
-
if precision
|
|
293
|
-
SQLServer::Type::DateTime2.new precision: precision
|
|
294
|
-
else
|
|
295
|
-
SQLServer::Type::DateTime.new
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
m.register_type %r{\Adatetimeoffset}i do |sql_type|
|
|
299
|
-
precision = extract_precision(sql_type)
|
|
300
|
-
SQLServer::Type::DateTimeOffset.new precision: precision
|
|
301
|
-
end
|
|
302
|
-
m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
|
|
303
|
-
m.register_type %r{\Atime}i do |sql_type|
|
|
304
|
-
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
|
305
|
-
SQLServer::Type::Time.new precision: precision
|
|
306
|
-
end
|
|
307
|
-
# Character Strings
|
|
308
|
-
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
|
309
|
-
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
|
310
|
-
m.register_type 'varchar(max)', SQLServer::Type::VarcharMax.new
|
|
311
|
-
m.register_type 'text', SQLServer::Type::Text.new
|
|
312
|
-
# Unicode Character Strings
|
|
313
|
-
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
|
314
|
-
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
|
315
|
-
m.alias_type 'string', 'nvarchar(4000)'
|
|
316
|
-
m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
|
|
317
|
-
m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
|
|
318
|
-
m.register_type 'ntext', SQLServer::Type::UnicodeText.new
|
|
319
|
-
# Binary Strings
|
|
320
|
-
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
|
321
|
-
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
|
322
|
-
m.register_type 'varbinary(max)', SQLServer::Type::VarbinaryMax.new
|
|
323
|
-
# Other Data Types
|
|
324
|
-
m.register_type 'uniqueidentifier', SQLServer::Type::Uuid.new
|
|
325
|
-
m.register_type 'timestamp', SQLServer::Type::Timestamp.new
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def translate_exception(e, message)
|
|
465
|
+
def type_map
|
|
466
|
+
TYPE_MAP
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def translate_exception(e, message:, sql:, binds:)
|
|
329
470
|
case message
|
|
330
|
-
when /(
|
|
331
|
-
|
|
332
|
-
when /
|
|
333
|
-
|
|
471
|
+
when /(SQL Server client is not connected)|(failed to execute statement)/i
|
|
472
|
+
ConnectionNotEstablished.new(message)
|
|
473
|
+
when /(cannot insert duplicate key .* with unique index) | (violation of (unique|primary) key constraint)/i
|
|
474
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
475
|
+
when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
|
|
476
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
334
477
|
when /has been chosen as the deadlock victim/i
|
|
335
|
-
DeadlockVictim.new(message)
|
|
478
|
+
DeadlockVictim.new(message, sql: sql, binds: binds)
|
|
336
479
|
when /database .* does not exist/i
|
|
337
480
|
NoDatabaseError.new(message)
|
|
338
481
|
when /data would be truncated/
|
|
339
|
-
ValueTooLong.new(message)
|
|
482
|
+
ValueTooLong.new(message, sql: sql, binds: binds)
|
|
483
|
+
when /connection timed out/
|
|
484
|
+
StatementTimeout.new(message, sql: sql, binds: binds)
|
|
340
485
|
when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
|
|
341
486
|
pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
|
|
342
487
|
MismatchedForeignKey.new(
|
|
@@ -348,9 +493,9 @@ module ActiveRecord
|
|
|
348
493
|
primary_key: pk_id.object
|
|
349
494
|
)
|
|
350
495
|
when /Cannot insert the value NULL into column.*does not allow nulls/
|
|
351
|
-
NotNullViolation.new(message)
|
|
496
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
|
352
497
|
when /Arithmetic overflow error/
|
|
353
|
-
RangeError.new(message)
|
|
498
|
+
RangeError.new(message, sql: sql, binds: binds)
|
|
354
499
|
else
|
|
355
500
|
super
|
|
356
501
|
end
|
|
@@ -358,83 +503,21 @@ module ActiveRecord
|
|
|
358
503
|
|
|
359
504
|
# === SQLServer Specific (Connection Management) ================ #
|
|
360
505
|
|
|
361
|
-
def connect
|
|
362
|
-
config = @connection_options
|
|
363
|
-
@connection = case config[:mode]
|
|
364
|
-
when :dblib
|
|
365
|
-
dblib_connect(config)
|
|
366
|
-
end
|
|
367
|
-
@spid = _raw_select('SELECT @@SPID', fetch: :rows).first.first
|
|
368
|
-
@version_year = version_year
|
|
369
|
-
configure_connection
|
|
370
|
-
end
|
|
371
|
-
|
|
372
506
|
def connection_errors
|
|
373
507
|
@connection_errors ||= [].tap do |errors|
|
|
374
508
|
errors << TinyTds::Error if defined?(TinyTds::Error)
|
|
375
509
|
end
|
|
376
510
|
end
|
|
377
511
|
|
|
378
|
-
def dblib_connect(config)
|
|
379
|
-
TinyTds::Client.new(
|
|
380
|
-
dataserver: config[:dataserver],
|
|
381
|
-
host: config[:host],
|
|
382
|
-
port: config[:port],
|
|
383
|
-
username: config[:username],
|
|
384
|
-
password: config[:password],
|
|
385
|
-
database: config[:database],
|
|
386
|
-
tds_version: config[:tds_version] || '7.3',
|
|
387
|
-
appname: config_appname(config),
|
|
388
|
-
login_timeout: config_login_timeout(config),
|
|
389
|
-
timeout: config_timeout(config),
|
|
390
|
-
encoding: config_encoding(config),
|
|
391
|
-
azure: config[:azure],
|
|
392
|
-
contained: config[:contained]
|
|
393
|
-
).tap do |client|
|
|
394
|
-
if config[:azure]
|
|
395
|
-
client.execute('SET ANSI_NULLS ON').do
|
|
396
|
-
client.execute('SET ANSI_NULL_DFLT_ON ON').do
|
|
397
|
-
client.execute('SET ANSI_PADDING ON').do
|
|
398
|
-
client.execute('SET ANSI_WARNINGS ON').do
|
|
399
|
-
else
|
|
400
|
-
client.execute('SET ANSI_DEFAULTS ON').do
|
|
401
|
-
end
|
|
402
|
-
client.execute('SET QUOTED_IDENTIFIER ON').do
|
|
403
|
-
client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
|
|
404
|
-
client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
|
|
405
|
-
client.execute('SET TEXTSIZE 2147483647').do
|
|
406
|
-
client.execute('SET CONCAT_NULL_YIELDS_NULL ON').do
|
|
407
|
-
end
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
def config_appname(config)
|
|
411
|
-
config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
def config_login_timeout(config)
|
|
415
|
-
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
def config_timeout(config)
|
|
419
|
-
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
def config_encoding(config)
|
|
423
|
-
config[:encoding].present? ? config[:encoding] : nil
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
def configure_connection ; end
|
|
427
|
-
|
|
428
|
-
def configure_application_name ; end
|
|
429
|
-
|
|
430
512
|
def initialize_dateformatter
|
|
431
513
|
@database_dateformat = user_options_dateformat
|
|
432
514
|
a, b, c = @database_dateformat.each_char.to_a
|
|
433
|
-
|
|
515
|
+
|
|
516
|
+
[a, b, c].each { |f| f.upcase! if f == "y" }
|
|
434
517
|
dateformat = "%#{a}-%#{b}-%#{c}"
|
|
435
518
|
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
|
436
519
|
::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
|
437
|
-
::Time::DATE_FORMATS[:_sqlserver_time] =
|
|
520
|
+
::Time::DATE_FORMATS[:_sqlserver_time] = "%H:%M:%S"
|
|
438
521
|
::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
|
|
439
522
|
::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
|
|
440
523
|
time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
|
|
@@ -443,13 +526,21 @@ module ActiveRecord
|
|
|
443
526
|
|
|
444
527
|
def version_year
|
|
445
528
|
return 2016 if sqlserver_version =~ /vNext/
|
|
529
|
+
|
|
446
530
|
/SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
|
|
447
|
-
rescue StandardError
|
|
531
|
+
rescue StandardError
|
|
448
532
|
2016
|
|
449
533
|
end
|
|
450
534
|
|
|
451
535
|
def sqlserver_version
|
|
452
|
-
@sqlserver_version ||= _raw_select(
|
|
536
|
+
@sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
private
|
|
540
|
+
|
|
541
|
+
def connect
|
|
542
|
+
@connection = self.class.new_client(@connection_options)
|
|
543
|
+
configure_connection
|
|
453
544
|
end
|
|
454
545
|
end
|
|
455
546
|
end
|
|
@@ -1,28 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
|
-
|
|
5
|
+
module SQLServer
|
|
6
|
+
class Column < ConnectionAdapters::Column
|
|
7
|
+
delegate :is_identity, :is_primary, :table_name, :ordinal_position, to: :sql_type_metadata
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
def initialize(*, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil, **)
|
|
10
|
+
super
|
|
11
|
+
@is_identity = is_identity
|
|
12
|
+
@is_primary = is_primary
|
|
13
|
+
@table_name = table_name
|
|
14
|
+
@ordinal_position = ordinal_position
|
|
15
|
+
end
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
def is_identity?
|
|
18
|
+
is_identity
|
|
19
|
+
end
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
def is_primary?
|
|
22
|
+
is_primary
|
|
23
|
+
end
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
def is_utf8?
|
|
26
|
+
sql_type =~ /nvarchar|ntext|nchar/i
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def case_sensitive?
|
|
30
|
+
collation && collation.match(/_CS/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def init_with(coder)
|
|
34
|
+
@is_identity = coder["is_identity"]
|
|
35
|
+
@is_primary = coder["is_primary"]
|
|
36
|
+
@table_name = coder["table_name"]
|
|
37
|
+
@ordinal_position = coder["ordinal_position"]
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def encode_with(coder)
|
|
42
|
+
coder["is_identity"] = @is_identity
|
|
43
|
+
coder["is_primary"] = @is_primary
|
|
44
|
+
coder["table_name"] = @table_name
|
|
45
|
+
coder["ordinal_position"] = @ordinal_position
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ==(other)
|
|
50
|
+
other.is_a?(Column) &&
|
|
51
|
+
super &&
|
|
52
|
+
is_identity? == other.is_identity? &&
|
|
53
|
+
is_primary? == other.is_primary? &&
|
|
54
|
+
table_name == other.table_name &&
|
|
55
|
+
ordinal_position == other.ordinal_position
|
|
56
|
+
end
|
|
57
|
+
alias :eql? :==
|
|
58
|
+
|
|
59
|
+
def hash
|
|
60
|
+
Column.hash ^
|
|
61
|
+
super.hash ^
|
|
62
|
+
is_identity?.hash ^
|
|
63
|
+
is_primary?.hash ^
|
|
64
|
+
table_name.hash ^
|
|
65
|
+
ordinal_position.hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
21
69
|
|
|
22
|
-
|
|
23
|
-
|
|
70
|
+
# In the Rails version of this method there is an assumption that the `default` value will always be a
|
|
71
|
+
# `String` class, which must be true for the MySQL/PostgreSQL/SQLite adapters. However, in the SQL Server
|
|
72
|
+
# adapter the `default` value can also be Boolean/Date/Time/etc. Changed the implementation of this method
|
|
73
|
+
# to handle non-String `default` objects.
|
|
74
|
+
def deduplicated
|
|
75
|
+
@name = -name
|
|
76
|
+
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
|
|
77
|
+
@default = (default.is_a?(String) ? -default : default.dup.freeze) if default
|
|
78
|
+
@default_function = -default_function if default_function
|
|
79
|
+
@collation = -collation if collation
|
|
80
|
+
@comment = -comment if comment
|
|
81
|
+
freeze
|
|
82
|
+
end
|
|
24
83
|
end
|
|
25
84
|
|
|
85
|
+
SQLServerColumn = SQLServer::Column
|
|
26
86
|
end
|
|
27
87
|
end
|
|
28
88
|
end
|