activerecord-sqlserver-adapter-odbc-extended 8.0.10
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 +7 -0
- data/.github/issue_template.md +22 -0
- data/.github/workflows/ci.yml +32 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile.ci +14 -0
- data/Gemfile +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/RUNNING_UNIT_TESTS.md +38 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
- data/compose.ci.yaml +15 -0
- data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
- data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +648 -0
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +44 -0
- data/test/cases/coerced_tests.rb +2796 -0
- data/test/cases/column_test_sqlserver.rb +848 -0
- data/test/cases/connection_test_sqlserver.rb +138 -0
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +42 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
- data/test/cases/fetch_test_sqlserver.rb +88 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
- data/test/cases/helper_sqlserver.rb +61 -0
- data/test/cases/migration_test_sqlserver.rb +144 -0
- data/test/cases/order_test_sqlserver.rb +153 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +198 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
- data/test/cases/schema_test_sqlserver.rb +111 -0
- data/test/cases/trigger_test_sqlserver.rb +51 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +54 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +38 -0
- data/test/debug.rb +16 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/booking.rb +5 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/customers_view.rb +5 -0
- data/test/models/sqlserver/datatype.rb +5 -0
- data/test/models/sqlserver/datatype_migration.rb +10 -0
- data/test/models/sqlserver/dollar_table_name.rb +5 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +5 -0
- data/test/models/sqlserver/fk_has_pk.rb +5 -0
- data/test/models/sqlserver/natural_pk_data.rb +6 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
- data/test/models/sqlserver/no_pk_data.rb +5 -0
- data/test/models/sqlserver/object_default.rb +5 -0
- data/test/models/sqlserver/quoted_table.rb +9 -0
- data/test/models/sqlserver/quoted_view_1.rb +5 -0
- data/test/models/sqlserver/quoted_view_2.rb +5 -0
- data/test/models/sqlserver/sst_memory.rb +5 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +5 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
- data/test/models/sqlserver/string_defaults_view.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/tinyint_pk.rb +5 -0
- data/test/models/sqlserver/trigger.rb +17 -0
- data/test/models/sqlserver/trigger_history.rb +5 -0
- data/test/models/sqlserver/upper.rb +5 -0
- data/test/models/sqlserver/uppered.rb +5 -0
- data/test/models/sqlserver/uuid.rb +5 -0
- data/test/schema/datatypes/2012.sql +56 -0
- data/test/schema/enable-in-memory-oltp.sql +81 -0
- data/test/schema/sqlserver_specific_schema.rb +363 -0
- data/test/support/coerceable_test_sqlserver.rb +55 -0
- data/test/support/connection_reflection.rb +32 -0
- data/test/support/core_ext/query_cache.rb +38 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- 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/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +46 -0
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +17 -0
- metadata +240 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
require "models/reply"
|
|
5
|
+
require "models/topic"
|
|
6
|
+
|
|
7
|
+
class ConnectionTestSQLServer < ActiveRecord::TestCase
|
|
8
|
+
self.use_transactional_tests = false
|
|
9
|
+
|
|
10
|
+
fixtures :topics, :accounts
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
connection.reconnect!
|
|
14
|
+
assert connection.active?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "affect rows" do
|
|
18
|
+
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
|
|
19
|
+
updated = Topic.update(topic_data.keys, topic_data.values)
|
|
20
|
+
assert_equal 2, updated.size
|
|
21
|
+
assert_equal "1 updated", Topic.find(1).content
|
|
22
|
+
assert_equal "2 updated", Topic.find(2).content
|
|
23
|
+
assert_equal 2, Topic.delete([1, 2])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "allow usage of :database connection option to remove setting from dsn" do
|
|
27
|
+
assert_equal "activerecord_unittest", connection.current_database
|
|
28
|
+
begin
|
|
29
|
+
connection.use_database("activerecord_unittest2")
|
|
30
|
+
assert_equal "activerecord_unittest2", connection.current_database
|
|
31
|
+
ensure
|
|
32
|
+
connection.use_database
|
|
33
|
+
assert_equal "activerecord_unittest", connection.current_database, "Would default back to connection options"
|
|
34
|
+
end
|
|
35
|
+
end unless connection_sqlserver_azure?
|
|
36
|
+
|
|
37
|
+
describe 'ODBC connection management' do
|
|
38
|
+
it "return finished ODBC statement handle from #execute without block" do
|
|
39
|
+
assert_all_odbc_statements_used_are_closed do
|
|
40
|
+
connection.execute('SELECT * FROM [topics]')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "finish ODBC statement handle from #execute with block" do
|
|
45
|
+
assert_all_odbc_statements_used_are_closed do
|
|
46
|
+
connection.execute('SELECT * FROM [topics]') { }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "finish connection from #raw_select" do
|
|
51
|
+
assert_all_odbc_statements_used_are_closed do
|
|
52
|
+
connection.send(:raw_select,'SELECT * FROM [topics]')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "execute without block closes statement" do
|
|
57
|
+
assert_all_odbc_statements_used_are_closed do
|
|
58
|
+
connection.execute("SELECT 1")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "execute with block closes statement" do
|
|
63
|
+
assert_all_odbc_statements_used_are_closed do
|
|
64
|
+
connection.execute("SELECT 1") do |sth|
|
|
65
|
+
assert !sth.finished?, "Statement should still be alive within block"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "insert with identity closes statement" do
|
|
71
|
+
assert_all_odbc_statements_used_are_closed do
|
|
72
|
+
connection.exec_insert "INSERT INTO accounts ([id],[firm_id],[credit_limit]) VALUES (999, 1, 50)", "SQL", []
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "insert without identity closes statement" do
|
|
77
|
+
assert_all_odbc_statements_used_are_closed do
|
|
78
|
+
connection.exec_insert "INSERT INTO accounts ([firm_id],[credit_limit]) VALUES (1, 50)", "SQL", []
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "active closes statement" do
|
|
83
|
+
assert_all_odbc_statements_used_are_closed do
|
|
84
|
+
connection.active?
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end if connection_odbc?
|
|
88
|
+
|
|
89
|
+
describe "Connection management" do
|
|
90
|
+
it "set spid on connect" do
|
|
91
|
+
_(["Fixnum", "Integer"]).must_include connection.spid.class.name
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "reset spid on disconnect!" do
|
|
95
|
+
connection.disconnect!
|
|
96
|
+
assert connection.spid.nil?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "reset raw connection on disconnect!" do
|
|
100
|
+
connection.disconnect!
|
|
101
|
+
_(connection.instance_variable_get(:@raw_connection)).must_be_nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "be able to disconnect and reconnect at will" do
|
|
105
|
+
disconnect_raw_connection!
|
|
106
|
+
assert !connection.active?
|
|
107
|
+
connection.reconnect!
|
|
108
|
+
assert connection.active?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def disconnect_raw_connection!
|
|
115
|
+
connection_options[:mode]
|
|
116
|
+
when :dblib
|
|
117
|
+
connection.raw_connection.close rescue nil
|
|
118
|
+
when :odbc
|
|
119
|
+
connection.raw_connection.disconnect rescue nil
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def assert_all_odbc_statements_used_are_closed(&block)
|
|
124
|
+
odbc = connection.raw_connection.class.parent
|
|
125
|
+
existing_handles = []
|
|
126
|
+
ObjectSpace.each_object(odbc::Statement) { |h| existing_handles << h }
|
|
127
|
+
existing_handle_ids = existing_handles.map(&:object_id)
|
|
128
|
+
assert existing_handles.all?(&:finished?), "Somewhere before the block some statements were not closed"
|
|
129
|
+
GC.disable
|
|
130
|
+
yield
|
|
131
|
+
used_handles = []
|
|
132
|
+
ObjectSpace.each_object(odbc::Statement) { |h| used_handles << h unless existing_handle_ids.include?(h.object_id) }
|
|
133
|
+
assert used_handles.size > 0, "No statements were used within given block"
|
|
134
|
+
assert used_handles.all?(&:finished?), "Statement should have been closed within given block"
|
|
135
|
+
ensure
|
|
136
|
+
GC.enable
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DbConsole < ActiveRecord::TestCase
|
|
4
|
+
subject { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter }
|
|
5
|
+
|
|
6
|
+
it "uses sqlplus to connect to database" do
|
|
7
|
+
subject.expects(:find_cmd_and_exec).with("sqlcmd", "-d", "db", "-U", "user", "-P", "secret", "-S", "tcp:localhost,1433")
|
|
8
|
+
|
|
9
|
+
config = make_db_config(adapter: "sqlserver", database: "db", username: "user", password: "secret", host: "localhost", port: 1433)
|
|
10
|
+
|
|
11
|
+
subject.dbconsole(config)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def make_db_config(config)
|
|
17
|
+
ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class TestDisconnectedAdapter < ActiveRecord::TestCase
|
|
6
|
+
self.use_transactional_tests = false
|
|
7
|
+
|
|
8
|
+
undef_method :setup
|
|
9
|
+
def setup
|
|
10
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
teardown do
|
|
14
|
+
return if in_memory_db?
|
|
15
|
+
db_config = ActiveRecord::Base.connection_db_config
|
|
16
|
+
ActiveRecord::Base.establish_connection(db_config)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "execute procedure after disconnect reconnects" do
|
|
20
|
+
@connection.execute_procedure :sp_tables, "sst_datatypes"
|
|
21
|
+
@connection.disconnect!
|
|
22
|
+
|
|
23
|
+
assert_nothing_raised do
|
|
24
|
+
@connection.execute_procedure :sp_tables, "sst_datatypes"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "execute query after disconnect reconnects" do
|
|
29
|
+
sql = "SELECT count(*) from products WHERE id IN(@0, @1)"
|
|
30
|
+
binds = [
|
|
31
|
+
ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new),
|
|
32
|
+
ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
@connection.exec_query sql, "TEST", binds
|
|
36
|
+
@connection.disconnect!
|
|
37
|
+
|
|
38
|
+
assert_nothing_raised do
|
|
39
|
+
@connection.exec_query sql, "TEST", binds
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "cases/helper_sqlserver"
|
|
2
|
+
require "models/citation"
|
|
3
|
+
require "models/book"
|
|
4
|
+
|
|
5
|
+
class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
|
6
|
+
fixtures :citations
|
|
7
|
+
|
|
8
|
+
def test_batch_preloading_too_many_ids
|
|
9
|
+
in_clause_length = 10_000
|
|
10
|
+
|
|
11
|
+
# We Monkey patch Preloader to work with batches of 10_000 records.
|
|
12
|
+
# Expect: N Books queries + Citation query
|
|
13
|
+
expected_query_count = (Citation.count / in_clause_length.to_f).ceil + 1
|
|
14
|
+
assert_queries_count(expected_query_count) do
|
|
15
|
+
Citation.preload(:reference_of).to_a.size
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class EnumTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
|
|
7
|
+
# Check that enums are supported for all string types.
|
|
8
|
+
# For each type we check: cast, serialize, and update by declaration.
|
|
9
|
+
# We create a custom class for each type to test.
|
|
10
|
+
%w[char_10 varchar_50 varchar_max text nchar_10 nvarchar_50 nvarchar_max ntext].each do |col_name|
|
|
11
|
+
describe "support #{col_name} enums" do
|
|
12
|
+
let(:klass) do
|
|
13
|
+
Class.new(ActiveRecord::Base) do
|
|
14
|
+
self.table_name = 'sst_datatypes'
|
|
15
|
+
|
|
16
|
+
enum col_name, { alpha: "A", beta: "B" }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "type.cast" do
|
|
21
|
+
type = klass.type_for_attribute(col_name)
|
|
22
|
+
|
|
23
|
+
assert_equal "alpha", type.cast('A')
|
|
24
|
+
assert_equal "beta", type.cast('B')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "type.serialize" do
|
|
28
|
+
type = klass.type_for_attribute(col_name)
|
|
29
|
+
|
|
30
|
+
assert_equal 'A', type.serialize('A')
|
|
31
|
+
assert_equal 'B', type.serialize('B')
|
|
32
|
+
|
|
33
|
+
assert_equal 'A', type.serialize(:alpha)
|
|
34
|
+
assert_equal 'B', type.serialize(:beta)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "update by declaration" do
|
|
38
|
+
r = klass.new
|
|
39
|
+
|
|
40
|
+
r.alpha!
|
|
41
|
+
assert_predicate r, :alpha?
|
|
42
|
+
|
|
43
|
+
r.beta!
|
|
44
|
+
assert_not_predicate r, :alpha?
|
|
45
|
+
assert_predicate r, :beta?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class ExecuteProcedureTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
it "execute a simple procedure" do
|
|
7
|
+
tables = ActiveRecord::Base.execute_procedure :sp_tables
|
|
8
|
+
assert_instance_of Array, tables
|
|
9
|
+
assert tables.first.respond_to?(:keys)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "take parameter arguments" do
|
|
13
|
+
tables = ActiveRecord::Base.execute_procedure :sp_tables, "sst_datatypes"
|
|
14
|
+
table_info = tables.first
|
|
15
|
+
assert_equal 1, tables.size
|
|
16
|
+
assert_equal (ENV["ARUNIT_DB_NAME"] || "activerecord_unittest"), table_info["TABLE_QUALIFIER"], "Table Info: #{table_info.inspect}"
|
|
17
|
+
assert_equal "TABLE", table_info["TABLE_TYPE"], "Table Info: #{table_info.inspect}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "allow multiple result sets to be returned" do
|
|
21
|
+
results1, results2 = ActiveRecord::Base.execute_procedure("sp_helpconstraint", "accounts")
|
|
22
|
+
assert_instance_of Array, results1
|
|
23
|
+
assert results1.first.respond_to?(:keys)
|
|
24
|
+
assert results1.first["Object Name"]
|
|
25
|
+
assert_instance_of Array, results2
|
|
26
|
+
assert results2.first.respond_to?(:keys)
|
|
27
|
+
assert results2.first["constraint_name"]
|
|
28
|
+
assert results2.first["constraint_type"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "take named parameter arguments" do
|
|
32
|
+
tables = ActiveRecord::Base.execute_procedure :sp_tables, table_name: "tables", table_owner: "sys"
|
|
33
|
+
table_info = tables.first
|
|
34
|
+
assert_equal 1, tables.size
|
|
35
|
+
assert_equal (ENV["ARUNIT_DB_NAME"] || "activerecord_unittest"), table_info["TABLE_QUALIFIER"], "Table Info: #{table_info.inspect}"
|
|
36
|
+
assert_equal "VIEW", table_info["TABLE_TYPE"], "Table Info: #{table_info.inspect}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "uses the proper timezone" do
|
|
40
|
+
date_proc = connection.execute_procedure("my_getutcdate").first["utcdate"]
|
|
41
|
+
date_base = connection.select_value("select GETUTCDATE()")
|
|
42
|
+
assert_equal date_base.change(usec: 0), date_proc.change(usec: 0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def transaction_with_procedure_and_return
|
|
46
|
+
ActiveRecord::Base.transaction do
|
|
47
|
+
connection.execute_procedure("my_getutcdate")
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'test deprecation with transaction return when executing procedure' do
|
|
53
|
+
assert_not_deprecated(ActiveRecord.deprecator) do
|
|
54
|
+
transaction_with_procedure_and_return
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
require "models/book"
|
|
5
|
+
|
|
6
|
+
class FetchTestSqlserver < ActiveRecord::TestCase
|
|
7
|
+
let(:books) { @books }
|
|
8
|
+
|
|
9
|
+
before { create_10_books }
|
|
10
|
+
|
|
11
|
+
it "work with fully qualified table and columns in select" do
|
|
12
|
+
books = Book.select("books.id, books.name").limit(3).offset(5)
|
|
13
|
+
assert_equal Book.all[5, 3].map(&:id), books.map(&:id)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "count" do
|
|
17
|
+
it "gauntlet" do
|
|
18
|
+
books[0].destroy
|
|
19
|
+
books[1].destroy
|
|
20
|
+
books[2].destroy
|
|
21
|
+
assert_equal 7, Book.count
|
|
22
|
+
assert_equal 1, Book.limit(1).offset(1).count
|
|
23
|
+
assert_equal 1, Book.limit(1).offset(5).count
|
|
24
|
+
assert_equal 1, Book.limit(1).offset(6).count
|
|
25
|
+
assert_equal 0, Book.limit(1).offset(7).count
|
|
26
|
+
assert_equal 3, Book.limit(3).offset(4).count
|
|
27
|
+
assert_equal 2, Book.limit(3).offset(5).count
|
|
28
|
+
assert_equal 1, Book.limit(3).offset(6).count
|
|
29
|
+
assert_equal 0, Book.limit(3).offset(7).count
|
|
30
|
+
assert_equal 0, Book.limit(3).offset(8).count
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "order" do
|
|
35
|
+
it "gauntlet" do
|
|
36
|
+
Book.where(name: "Name-10").delete_all
|
|
37
|
+
_(Book.order(:name).limit(1).offset(1).map(&:name)).must_equal ["Name-2"]
|
|
38
|
+
_(Book.order(:name).limit(2).offset(2).map(&:name)).must_equal ["Name-3", "Name-4"]
|
|
39
|
+
_(Book.order(:name).limit(2).offset(7).map(&:name)).must_equal ["Name-8", "Name-9"]
|
|
40
|
+
_(Book.order(:name).limit(3).offset(7).map(&:name)).must_equal ["Name-8", "Name-9"]
|
|
41
|
+
_(Book.order(:name).limit(3).offset(9).map(&:name)).must_equal []
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "FROM subquery" do
|
|
46
|
+
let(:from_sql) { "(SELECT [books].* FROM [books]) [books]" }
|
|
47
|
+
|
|
48
|
+
it "SQL generated correctly for FROM subquery if order provided" do
|
|
49
|
+
query = Book.from(from_sql).order(:id).limit(5)
|
|
50
|
+
|
|
51
|
+
assert_equal query.to_sql, "SELECT [books].* FROM (SELECT [books].* FROM [books]) [books] ORDER BY [books].[id] ASC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY"
|
|
52
|
+
assert_equal query.to_a.count, 5
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "exception thrown if FROM subquery is provided without an order" do
|
|
56
|
+
query = Book.from(from_sql).limit(5)
|
|
57
|
+
|
|
58
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
|
59
|
+
query.to_sql
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
protected
|
|
65
|
+
|
|
66
|
+
def create_10_books
|
|
67
|
+
Book.delete_all
|
|
68
|
+
@books = (1..10).map { |i| Book.create! name: "Name-#{i}" }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class DeterministicFetchWithCompositePkTestSQLServer < ActiveRecord::TestCase
|
|
73
|
+
it "orders by the identity column if table has one" do
|
|
74
|
+
SSCompositePkWithIdentity.delete_all
|
|
75
|
+
SSCompositePkWithIdentity.create(pk_col_two: 2)
|
|
76
|
+
SSCompositePkWithIdentity.create(pk_col_two: 1)
|
|
77
|
+
|
|
78
|
+
_(SSCompositePkWithIdentity.take(1).map(&:pk_col_two)).must_equal [2]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "orders by the first column if table has no identity column" do
|
|
82
|
+
SSCompositePkWithoutIdentity.delete_all
|
|
83
|
+
SSCompositePkWithoutIdentity.create(pk_col_one: 2, pk_col_two: 2)
|
|
84
|
+
SSCompositePkWithoutIdentity.create(pk_col_one: 1, pk_col_two: 1)
|
|
85
|
+
|
|
86
|
+
_(SSCompositePkWithoutIdentity.take(1).map(&:pk_col_two)).must_equal [1]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class FullyQualifiedIdentifierTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
describe "local server" do
|
|
7
|
+
it "should use table name in select projections" do
|
|
8
|
+
table = Arel::Table.new(:table)
|
|
9
|
+
expected_sql = "SELECT [table].[name] FROM [table]"
|
|
10
|
+
assert_equal expected_sql, table.project(table[:name]).to_sql
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "remote server" do
|
|
15
|
+
before do
|
|
16
|
+
connection_options[:database_prefix] = "[my.server].db.schema."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after do
|
|
20
|
+
connection_options.delete :database_prefix
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should use fully qualified table name in select from clause" do
|
|
24
|
+
table = Arel::Table.new(:table)
|
|
25
|
+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table]"
|
|
26
|
+
assert_equal expected_sql, table.project(Arel.star).to_sql
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should not use fully qualified table name in select projections" do
|
|
30
|
+
table = Arel::Table.new(:table)
|
|
31
|
+
expected_sql = "SELECT [table].[name] FROM [my.server].[db].[schema].[table]"
|
|
32
|
+
assert_equal expected_sql, table.project(table[:name]).to_sql
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should not use fully qualified table name in where clause" do
|
|
36
|
+
table = Arel::Table.new(:table)
|
|
37
|
+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table] WHERE [table].[id] = 42"
|
|
38
|
+
quietly { assert_equal expected_sql, table.project(Arel.star).where(table[:id].eq(42)).to_sql }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should not use fully qualified table name in order clause" do
|
|
42
|
+
table = Arel::Table.new(:table)
|
|
43
|
+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table] ORDER BY [table].[name]"
|
|
44
|
+
assert_equal expected_sql, table.project(Arel.star).order(table[:name]).to_sql
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should use fully qualified table name in insert statement" do
|
|
48
|
+
manager = Arel::InsertManager.new
|
|
49
|
+
manager.into Arel::Table.new(:table)
|
|
50
|
+
manager.values = manager.create_values [Arel.sql("*")]
|
|
51
|
+
expected_sql = "INSERT INTO [my.server].[db].[schema].[table] VALUES (*)"
|
|
52
|
+
quietly { assert_equal expected_sql, manager.to_sql }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should use fully qualified table name in update statement" do
|
|
56
|
+
table = Arel::Table.new(:table)
|
|
57
|
+
manager = Arel::UpdateManager.new
|
|
58
|
+
manager.table(table).where(table[:id].eq(42))
|
|
59
|
+
manager.set([[table[:name], "Bob"]])
|
|
60
|
+
expected_sql = "UPDATE [my.server].[db].[schema].[table] SET [name] = N'Bob' WHERE [table].[id] = 42"
|
|
61
|
+
quietly { assert_equal expected_sql, manager.to_sql }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should use fully qualified table name in delete statement" do
|
|
65
|
+
table = Arel::Table.new(:table)
|
|
66
|
+
manager = Arel::DeleteManager.new
|
|
67
|
+
manager.from(table).where(table[:id].eq(42))
|
|
68
|
+
expected_sql = "DELETE FROM [my.server].[db].[schema].[table] WHERE [table].[id] = 42"
|
|
69
|
+
quietly { assert_equal expected_sql, manager.to_sql }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "support/paths_sqlserver"
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
Bundler.require :default, :development
|
|
6
|
+
require "pry"
|
|
7
|
+
require "support/core_ext/query_cache"
|
|
8
|
+
require "support/minitest_sqlserver"
|
|
9
|
+
require "support/test_in_memory_oltp"
|
|
10
|
+
require "support/table_definition_sqlserver"
|
|
11
|
+
require "cases/helper"
|
|
12
|
+
require "support/load_schema_sqlserver"
|
|
13
|
+
require "support/coerceable_test_sqlserver"
|
|
14
|
+
require "support/connection_reflection"
|
|
15
|
+
require "support/query_assertions"
|
|
16
|
+
require "mocha/minitest"
|
|
17
|
+
|
|
18
|
+
module ActiveSupport
|
|
19
|
+
class TestCase < ::Minitest::Test
|
|
20
|
+
include ARTest::SQLServer::CoerceableTest
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ActiveRecord
|
|
25
|
+
class TestCase < ActiveSupport::TestCase
|
|
26
|
+
SQLServer = ActiveRecord::ConnectionAdapters::SQLServer
|
|
27
|
+
|
|
28
|
+
include ARTest::SQLServer::ConnectionReflection,
|
|
29
|
+
ActiveSupport::Testing::Stream,
|
|
30
|
+
ARTest::SQLServer::QueryAssertions
|
|
31
|
+
|
|
32
|
+
let(:logger) { ActiveRecord::Base.logger }
|
|
33
|
+
|
|
34
|
+
setup :ensure_clean_rails_env
|
|
35
|
+
setup :remove_backtrace_silencers
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def ensure_clean_rails_env
|
|
40
|
+
Rails.instance_variable_set(:@_env, nil) if defined?(::Rails)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove_backtrace_silencers
|
|
44
|
+
Rails.backtrace_cleaner.remove_silencers!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def host_windows?
|
|
48
|
+
RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def with_use_output_inserted_disabled
|
|
52
|
+
klass = ActiveRecord::ConnectionAdapters::SQLServerAdapter
|
|
53
|
+
klass.use_output_inserted = false
|
|
54
|
+
yield
|
|
55
|
+
ensure
|
|
56
|
+
klass.use_output_inserted = true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Dir["#{ARTest::SQLServer.test_root_sqlserver}/models/**/*.rb"].each { |f| require f }
|