sql_cmd 0.3.0 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sql_cmd/always_on.rb +9 -2
- data/lib/sql_cmd/backups.rb +12 -7
- data/lib/sql_cmd/database.rb +25 -20
- data/lib/sql_cmd/format.rb +1 -1
- data/lib/sql_cmd/query.rb +13 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84ae28535256cbad4ab450ea06fe4807b593b938548edc13f3edad224174f819
|
4
|
+
data.tar.gz: d6dfe44866a53bbc52d4bc7ebd1caf80d521a8b8ce70a56d7af9dc5bae3bc5cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d89c55abf7b3cf3abc9bc03d4ee15077326054b959af3a22f68e3b0278cd3af23a12dd027521cfc50d036cc888b5d0b515c62089424079bef7c535a9a55bdd82
|
7
|
+
data.tar.gz: dc56ef0c9e56263982497ef965a9108ab0710a10992dcad49a9f67e81bd4f451eeaa41f64e182e0a1ff2d628ef061844e66e0c745842c99225d699ff25193b52
|
data/lib/sql_cmd/always_on.rb
CHANGED
@@ -62,8 +62,10 @@ module SqlCmd
|
|
62
62
|
EasyIO.logger.header 'AlwaysOn Permissions Migration to Replica'
|
63
63
|
EasyIO.logger.debug 'Migrating logins to replica...'
|
64
64
|
import_script_filename = SqlCmd.export_logins(start_time, primary_connection_string, database_name)
|
65
|
-
|
66
|
-
|
65
|
+
unless import_script_filename.nil?
|
66
|
+
EasyIO.logger.info "Importing logins on [#{SqlCmd.connection_string_part(replica_connection_string, :server)}]..."
|
67
|
+
SqlCmd.execute_script_file(replica_connection_string, import_script_filename)
|
68
|
+
end
|
67
69
|
EasyIO.logger.debug 'Running database_status script...'
|
68
70
|
database_info = SqlCmd::Database.info(connection_string, database_name)
|
69
71
|
replica_database_info = SqlCmd::Database.info(replica_connection_string, database_name)
|
@@ -263,5 +265,10 @@ module SqlCmd
|
|
263
265
|
raise 'Failed to remove database from AlwaysOn Availability Group' unless database_info['AvailabilityGroup'].nil? && replica_database_info['AvailabilityGroup'].nil?
|
264
266
|
EasyIO.logger.info "[#{database_name}] removed from AlwaysOn availability group successfully..."
|
265
267
|
end
|
268
|
+
|
269
|
+
def enabled?(connection_string)
|
270
|
+
server_info = SqlCmd.get_sql_server_settings(connection_string)
|
271
|
+
!server_info['AvailabilityGroup'].nil? && !server_info['SecondaryReplica'].nil?
|
272
|
+
end
|
266
273
|
end
|
267
274
|
end
|
data/lib/sql_cmd/backups.rb
CHANGED
@@ -54,22 +54,27 @@ module SqlCmd
|
|
54
54
|
files.map { |f, properties| ["#{base_url}/#{f}", properties[:last_modified]] }.to_h
|
55
55
|
end
|
56
56
|
|
57
|
-
def backup_location_and_basename(start_time, connection_string, database_name, options = {}, backup_url: nil)
|
57
|
+
def backup_location_and_basename(start_time, connection_string, database_name, options = {}, backup_folder: nil, backup_url: nil, backup_basename: nil)
|
58
58
|
start_time = SqlCmd.unify_start_time(start_time)
|
59
59
|
database_info = SqlCmd::Database.info(connection_string, database_name)
|
60
60
|
server_settings = get_sql_server_settings(connection_string)
|
61
61
|
backup_type = backup_url.nil? || backup_url.empty? ? 'DISK' : 'URL'
|
62
62
|
if database_info['DatabaseNotFound']
|
63
|
-
|
64
|
-
return [backup_url,
|
63
|
+
backup_basename = "#{database_name}_#{EasyTime.yyyymmdd(start_time)}"
|
64
|
+
return [backup_url, backup_basename] if backup_type == 'URL'
|
65
65
|
backup_unc_location = SqlCmd.config['sql_cmd']['backups']['backup_to_host_sql_server'] ? "\\\\#{server_settings['ServerName']}\\#{SqlCmd.config['sql_cmd']['backups']['default_backup_share']}" : SqlCmd.config['sql_cmd']['backups']['default_destination']
|
66
|
-
return Dir.glob("#{backup_unc_location}/#{
|
66
|
+
return Dir.glob("#{backup_unc_location}/#{backup_basename}*".tr('\\', '/')).empty? ? [nil, nil] : [backup_unc_location, backup_basename]
|
67
67
|
end
|
68
68
|
|
69
69
|
backup_file_path = database_info['BackupFileLocation']
|
70
|
+
if backup_file_path =~ /^\{.*\}$/
|
71
|
+
backup_basename ||= "#{database_name}_#{EasyTime.yyyymmdd(start_time)}"
|
72
|
+
backup_files, backup_basename = SqlCmd::Database.existing_backup_files(server_settings, options, backup_folder: backup_folder, backup_url: backup_url, backup_basename: backup_basename, log_only: options['logonly'])
|
73
|
+
backup_file_path = backup_files.first
|
74
|
+
end
|
70
75
|
backup_file = ::File.basename(backup_file_path)
|
71
|
-
|
72
|
-
return [nil,
|
76
|
+
backup_basename = backup_basename_from_path(backup_file)
|
77
|
+
return [nil, backup_basename] if backup_type == 'URL'
|
73
78
|
backup_unc_location = to_unc_path(::File.dirname(backup_file_path), server_settings['ServerName'])
|
74
79
|
backup_folder = if ::File.exist?("#{backup_unc_location}/#{backup_file}")
|
75
80
|
backup_unc_location
|
@@ -77,7 +82,7 @@ module SqlCmd
|
|
77
82
|
"\\\\#{server_settings['ServerName']}\\#{SqlCmd.config['sql_cmd']['backups']['default_backup_share']}"
|
78
83
|
end
|
79
84
|
return [nil, nil] unless defined?(backup_folder)
|
80
|
-
[backup_folder,
|
85
|
+
[backup_folder, backup_basename]
|
81
86
|
end
|
82
87
|
|
83
88
|
# sql_server_settings can be for any server that has network access to these files
|
data/lib/sql_cmd/database.rb
CHANGED
@@ -313,10 +313,10 @@ module SqlCmd
|
|
313
313
|
SqlCmd.run_sql_as_job(connection_string, sql_backup_script, "Backup: #{backup_basename}", values: values, retries: 1, retry_delay: 30)
|
314
314
|
end
|
315
315
|
|
316
|
-
def monitor_backup(backup_start_time, connection_string, database_name, options = {}, backup_folder: nil, backup_url: nil, backup_basename: nil, log_only: false, retries: 3, retry_delay: 10)
|
316
|
+
def monitor_backup(backup_start_time, connection_string, database_name, options = {}, job_name: nil, backup_folder: nil, backup_url: nil, backup_basename: nil, log_only: false, retries: 3, retry_delay: 10)
|
317
317
|
backup_start_time = SqlCmd.unify_start_time(backup_start_time)
|
318
318
|
backup_status_script = ::File.read("#{SqlCmd.sql_script_dir}/Status/BackupProgress.sql")
|
319
|
-
job_name
|
319
|
+
job_name ||= "Backup: #{database_name}"
|
320
320
|
EasyIO.logger.info 'Checking backup status...'
|
321
321
|
EasyIO.logger.debug "Backup start time: #{backup_start_time}"
|
322
322
|
sleep(3) # Give the job time to start
|
@@ -340,9 +340,9 @@ module SqlCmd
|
|
340
340
|
# job_message = job_history_message(connection_string, job_name) unless result == :current
|
341
341
|
if job_started # Check if job has timed out after stopping without completing
|
342
342
|
job_completion_time ||= Time.now
|
343
|
-
_raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time,
|
343
|
+
_raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time, job_name, job_status: job_status, job_started: job_started) if Time.now > job_completion_time + timeout
|
344
344
|
elsif Time.now > monitoring_start_time + timeout # Job never started and timed out
|
345
|
-
_raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time,
|
345
|
+
_raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time, job_name, job_status: job_status, job_started: job_started)
|
346
346
|
end
|
347
347
|
sleep(timer_interval)
|
348
348
|
next
|
@@ -380,18 +380,18 @@ module SqlCmd
|
|
380
380
|
result
|
381
381
|
end
|
382
382
|
|
383
|
-
def _raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time,
|
383
|
+
def _raise_backup_failure(connection_string, database_name, last_backup_date_key, backup_start_time, job_name, job_status: nil, job_started: false)
|
384
384
|
server_name = SqlCmd.connection_string_part(connection_string, :server)
|
385
|
-
if job_started
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
raise failure_message + "Last backup time retrieved from: #{server_name}\\#{database_name}"
|
385
|
+
failure_message = if job_started
|
386
|
+
"Backup may have failed as the backup has stopped and the last backup time shows #{SqlCmd::Database.info(connection_string, database_name)[last_backup_date_key]} " \
|
387
|
+
"but the backup should be newer than #{backup_start_time}! " +
|
388
|
+
(job_status == 'NoJob' ? 'The job exited with success and so does not exist!' : "Check sql job '#{job_name}' history on [#{server_name}] for details. \n")
|
389
|
+
else
|
390
|
+
"Backup appears to have failed! The last backup time shows #{SqlCmd::Database.info(connection_string, database_name)[last_backup_date_key]} " \
|
391
|
+
"but the backup should be newer than #{backup_start_time}! " +
|
392
|
+
(job_status == 'NoJob' ? 'The backup job could not be found!' : "The job did not start in time. Check sql job '#{job_name}' history on [#{server_name}] for details.")
|
393
|
+
end
|
394
|
+
raise failure_message + " Last backup time retrieved from: #{server_name}\\#{database_name}"
|
395
395
|
end
|
396
396
|
|
397
397
|
# Creates and starts a SQL job to restore a database.
|
@@ -477,7 +477,7 @@ module SqlCmd
|
|
477
477
|
end
|
478
478
|
failure_message = 'Restore appears to have failed! '
|
479
479
|
failure_message += job_status == 'NoJob' ? 'The job could not be found and the restored database is not up to date!' : "The job did not start in time. Check sql job 'Restore: #{database_name}' history on [#{server_name}] for details."
|
480
|
-
raise failure_message + "
|
480
|
+
raise failure_message + "\nRestore destination: #{server_name}\\#{database_name}"
|
481
481
|
end
|
482
482
|
|
483
483
|
def check_restore_date(start_time, connection_string, database_name, messages = :none, log_only: false)
|
@@ -500,8 +500,8 @@ module SqlCmd
|
|
500
500
|
database_info = info(connection_string, database_name) if database_info.nil?
|
501
501
|
server_name = SqlCmd.connection_string_part(connection_string, :server)
|
502
502
|
EasyIO.logger.info "Ensuring full backup has taken place for [#{server_name}].[#{database_name}]..."
|
503
|
-
|
504
|
-
|
503
|
+
start_time = database_info['LastRestoreDate'] || database_info['create_date']
|
504
|
+
if force_backup || database_info['LastNonCopyOnlyFullBackupDate'].nil? || (database_info['LastNonCopyOnlyFullBackupDate'] < start_time) # Ensure last full backup occurred AFTER the DB was last restored/created
|
505
505
|
EasyIO.logger.info 'Running full backup...'
|
506
506
|
backup_basename = "full_backup-#{database_name}_#{EasyTime.yyyymmdd}" # If a full_backup_method was not provided, use this name for the database backup for clarity
|
507
507
|
full_backup_method.nil? ? SqlCmd::Database.backup(Time.now, connection_string, database_name, backup_basename: backup_basename, options: { 'copyonly' => false }) : full_backup_method.call
|
@@ -511,7 +511,7 @@ module SqlCmd
|
|
511
511
|
def duplicate(start_time, source_connection_string, source_database_name, destination_connection_string, destination_database_name, backup_folder: nil, backup_url: nil, backup_basename: nil, force_restore: false, full_backup_method: nil, options: {})
|
512
512
|
start_time = SqlCmd.unify_start_time(start_time)
|
513
513
|
backup(start_time, source_connection_string, source_database_name, backup_folder: backup_folder, backup_url: backup_url, backup_basename: backup_basename, options: options) unless info(source_connection_string, source_database_name)['DatabaseNotFound']
|
514
|
-
backup_folder, backup_basename = SqlCmd.backup_location_and_basename(start_time, source_connection_string, source_database_name, options, backup_url: backup_url) # TODO: rework for URL
|
514
|
+
backup_folder, backup_basename = SqlCmd.backup_location_and_basename(start_time, source_connection_string, source_database_name, options, backup_folder: backup_folder, backup_url: backup_url, backup_basename: backup_basename) # TODO: rework for URL
|
515
515
|
if (backup_folder.nil? && backup_url.nil?) || backup_basename.nil?
|
516
516
|
source_server = SqlCmd.connection_string_part(source_connection_string, :server)
|
517
517
|
destination_server = SqlCmd.connection_string_part(destination_connection_string, :server)
|
@@ -535,7 +535,11 @@ module SqlCmd
|
|
535
535
|
end
|
536
536
|
|
537
537
|
def apply_recovery_model(connection_string, database_name, options)
|
538
|
-
|
538
|
+
if recovery_model_set?(connection_string, database_name, options)
|
539
|
+
EasyIO.logger.info "Recovery model already set to '#{options['recovery_model']}'. No change needed."
|
540
|
+
return
|
541
|
+
end
|
542
|
+
EasyIO.logger.info "Setting recovery model to '#{options['recovery_model']}'..."
|
539
543
|
options['recovery_model'] ||= 'FULL'
|
540
544
|
options['rollback'] ||= 'ROLLBACK IMMEDIATE' # other options: ROLLBACK AFTER 30, NO_WAIT
|
541
545
|
sql_script = "ALTER DATABASE [#{database_name}] SET RECOVERY #{options['recovery_model']} WITH #{options['rollback']}"
|
@@ -547,6 +551,7 @@ module SqlCmd
|
|
547
551
|
#{'=' * 120}\n"
|
548
552
|
EOS
|
549
553
|
raise failure_message unless recovery_model_set?(connection_string, database_name, options)
|
554
|
+
EasyIO.logger.info "Recovery model updated to '#{options['recovery_model']}'."
|
550
555
|
end
|
551
556
|
|
552
557
|
def recovery_model_set?(connection_string, database_name, options)
|
data/lib/sql_cmd/format.rb
CHANGED
@@ -85,7 +85,7 @@ module SqlCmd
|
|
85
85
|
end
|
86
86
|
|
87
87
|
# get the basename of the backup based on a full file_path such as the SQL value from [backupmediafamily].[physical_device_name]
|
88
|
-
def
|
88
|
+
def backup_basename_from_path(backup_path)
|
89
89
|
return nil if backup_path.nil? || backup_path.empty?
|
90
90
|
::File.basename(backup_path).gsub(/(\.part\d+)?\.(bak|trn)/i, '')
|
91
91
|
end
|
data/lib/sql_cmd/query.rb
CHANGED
@@ -83,7 +83,18 @@ module SqlCmd
|
|
83
83
|
raise
|
84
84
|
end
|
85
85
|
ensure
|
86
|
-
|
86
|
+
retry_count = 0
|
87
|
+
retries = 3
|
88
|
+
retry_delay = 3
|
89
|
+
begin
|
90
|
+
::File.delete "#{@scripts_cache_windows}\\sql_helper_#{start_time}.ps1" if defined?(start_time) && ::File.exist?("#{@scripts_cache_windows}\\sql_helper_#{start_time}.ps1")
|
91
|
+
rescue # Try to delete the file 3 times
|
92
|
+
if (retry_count += 1) <= retries
|
93
|
+
sleep(retry_delay)
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
raise
|
97
|
+
end
|
87
98
|
end
|
88
99
|
|
89
100
|
def convert_powershell_tables_to_hash(json_string, return_type = :all_tables, at_timezone: 'UTC', string_to_time_by: '%Y-%m-%d %H:%M:%S %z') # options: :all_tables, :first_table, :first_row
|
@@ -234,6 +245,7 @@ module SqlCmd
|
|
234
245
|
def migrate_logins(start_time, source_connection_string, destination_connection_string, database_name)
|
235
246
|
start_time = SqlCmd.unify_start_time(start_time)
|
236
247
|
import_script_filename = export_logins(start_time, source_connection_string, database_name)
|
248
|
+
return if import_script_filename.nil?
|
237
249
|
if ::File.exist?(import_script_filename)
|
238
250
|
EasyIO.logger.info "Importing logins on [#{connection_string_part(destination_connection_string, :server)}]..."
|
239
251
|
execute_script_file(destination_connection_string, import_script_filename)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sql_cmd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Munoz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: easy_json_config
|