Skip to content

Views related fixes for structure dump and load #400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ test/profile/output/*
.idea
coverage/*
.flooignore
.floo
.floo
.vagrant/
Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,26 @@ def identity_column(table_name)
schema_cache.columns(table_name).find(&:is_identity?)
end

# === SQLServer Specific (Stored Rrocedure Reflection) ====================== #

def routines(routine_type = nil)
select_values "SELECT #{lowercase_schema_reflection_sql('ROUTINE_NAME')} FROM INFORMATION_SCHEMA.ROUTINES #{"WHERE ROUTINE_TYPE = '#{routine_type}'" if routine_type} ORDER BY ROUTINE_NAME", 'SCHEMA'
end

def routine_information(routine_name)
identifier = SQLServer::Utils.extract_identifiers(routine_name)
routine_info = select_one "SELECT * FROM information_schema.routines WHERE ROUTINE_NAME = '#{identifier.object}'", 'SCHEMA'

if routine_info
routine_info = routine_info.with_indifferent_access
if routine_info[:ROUTINE_DEFINITION].blank?
warn "No routing definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
nil
else
routine_info
end
end
end

private

Expand Down
27 changes: 24 additions & 3 deletions lib/active_record/tasks/sqlserver_database_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ def structure_dump(filename)
]
table_args = connection.tables.map { |t| Shellwords.escape(t) }
command.concat(table_args)
view_args = connection.views.map { |v| Shellwords.escape(v) }
command.concat(view_args)
raise 'Error dumping database' unless Kernel.system(command.join(' '))
dump = File.read(filename)
dump.gsub!(/^USE .*$\nGO\n/, '') # Strip db USE statements
Expand All @@ -69,10 +67,33 @@ def structure_dump(filename)
dump.gsub!(/nvarchar\(-1\)/, 'nvarchar(max)') # Fix nvarchar(-1) column defs
dump.gsub!(/text\(\d+\)/, 'text') # Fix text(16) column defs
File.open(filename, "w") { |file| file.puts dump }

# defncopy appears to truncate definition output in some circumstances
# Also create view needs to be the first operation in the batch.
File.open(filename, 'a') { |file|
connection.send(:views).each do |v|
view_info = connection.send(:view_information, v)
file.puts "\r\nGO\r\n#{view_info[:VIEW_DEFINITION]}"
end
}

# Export any routines (stored procedures, functions, etc.)
File.open(filename, 'a') { |file|
connection.send(:routines).each do |r|
routine_info = connection.send(:routine_information, r)
file.puts "\r\nGO\r\n#{routine_info[:ROUTINE_DEFINITION]}"
end
file.puts "\r\nGO\r\n"
}
end

def structure_load(filename)
connection.execute File.read(filename)
structure = File.read(filename)
# Split by GO so that operations that must be in separate batches are in
# separate batches
structure.split(/^GO/).each { |s|
connection.execute s
}
end


Expand Down
19 changes: 19 additions & 0 deletions test/cases/adapter_test_sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -416,5 +416,24 @@ class AdapterTestSQLServer < ActiveRecord::TestCase

end


describe 'routines' do

it 'return an array' do
assert_instance_of Array, connection.send(:routines)
end

it 'sst_routines_1 routine must exist' do
connection.send(:routines).must_include 'sst_routine_1'
end

it 'allow the connection#routine_information method to return data on the routine' do
routine_info = connection.send(:routine_information,'sst_routine_2')
assert_equal('sst_routine_2', routine_info['ROUTINE_NAME'])
assert_match(/CREATE FUNCTION sst_routine_2/, routine_info['ROUTINE_DEFINITION'])
end

end

end

21 changes: 20 additions & 1 deletion test/cases/rake_test_sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
t.text_basic :background2
t.timestamps null: false
end

connection.execute <<-CUSTOMERSVIEW
CREATE VIEW users_view AS
SELECT name FROM users
CUSTOMERSVIEW

connection.execute <<-PROCEDUREDEF
CREATE PROCEDURE users_procedure AS
SELECT 1
PROCEDUREDEF
end

after do
Expand All @@ -122,19 +132,28 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
it 'dumps structure and accounts for defncopy oddities' do
db_tasks.structure_dump configuration, filename
filedata.wont_match %r{\AUSE.*\z}
filedata.wont_match %r{\AGO.*\z}
filedata.must_match %r{email\s+nvarchar\(4000\)}
filedata.must_match %r{background1\s+nvarchar\(max\)}
filedata.must_match %r{background2\s+text\s+}
end

it 'can load dumped structure' do
db_tasks.structure_dump configuration, filename

filedata.must_match %r{CREATE TABLE dbo\.users}
filedata.must_match %r{CREATE PROCEDURE users_procedure}
filedata.must_match %r{CREATE VIEW users_view}

db_tasks.purge(configuration)
connection.tables.wont_include 'users'
connection.send(:routines).wont_include 'users_procedure'
connection.send(:views).wont_include 'users_view'

db_tasks.load_schema_for configuration, :sql, filename

connection.tables.must_include 'users'
connection.send(:routines).must_include 'users_procedure'
connection.send(:views).must_include 'users_view'
end

end
18 changes: 18 additions & 0 deletions test/schema/sqlserver_specific_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@
FROM sst_string_defaults
STRINGDEFAULTSBIGVIEW

# Routines

execute "IF EXISTS (SELECT ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sst_routine_1') DROP PROCEDURE sst_routine_1"
execute <<-PROCEDUREDEF
CREATE PROCEDURE sst_routine_1 AS
SELECT 1
PROCEDUREDEF

execute "IF EXISTS (SELECT ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sst_routine_2') DROP FUNCTION sst_routine_2"
execute <<-FUNCDEF
CREATE FUNCTION sst_routine_2 ()
RETURNS int
AS
BEGIN
RETURN 1
END
FUNCDEF

# Another schema.

create_table :sst_schema_columns, force: true do |t|
Expand Down