Skip to content

Commit c241f04

Browse files
committed
Merge pull request #427 from jippeholwerda/linked-server-compatability
Add linked server support to queries/commands
2 parents dca09a0 + 188f986 commit c241f04

File tree

8 files changed

+146
-2
lines changed

8 files changed

+146
-2
lines changed

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def select(sql, name = nil, binds = [])
216216
end
217217

218218
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
219-
sql = if pk && self.class.use_output_inserted
219+
sql = if pk && self.class.use_output_inserted && !remote_server?
220220
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
221221
sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
222222
else

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,11 @@ def initialize_native_database_types
220220
end
221221

222222
def column_definitions(table_name)
223-
identifier = SQLServer::Utils.extract_identifiers(table_name)
223+
if remote_server?
224+
identifier = SQLServer::Utils.extract_identifiers("#{database_prefix}#{table_name}")
225+
else
226+
identifier = SQLServer::Utils.extract_identifiers(table_name)
227+
end
224228
database = identifier.fully_qualified_database_quoted
225229
view_exists = schema_cache.view_exists?(table_name)
226230
view_tblnm = table_name_or_views_table_name(table_name) if view_exists

lib/active_record/connection_adapters/sqlserver/utils.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ def fully_qualified_database_quoted
4343
[server_quoted, database_quoted].compact.join(SEPARATOR)
4444
end
4545

46+
def fully_qualified?
47+
parts.compact.size == 4
48+
end
49+
4650
def to_s
4751
quoted
4852
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ def sqlserver_azure?
181181
@sqlserver_azure
182182
end
183183

184+
def remote_server?
185+
!!database_prefix and SQLServer::Utils.extract_identifiers(@connection_options[:database_prefix]).fully_qualified?
186+
end
187+
188+
def database_prefix
189+
@connection_options[:database_prefix]
190+
end
191+
184192
def version
185193
self.class::VERSION
186194
end

lib/arel/visitors/sqlserver.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ def visit_Arel_Nodes_SelectStatement o, collector
7373
@select_statement = nil
7474
end
7575

76+
def visit_Arel_Table o, collector
77+
table_name = if o.engine.connection.remote_server?
78+
remote_server_table_name(o)
79+
else
80+
quote_table_name(o.name)
81+
end
82+
if o.table_alias
83+
collector << "#{table_name} #{quote_table_name o.table_alias}"
84+
else
85+
collector << table_name
86+
end
87+
end
88+
7689
def visit_Arel_Nodes_JoinSource o, collector
7790
if o.left
7891
collector = visit o.left, collector
@@ -185,6 +198,12 @@ def primary_Key_From_Table t
185198
column_name ? t[column_name] : nil
186199
end
187200

201+
def remote_server_table_name o
202+
ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
203+
"#{o.engine.connection.database_prefix}#{o.name}"
204+
).quoted
205+
end
206+
188207
end
189208
end
190209
end

test/cases/adapter_test_sqlserver.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,5 +416,25 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
416416

417417
end
418418

419+
describe 'remote_server?' do
420+
after do
421+
connection.instance_variable_get(:@connection_options).delete(:database_prefix)
422+
end
423+
424+
it 'should return false if database_prefix is not configured' do
425+
assert_equal false, connection.remote_server?
426+
end
427+
428+
it 'should return true if database_prefix has been set' do
429+
connection.instance_variable_get(:@connection_options)[:database_prefix] = "server.database.schema."
430+
assert_equal true, connection.remote_server?
431+
end
432+
433+
it 'should return false if database_prefix has been set incorrectly' do
434+
connection.instance_variable_get(:@connection_options)[:database_prefix] = "server.database.schema"
435+
assert_equal false, connection.remote_server?
436+
end
437+
end
438+
419439
end
420440

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
require 'cases/helper_sqlserver'
2+
3+
class FullyQualifiedIdentifierTestSQLServer < ActiveRecord::TestCase
4+
describe 'local server' do
5+
it 'should use table name in select projections' do
6+
table = Arel::Table.new(:table)
7+
expected_sql = "SELECT [table].[name] FROM [table]"
8+
assert_equal expected_sql, table.project(table[:name]).to_sql
9+
end
10+
end
11+
12+
describe 'remote server' do
13+
before do
14+
connection.instance_variable_get(:@connection_options)[:database_prefix] = "[my.server].db.schema."
15+
end
16+
17+
after do
18+
connection.instance_variable_get(:@connection_options).delete(:database_prefix)
19+
end
20+
21+
it 'should use fully qualified table name in select from clause' do
22+
table = Arel::Table.new(:table)
23+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table]"
24+
assert_equal expected_sql, table.project(Arel.star).to_sql
25+
end
26+
27+
it 'should not use fully qualified table name in select projections' do
28+
table = Arel::Table.new(:table)
29+
expected_sql = "SELECT [table].[name] FROM [my.server].[db].[schema].[table]"
30+
assert_equal expected_sql, table.project(table[:name]).to_sql
31+
end
32+
33+
it 'should not use fully qualified table name in where clause' do
34+
table = Arel::Table.new(:table)
35+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table] WHERE [table].[id] = 42"
36+
assert_equal expected_sql, table.project(Arel.star).where(table[:id].eq(42)).to_sql
37+
end
38+
39+
it 'should not use fully qualified table name in order clause' do
40+
table = Arel::Table.new(:table)
41+
expected_sql = "SELECT * FROM [my.server].[db].[schema].[table] ORDER BY [table].[name]"
42+
assert_equal expected_sql, table.project(Arel.star).order(table[:name]).to_sql
43+
end
44+
45+
it 'should use fully qualified table name in insert statement' do
46+
manager = Arel::InsertManager.new(Arel::Table.engine)
47+
manager.into Arel::Table.new(:table)
48+
manager.values = manager.create_values [Arel.sql('*')], %w{ a }
49+
expected_sql = "INSERT INTO [my.server].[db].[schema].[table] VALUES (*)"
50+
assert_equal expected_sql, manager.to_sql
51+
end
52+
53+
it 'should use fully qualified table name in update statement' do
54+
table = Arel::Table.new(:table)
55+
manager = Arel::UpdateManager.new(Arel::Table.engine)
56+
manager.table(table).where(table[:id].eq(42))
57+
manager.set([[table[:name], "Bob"]])
58+
expected_sql = "UPDATE [my.server].[db].[schema].[table] SET [name] = N'Bob' WHERE [table].[id] = 42"
59+
assert_equal expected_sql, manager.to_sql
60+
end
61+
62+
it 'should use fully qualified table name in delete statement' do
63+
table = Arel::Table.new(:table)
64+
manager = Arel::DeleteManager.new(Arel::Table.engine)
65+
manager.from(table).where(table[:id].eq(42))
66+
expected_sql = "DELETE FROM [my.server].[db].[schema].[table] WHERE [table].[id] = 42"
67+
assert_equal expected_sql, manager.to_sql
68+
end
69+
end
70+
end

test/cases/utils_test_sqlserver.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ class UtilsTestSQLServer < ActiveRecord::TestCase
8686
SQLServer::Utils.extract_identifiers('[obj.name].[foo]').quoted.must_equal '[obj.name].[foo]'
8787
end
8888

89+
it 'should indicate if a name is fully qualitified' do
90+
SQLServer::Utils.extract_identifiers('object').fully_qualified?.must_equal false
91+
SQLServer::Utils.extract_identifiers('schema.object').fully_qualified?.must_equal false
92+
SQLServer::Utils.extract_identifiers('database.schema.object').fully_qualified?.must_equal false
93+
SQLServer::Utils.extract_identifiers('database.object').fully_qualified?.must_equal false
94+
SQLServer::Utils.extract_identifiers('server...object').fully_qualified?.must_equal false
95+
SQLServer::Utils.extract_identifiers('server.database..object').fully_qualified?.must_equal false
96+
SQLServer::Utils.extract_identifiers('server.database.schema.object').fully_qualified?.must_equal true
97+
SQLServer::Utils.extract_identifiers('server.database.schema.').fully_qualified?.must_equal true
98+
SQLServer::Utils.extract_identifiers('[obj.name]').fully_qualified?.must_equal false
99+
SQLServer::Utils.extract_identifiers('[schema].[obj.name]').fully_qualified?.must_equal false
100+
SQLServer::Utils.extract_identifiers('[database].[schema].[obj.name]').fully_qualified?.must_equal false
101+
SQLServer::Utils.extract_identifiers('[database].[obj.name]').fully_qualified?.must_equal false
102+
SQLServer::Utils.extract_identifiers('[server.name]...[obj.name]').fully_qualified?.must_equal false
103+
SQLServer::Utils.extract_identifiers('[server.name].[database]..[obj.name]').fully_qualified?.must_equal false
104+
SQLServer::Utils.extract_identifiers('[server.name].[database].[schema].[obj.name]').fully_qualified?.must_equal true
105+
SQLServer::Utils.extract_identifiers('[server.name].[database].[schema].').fully_qualified?.must_equal true
106+
end
107+
89108
it 'can return fully qualified quoted table name' do
90109
name = SQLServer::Utils.extract_identifiers('[server.name].[database].[schema].[object]')
91110
name.fully_qualified_database_quoted.must_equal '[server.name].[database]'

0 commit comments

Comments
 (0)