sql_cmd 0.3.0 → 0.3.3
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/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
|