activerecord-sqlserver-adapter 6.0.1 → 6.1.1.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/.github/workflows/ci.yml +26 -0
- data/CHANGELOG.md +29 -46
- data/README.md +32 -3
- data/RUNNING_UNIT_TESTS.md +1 -1
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +28 -16
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +8 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +31 -9
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -69
- data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
- data/lib/active_record/sqlserver_base.rb +9 -15
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
- data/lib/arel/visitors/sqlserver.rb +125 -40
- data/test/cases/adapter_test_sqlserver.rb +50 -16
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/coerced_tests.rb +611 -78
- data/test/cases/column_test_sqlserver.rb +9 -2
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
- data/test/cases/fetch_test_sqlserver.rb +18 -0
- data/test/cases/in_clause_test_sqlserver.rb +27 -0
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +51 -0
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +7 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +38 -2
- data/test/cases/schema_dumper_test_sqlserver.rb +14 -3
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/schema/sqlserver_specific_schema.rb +25 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +14 -12
- metadata +29 -9
- data/.travis.yml +0 -23
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
@@ -3,9 +3,13 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLServer
|
6
|
-
class SchemaCreation <
|
6
|
+
class SchemaCreation < SchemaCreation
|
7
7
|
private
|
8
8
|
|
9
|
+
def supports_index_using?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
9
13
|
def visit_TableDefinition(o)
|
10
14
|
if_not_exists = o.if_not_exists
|
11
15
|
|
@@ -29,11 +33,28 @@ module ActiveRecord
|
|
29
33
|
sql
|
30
34
|
end
|
31
35
|
|
36
|
+
def visit_CreateIndexDefinition(o)
|
37
|
+
if_not_exists = o.if_not_exists
|
38
|
+
|
39
|
+
o.if_not_exists = false
|
40
|
+
|
41
|
+
sql = super
|
42
|
+
|
43
|
+
if if_not_exists
|
44
|
+
sql = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}') #{sql}"
|
45
|
+
end
|
46
|
+
|
47
|
+
sql
|
48
|
+
end
|
49
|
+
|
32
50
|
def add_column_options!(sql, options)
|
33
51
|
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
|
34
52
|
if options[:null] == false
|
35
53
|
sql << " NOT NULL"
|
36
54
|
end
|
55
|
+
if options[:collation].present?
|
56
|
+
sql << " COLLATE #{options[:collation]}"
|
57
|
+
end
|
37
58
|
if options[:is_identity] == true
|
38
59
|
sql << " IDENTITY(1,1)"
|
39
60
|
end
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def explicit_primary_key_default?(column)
|
18
|
-
column.
|
18
|
+
column.type == :integer && !column.is_identity?
|
19
19
|
end
|
20
20
|
|
21
21
|
def schema_limit(column)
|
@@ -27,11 +27,17 @@ module ActiveRecord
|
|
27
27
|
def schema_collation(column)
|
28
28
|
return unless column.collation
|
29
29
|
|
30
|
-
|
30
|
+
# use inspect to ensure collation is dumped as string. Without this it's dumped as
|
31
|
+
# a constant ('collation: SQL_Latin1_General_CP1_CI_AS')
|
32
|
+
collation = column.collation.inspect
|
33
|
+
# use inspect to ensure string comparison
|
34
|
+
default_collation = @connection.collation.inspect
|
35
|
+
|
36
|
+
collation if collation != default_collation
|
31
37
|
end
|
32
38
|
|
33
39
|
def default_primary_key?(column)
|
34
|
-
super && column.
|
40
|
+
super && column.is_identity?
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
if options[:if_exists] && @version_year < 2016
|
30
|
-
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"
|
31
31
|
else
|
32
32
|
super
|
33
33
|
end
|
@@ -51,7 +51,7 @@ module ActiveRecord
|
|
51
51
|
index[:index_keys].split(",").each do |column|
|
52
52
|
column.strip!
|
53
53
|
|
54
|
-
if column.
|
54
|
+
if column.end_with?("(-)")
|
55
55
|
column.gsub! "(-)", ""
|
56
56
|
orders[column] = :desc
|
57
57
|
end
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
|
87
|
-
|
87
|
+
SQLServer::Column.new(
|
88
88
|
name,
|
89
89
|
default,
|
90
90
|
sql_type_metadata,
|
@@ -130,8 +130,9 @@ module ActiveRecord
|
|
130
130
|
rename_table_indexes(table_name, new_name)
|
131
131
|
end
|
132
132
|
|
133
|
-
def remove_column(table_name, column_name, type = nil, options
|
133
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
134
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)
|
135
136
|
|
136
137
|
remove_check_constraints(table_name, column_name)
|
137
138
|
remove_default_constraint(table_name, column_name)
|
@@ -156,6 +157,7 @@ module ActiveRecord
|
|
156
157
|
end
|
157
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?
|
158
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?
|
159
161
|
alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
|
160
162
|
sql_commands << alter_command
|
161
163
|
if without_constraints
|
@@ -190,7 +192,7 @@ module ActiveRecord
|
|
190
192
|
end
|
191
193
|
|
192
194
|
def rename_index(table_name, old_name, new_name)
|
193
|
-
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{
|
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
|
194
196
|
|
195
197
|
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
|
196
198
|
execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
|
@@ -281,14 +283,33 @@ module ActiveRecord
|
|
281
283
|
SQLServer::SchemaDumper.create(self, options)
|
282
284
|
end
|
283
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
|
+
|
284
301
|
private
|
285
302
|
|
286
303
|
def data_source_sql(name = nil, type: nil)
|
287
304
|
scope = quoted_scope name, type: type
|
288
|
-
|
305
|
+
|
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
|
+
|
289
310
|
sql = "SELECT #{table_name}"
|
290
|
-
sql += " FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
|
291
|
-
sql += " WHERE TABLE_CATALOG =
|
311
|
+
sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
|
312
|
+
sql += " WHERE TABLE_CATALOG = #{table_catalog}"
|
292
313
|
sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
|
293
314
|
sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
|
294
315
|
sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
|
@@ -299,6 +320,7 @@ module ActiveRecord
|
|
299
320
|
def quoted_scope(name = nil, type: nil)
|
300
321
|
identifier = SQLServer::Utils.extract_identifiers(name)
|
301
322
|
{}.tap do |scope|
|
323
|
+
scope[:database] = identifier.database if identifier.database
|
302
324
|
scope[:schema] = identifier.schema || "dbo"
|
303
325
|
scope[:name] = identifier.object if identifier.object
|
304
326
|
scope[:type] = type if type
|
@@ -310,7 +332,7 @@ module ActiveRecord
|
|
310
332
|
def initialize_native_database_types
|
311
333
|
{
|
312
334
|
primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
313
|
-
primary_key_nonclustered: "
|
335
|
+
primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
|
314
336
|
integer: { name: "int", limit: 4 },
|
315
337
|
bigint: { name: "bigint" },
|
316
338
|
boolean: { name: "bit" },
|
@@ -3,16 +3,45 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLServer
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
7
|
+
undef to_yaml if method_defined?(:to_yaml)
|
8
|
+
|
9
|
+
include Deduplicable
|
10
|
+
|
11
|
+
attr_reader :is_identity, :is_primary, :table_name, :ordinal_position
|
12
|
+
|
13
|
+
def initialize(type_metadata, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil)
|
14
|
+
super(type_metadata)
|
15
|
+
@is_identity = is_identity
|
16
|
+
@is_primary = is_primary
|
17
|
+
@table_name = table_name
|
18
|
+
@ordinal_position = ordinal_position
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
other.is_a?(TypeMetadata) &&
|
23
|
+
__getobj__ == other.__getobj__ &&
|
24
|
+
is_identity == other.is_identity &&
|
25
|
+
is_primary == other.is_primary &&
|
26
|
+
table_name == other.table_name &&
|
27
|
+
ordinal_position == other.ordinal_position
|
28
|
+
end
|
29
|
+
alias eql? ==
|
30
|
+
|
31
|
+
def hash
|
32
|
+
TypeMetadata.hash ^
|
33
|
+
__getobj__.hash ^
|
34
|
+
is_identity.hash ^
|
35
|
+
is_primary.hash ^
|
36
|
+
table_name.hash ^
|
37
|
+
ordinal_position.hash
|
10
38
|
end
|
11
39
|
|
12
|
-
|
40
|
+
private
|
13
41
|
|
14
|
-
def
|
15
|
-
|
42
|
+
def deduplicated
|
43
|
+
__setobj__(__getobj__.deduplicate)
|
44
|
+
super
|
16
45
|
end
|
17
46
|
end
|
18
47
|
end
|
@@ -31,9 +31,9 @@ module ActiveRecord
|
|
31
31
|
module SQLServerRealTransaction
|
32
32
|
attr_reader :starting_isolation_level
|
33
33
|
|
34
|
-
def initialize(connection,
|
34
|
+
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
|
35
35
|
@connection = connection
|
36
|
-
@starting_isolation_level = current_isolation_level if
|
36
|
+
@starting_isolation_level = current_isolation_level if isolation
|
37
37
|
super
|
38
38
|
end
|
39
39
|
|
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/sqlserver/type/small_integer"
|
|
11
11
|
require "active_record/connection_adapters/sqlserver/type/tiny_integer"
|
12
12
|
require "active_record/connection_adapters/sqlserver/type/boolean"
|
13
13
|
require "active_record/connection_adapters/sqlserver/type/decimal"
|
14
|
+
require "active_record/connection_adapters/sqlserver/type/decimal_without_scale"
|
14
15
|
require "active_record/connection_adapters/sqlserver/type/money"
|
15
16
|
require "active_record/connection_adapters/sqlserver/type/small_money"
|
16
17
|
# Approximate Numerics
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLServer
|
6
|
+
module Type
|
7
|
+
class DecimalWithoutScale < ActiveRecord::Type::DecimalWithoutScale
|
8
|
+
def sqlserver_type
|
9
|
+
"decimal".yield_self do |type|
|
10
|
+
type += "(#{precision.to_i},0)" if precision
|
11
|
+
type
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def type_cast_for_schema(value)
|
16
|
+
value.is_a?(BigDecimal) ? value.to_s : value.inspect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -10,7 +10,6 @@ require "active_record/connection_adapters/sqlserver/core_ext/explain"
|
|
10
10
|
require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
|
11
11
|
require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
|
12
12
|
require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
|
13
|
-
require "active_record/connection_adapters/sqlserver/core_ext/query_methods"
|
14
13
|
require "active_record/connection_adapters/sqlserver/core_ext/preloader"
|
15
14
|
require "active_record/connection_adapters/sqlserver/version"
|
16
15
|
require "active_record/connection_adapters/sqlserver/type"
|
@@ -59,13 +58,73 @@ module ActiveRecord
|
|
59
58
|
self.use_output_inserted = true
|
60
59
|
self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
|
61
60
|
|
62
|
-
|
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
|
+
config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def config_login_timeout(config)
|
112
|
+
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def config_timeout(config)
|
116
|
+
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def config_encoding(config)
|
120
|
+
config[:encoding].present? ? config[:encoding] : nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize(connection, logger, _connection_options, config)
|
63
125
|
super(connection, logger, config)
|
64
|
-
# Our Responsibility
|
65
126
|
@connection_options = config
|
66
|
-
|
67
|
-
initialize_dateformatter
|
68
|
-
use_database
|
127
|
+
configure_connection
|
69
128
|
end
|
70
129
|
|
71
130
|
# === Abstract Adapter ========================================== #
|
@@ -152,6 +211,10 @@ module ActiveRecord
|
|
152
211
|
true
|
153
212
|
end
|
154
213
|
|
214
|
+
def supports_optimizer_hints?
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
155
218
|
def supports_lazy_transactions?
|
156
219
|
true
|
157
220
|
end
|
@@ -222,6 +285,14 @@ module ActiveRecord
|
|
222
285
|
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
223
286
|
end
|
224
287
|
|
288
|
+
def configure_connection
|
289
|
+
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
290
|
+
@version_year = version_year
|
291
|
+
|
292
|
+
initialize_dateformatter
|
293
|
+
use_database
|
294
|
+
end
|
295
|
+
|
225
296
|
# === Abstract Adapter (Misc Support) =========================== #
|
226
297
|
|
227
298
|
def tables_with_referential_integrity
|
@@ -296,6 +367,7 @@ module ActiveRecord
|
|
296
367
|
|
297
368
|
def initialize_type_map(m = type_map)
|
298
369
|
m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
|
370
|
+
|
299
371
|
# Exact Numerics
|
300
372
|
register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
|
301
373
|
m.alias_type "bigint", "bigint(8)"
|
@@ -308,16 +380,22 @@ module ActiveRecord
|
|
308
380
|
m.alias_type "tinyint", "tinyint(1)"
|
309
381
|
m.register_type "bit", SQLServer::Type::Boolean.new
|
310
382
|
m.register_type %r{\Adecimal}i do |sql_type|
|
311
|
-
scale
|
383
|
+
scale = extract_scale(sql_type)
|
312
384
|
precision = extract_precision(sql_type)
|
313
|
-
|
385
|
+
if scale == 0
|
386
|
+
SQLServer::Type::DecimalWithoutScale.new(precision: precision)
|
387
|
+
else
|
388
|
+
SQLServer::Type::Decimal.new(precision: precision, scale: scale)
|
389
|
+
end
|
314
390
|
end
|
315
391
|
m.alias_type %r{\Anumeric}i, "decimal"
|
316
392
|
m.register_type "money", SQLServer::Type::Money.new
|
317
393
|
m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
|
394
|
+
|
318
395
|
# Approximate Numerics
|
319
396
|
m.register_type "float", SQLServer::Type::Float.new
|
320
397
|
m.register_type "real", SQLServer::Type::Real.new
|
398
|
+
|
321
399
|
# Date and Time
|
322
400
|
m.register_type "date", SQLServer::Type::Date.new
|
323
401
|
m.register_type %r{\Adatetime} do |sql_type|
|
@@ -337,11 +415,13 @@ module ActiveRecord
|
|
337
415
|
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
338
416
|
SQLServer::Type::Time.new precision: precision
|
339
417
|
end
|
418
|
+
|
340
419
|
# Character Strings
|
341
420
|
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
342
421
|
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
343
422
|
m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
|
344
423
|
m.register_type "text", SQLServer::Type::Text.new
|
424
|
+
|
345
425
|
# Unicode Character Strings
|
346
426
|
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
347
427
|
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
@@ -349,10 +429,12 @@ module ActiveRecord
|
|
349
429
|
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
350
430
|
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
351
431
|
m.register_type "ntext", SQLServer::Type::UnicodeText.new
|
432
|
+
|
352
433
|
# Binary Strings
|
353
434
|
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
354
435
|
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
355
436
|
m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
|
437
|
+
|
356
438
|
# Other Data Types
|
357
439
|
m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
|
358
440
|
m.register_type "timestamp", SQLServer::Type::Timestamp.new
|
@@ -360,6 +442,8 @@ module ActiveRecord
|
|
360
442
|
|
361
443
|
def translate_exception(e, message:, sql:, binds:)
|
362
444
|
case message
|
445
|
+
when /(SQL Server client is not connected)|(failed to execute statement)/i
|
446
|
+
ConnectionNotEstablished.new(message)
|
363
447
|
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
364
448
|
RecordNotUnique.new(message, sql: sql, binds: binds)
|
365
449
|
when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
|
@@ -393,78 +477,18 @@ module ActiveRecord
|
|
393
477
|
|
394
478
|
# === SQLServer Specific (Connection Management) ================ #
|
395
479
|
|
396
|
-
def connect
|
397
|
-
config = @connection_options
|
398
|
-
@connection = case config[:mode]
|
399
|
-
when :dblib
|
400
|
-
dblib_connect(config)
|
401
|
-
end
|
402
|
-
@spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
|
403
|
-
@version_year = version_year
|
404
|
-
configure_connection
|
405
|
-
end
|
406
|
-
|
407
480
|
def connection_errors
|
408
481
|
@connection_errors ||= [].tap do |errors|
|
409
482
|
errors << TinyTds::Error if defined?(TinyTds::Error)
|
410
483
|
end
|
411
484
|
end
|
412
485
|
|
413
|
-
def dblib_connect(config)
|
414
|
-
TinyTds::Client.new(
|
415
|
-
dataserver: config[:dataserver],
|
416
|
-
host: config[:host],
|
417
|
-
port: config[:port],
|
418
|
-
username: config[:username],
|
419
|
-
password: config[:password],
|
420
|
-
database: config[:database],
|
421
|
-
tds_version: config[:tds_version] || "7.3",
|
422
|
-
appname: config_appname(config),
|
423
|
-
login_timeout: config_login_timeout(config),
|
424
|
-
timeout: config_timeout(config),
|
425
|
-
encoding: config_encoding(config),
|
426
|
-
azure: config[:azure],
|
427
|
-
contained: config[:contained]
|
428
|
-
).tap do |client|
|
429
|
-
if config[:azure]
|
430
|
-
client.execute("SET ANSI_NULLS ON").do
|
431
|
-
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
432
|
-
client.execute("SET ANSI_PADDING ON").do
|
433
|
-
client.execute("SET ANSI_WARNINGS ON").do
|
434
|
-
else
|
435
|
-
client.execute("SET ANSI_DEFAULTS ON").do
|
436
|
-
end
|
437
|
-
client.execute("SET QUOTED_IDENTIFIER ON").do
|
438
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
439
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
440
|
-
client.execute("SET TEXTSIZE 2147483647").do
|
441
|
-
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
442
|
-
end
|
443
|
-
end
|
444
|
-
|
445
|
-
def config_appname(config)
|
446
|
-
config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
|
447
|
-
end
|
448
|
-
|
449
|
-
def config_login_timeout(config)
|
450
|
-
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
451
|
-
end
|
452
|
-
|
453
|
-
def config_timeout(config)
|
454
|
-
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
455
|
-
end
|
456
|
-
|
457
|
-
def config_encoding(config)
|
458
|
-
config[:encoding].present? ? config[:encoding] : nil
|
459
|
-
end
|
460
|
-
|
461
|
-
def configure_connection; end
|
462
|
-
|
463
486
|
def configure_application_name; end
|
464
487
|
|
465
488
|
def initialize_dateformatter
|
466
489
|
@database_dateformat = user_options_dateformat
|
467
490
|
a, b, c = @database_dateformat.each_char.to_a
|
491
|
+
|
468
492
|
[a, b, c].each { |f| f.upcase! if f == "y" }
|
469
493
|
dateformat = "%#{a}-%#{b}-%#{c}"
|
470
494
|
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
@@ -487,6 +511,13 @@ module ActiveRecord
|
|
487
511
|
def sqlserver_version
|
488
512
|
@sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
|
489
513
|
end
|
514
|
+
|
515
|
+
private
|
516
|
+
|
517
|
+
def connect
|
518
|
+
@connection = self.class.new_client(@connection_options)
|
519
|
+
configure_connection
|
520
|
+
end
|
490
521
|
end
|
491
522
|
end
|
492
523
|
end
|