dbsketch 0.0.1
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/LICENSE +674 -0
- data/README.md +1 -0
- data/lib/dbsketch.rb +64 -0
- data/lib/dbsketch/automation/automation_error.rb +15 -0
- data/lib/dbsketch/automation/database_connection_details.rb +32 -0
- data/lib/dbsketch/automation/database_importer.rb +184 -0
- data/lib/dbsketch/automation/database_proxy.rb +78 -0
- data/lib/dbsketch/automation/table_importer.rb +114 -0
- data/lib/dbsketch/comparison/check_constraint_comparator.rb +54 -0
- data/lib/dbsketch/comparison/column_comparator.rb +65 -0
- data/lib/dbsketch/comparison/comparison_error.rb +15 -0
- data/lib/dbsketch/comparison/computed_column_comparator.rb +63 -0
- data/lib/dbsketch/comparison/database_comparator.rb +115 -0
- data/lib/dbsketch/comparison/diff.rb +37 -0
- data/lib/dbsketch/comparison/foreign_key_comparator.rb +56 -0
- data/lib/dbsketch/comparison/function_comparator.rb +56 -0
- data/lib/dbsketch/comparison/index_comparator.rb +65 -0
- data/lib/dbsketch/comparison/primary_key_comparator.rb +61 -0
- data/lib/dbsketch/comparison/procedure_comparator.rb +54 -0
- data/lib/dbsketch/comparison/table_comparator.rb +158 -0
- data/lib/dbsketch/comparison/trigger_comparator.rb +53 -0
- data/lib/dbsketch/comparison/type_comparator.rb +51 -0
- data/lib/dbsketch/comparison/unique_constraint_comparator.rb +58 -0
- data/lib/dbsketch/comparison/view_comparator.rb +54 -0
- data/lib/dbsketch/model/abstract_column.rb +25 -0
- data/lib/dbsketch/model/check_constraint.rb +23 -0
- data/lib/dbsketch/model/column.rb +35 -0
- data/lib/dbsketch/model/computed_column.rb +27 -0
- data/lib/dbsketch/model/custom_code.rb +21 -0
- data/lib/dbsketch/model/database.rb +87 -0
- data/lib/dbsketch/model/database_object.rb +71 -0
- data/lib/dbsketch/model/foreign_key.rb +29 -0
- data/lib/dbsketch/model/function.rb +23 -0
- data/lib/dbsketch/model/index.rb +40 -0
- data/lib/dbsketch/model/model_error.rb +15 -0
- data/lib/dbsketch/model/operation.rb +28 -0
- data/lib/dbsketch/model/primary_key.rb +33 -0
- data/lib/dbsketch/model/procedure.rb +18 -0
- data/lib/dbsketch/model/table.rb +171 -0
- data/lib/dbsketch/model/trigger.rb +32 -0
- data/lib/dbsketch/model/type.rb +92 -0
- data/lib/dbsketch/model/unique_constraint.rb +33 -0
- data/lib/dbsketch/model/view.rb +23 -0
- data/lib/dbsketch/rendering/meta/column_renderer.rb +76 -0
- data/lib/dbsketch/rendering/meta/database_renderer.rb +112 -0
- data/lib/dbsketch/rendering/meta/foreign_key_renderer.rb +31 -0
- data/lib/dbsketch/rendering/meta/index_renderer.rb +30 -0
- data/lib/dbsketch/rendering/meta/operation_renderer.rb +66 -0
- data/lib/dbsketch/rendering/meta/table_renderer.rb +177 -0
- data/lib/dbsketch/rendering/meta/trigger_renderer.rb +31 -0
- data/lib/dbsketch/rendering/meta/type_renderer.rb +37 -0
- data/lib/dbsketch/rendering/meta/view_renderer.rb +32 -0
- data/lib/dbsketch/rendering/sql/column_renderer.rb +75 -0
- data/lib/dbsketch/rendering/sql/database_diff_renderer.rb +94 -0
- data/lib/dbsketch/rendering/sql/database_renderer.rb +139 -0
- data/lib/dbsketch/rendering/sql/foreign_key_renderer.rb +24 -0
- data/lib/dbsketch/rendering/sql/index_renderer.rb +31 -0
- data/lib/dbsketch/rendering/sql/operation_renderer.rb +64 -0
- data/lib/dbsketch/rendering/sql/table_renderer.rb +148 -0
- data/lib/dbsketch/rendering/sql/trigger_renderer.rb +31 -0
- data/lib/dbsketch/rendering/sql/type_renderer.rb +28 -0
- data/lib/dbsketch/rendering/sql/view_renderer.rb +31 -0
- data/lib/dbsketch/version.rb +3 -0
- metadata +134 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# dbsketch
|
data/lib/dbsketch.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'dbsketch/model/abstract_column'
|
2
|
+
require_relative 'dbsketch/model/check_constraint'
|
3
|
+
require_relative 'dbsketch/model/column'
|
4
|
+
require_relative 'dbsketch/model/computed_column'
|
5
|
+
require_relative 'dbsketch/model/custom_code'
|
6
|
+
require_relative 'dbsketch/model/database'
|
7
|
+
require_relative 'dbsketch/model/database_object'
|
8
|
+
require_relative 'dbsketch/model/foreign_key'
|
9
|
+
require_relative 'dbsketch/model/function'
|
10
|
+
require_relative 'dbsketch/model/index'
|
11
|
+
require_relative 'dbsketch/model/model_error'
|
12
|
+
require_relative 'dbsketch/model/operation'
|
13
|
+
require_relative 'dbsketch/model/primary_key'
|
14
|
+
require_relative 'dbsketch/model/procedure'
|
15
|
+
require_relative 'dbsketch/model/table'
|
16
|
+
require_relative 'dbsketch/model/trigger'
|
17
|
+
require_relative 'dbsketch/model/type'
|
18
|
+
require_relative 'dbsketch/model/unique_constraint'
|
19
|
+
require_relative 'dbsketch/model/view'
|
20
|
+
|
21
|
+
require_relative 'dbsketch/automation/automation_error'
|
22
|
+
require_relative 'dbsketch/automation/database_connection_details'
|
23
|
+
require_relative 'dbsketch/automation/database_importer'
|
24
|
+
require_relative 'dbsketch/automation/database_proxy'
|
25
|
+
require_relative 'dbsketch/automation/table_importer'
|
26
|
+
|
27
|
+
require_relative 'dbsketch/comparison/check_constraint_comparator'
|
28
|
+
require_relative 'dbsketch/comparison/column_comparator'
|
29
|
+
require_relative 'dbsketch/comparison/computed_column_comparator'
|
30
|
+
require_relative 'dbsketch/comparison/database_comparator'
|
31
|
+
require_relative 'dbsketch/comparison/diff'
|
32
|
+
require_relative 'dbsketch/comparison/foreign_key_comparator'
|
33
|
+
require_relative 'dbsketch/comparison/function_comparator'
|
34
|
+
require_relative 'dbsketch/comparison/primary_key_comparator'
|
35
|
+
require_relative 'dbsketch/comparison/procedure_comparator'
|
36
|
+
require_relative 'dbsketch/comparison/table_comparator'
|
37
|
+
require_relative 'dbsketch/comparison/trigger_comparator'
|
38
|
+
require_relative 'dbsketch/comparison/type_comparator'
|
39
|
+
require_relative 'dbsketch/comparison/unique_constraint_comparator'
|
40
|
+
require_relative 'dbsketch/comparison/view_comparator'
|
41
|
+
|
42
|
+
require_relative 'dbsketch/rendering/meta/column_renderer'
|
43
|
+
require_relative 'dbsketch/rendering/meta/database_renderer'
|
44
|
+
require_relative 'dbsketch/rendering/meta/foreign_key_renderer'
|
45
|
+
require_relative 'dbsketch/rendering/meta/index_renderer'
|
46
|
+
require_relative 'dbsketch/rendering/meta/operation_renderer'
|
47
|
+
require_relative 'dbsketch/rendering/meta/table_renderer'
|
48
|
+
require_relative 'dbsketch/rendering/meta/trigger_renderer'
|
49
|
+
require_relative 'dbsketch/rendering/meta/type_renderer'
|
50
|
+
require_relative 'dbsketch/rendering/meta/view_renderer'
|
51
|
+
|
52
|
+
require_relative 'dbsketch/rendering/sql/column_renderer'
|
53
|
+
require_relative 'dbsketch/rendering/sql/database_renderer'
|
54
|
+
require_relative 'dbsketch/rendering/sql/foreign_key_renderer'
|
55
|
+
require_relative 'dbsketch/rendering/sql/index_renderer'
|
56
|
+
require_relative 'dbsketch/rendering/sql/operation_renderer'
|
57
|
+
require_relative 'dbsketch/rendering/sql/table_renderer'
|
58
|
+
require_relative 'dbsketch/rendering/sql/trigger_renderer'
|
59
|
+
require_relative 'dbsketch/rendering/sql/type_renderer'
|
60
|
+
require_relative 'dbsketch/rendering/sql/view_renderer'
|
61
|
+
|
62
|
+
module Dbsketch
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Represents database model error (for example, two columns with a same name in a table)
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
module Dbsketch
|
6
|
+
module Automation
|
7
|
+
|
8
|
+
class AutomationError < StandardError
|
9
|
+
def initialize message
|
10
|
+
super(message)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Connection details to database on server
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
module Dbsketch
|
6
|
+
module Automation
|
7
|
+
|
8
|
+
class DatabaseConnectionDetails
|
9
|
+
def initialize host:, instance: nil, database:, user: nil, password: nil
|
10
|
+
### Preconditions
|
11
|
+
raise ArgumentError, "host is not a String" unless host.is_a? String
|
12
|
+
raise ArgumentError, "instance is not a String" unless nil == instance or instance.is_a? String
|
13
|
+
raise ArgumentError, "database is not a String" unless database.is_a? String
|
14
|
+
raise ArgumentError, "user is not a String" unless nil == user or user.is_a? String
|
15
|
+
raise ArgumentError, "password is not a String" unless nil == password or password.is_a? String
|
16
|
+
###
|
17
|
+
@host = host
|
18
|
+
@instance = instance
|
19
|
+
@database = database
|
20
|
+
@user = user
|
21
|
+
@password = password
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :host, :instance, :database, :user, :password
|
25
|
+
|
26
|
+
def full_host
|
27
|
+
(nil == @instance) ? @host : "#{@host}\\#{@instance}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Import database into memory (Dbsketch::Model structure)
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative '../model/database'
|
6
|
+
require_relative 'automation_error'
|
7
|
+
require_relative 'database_proxy'
|
8
|
+
require_relative 'table_importer'
|
9
|
+
|
10
|
+
module Dbsketch
|
11
|
+
module Automation
|
12
|
+
|
13
|
+
class DatabaseImporter
|
14
|
+
|
15
|
+
def initialize db
|
16
|
+
### Preconditions
|
17
|
+
raise ArgumentError, "database is not a Dbsketch::Automation::DatabaseProxy" unless db.is_a? DatabaseProxy
|
18
|
+
###
|
19
|
+
@db = db
|
20
|
+
@table_importer = TableImporter.new @db
|
21
|
+
end
|
22
|
+
|
23
|
+
def import options: {}
|
24
|
+
options = {
|
25
|
+
:index_white_list => [], :index_black_list => [],
|
26
|
+
:operation_white_list => [], :operation_black_list => [],
|
27
|
+
:table_white_list => [], :table_black_list => [],
|
28
|
+
:view_white_list => [], :view_black_list => []
|
29
|
+
}.merge options
|
30
|
+
options[:index_white_list] = (options[:index_white_list].is_a? Array) ? options[:index_white_list] : [options[:index_white_list]]
|
31
|
+
options[:index_black_list] = (options[:index_black_list].is_a? Array) ? options[:index_black_list] : [options[:index_black_list]]
|
32
|
+
options[:operation_white_list] = (options[:operation_white_list].is_a? Array) ? options[:operation_white_list] : [options[:operation_white_list]]
|
33
|
+
options[:operation_black_list] = (options[:operation_black_list].is_a? Array) ? options[:operation_black_list] : [options[:operation_black_list]]
|
34
|
+
options[:table_white_list] = (options[:table_white_list].is_a? Array) ? options[:table_white_list] : [options[:table_white_list]]
|
35
|
+
options[:table_black_list] = (options[:table_black_list].is_a? Array) ? options[:table_black_list] : [options[:table_black_list]]
|
36
|
+
options[:view_white_list] = (options[:view_white_list].is_a? Array) ? options[:view_white_list] : [options[:view_white_list]]
|
37
|
+
options[:view_black_list] = (options[:view_black_list].is_a? Array) ? options[:view_black_list] : [options[:view_black_list]]
|
38
|
+
### Preconditions
|
39
|
+
raise ArgumentError, "options[:operation_white_list] and options[:operation_black_list] are exclusive." if not options[:operation_white_list].empty? and not options[:operation_black_list].empty?
|
40
|
+
raise ArgumentError, "options[:table_white_list] and options[:table_black_list] are exclusive." if not options[:table_white_list].empty? and not options[:table_black_list].empty?
|
41
|
+
raise ArgumentError, "options[:view_white_list] and options[:view_black_list] are exclusive." if not options[:view_white_list].empty? and not options[:view_black_list].empty?
|
42
|
+
###
|
43
|
+
|
44
|
+
imported_database = Dbsketch::Model::Database.new
|
45
|
+
|
46
|
+
@db.fetch("select * from sys.tables").all.each do |db_table|
|
47
|
+
if (options[:table_white_list].empty? or name_included_in_list? db_table[:name], options[:table_white_list]) and not name_included_in_list? db_table[:name], options[:table_black_list]
|
48
|
+
table = import_table(db_table[:name])
|
49
|
+
imported_database.add table
|
50
|
+
import_indexes imported_database, table, db_table, options
|
51
|
+
import_triggers imported_database, table, db_table
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
imported_database.tables.each do |table|
|
56
|
+
import_foreign_keys imported_database, table
|
57
|
+
end
|
58
|
+
|
59
|
+
import_views imported_database, options
|
60
|
+
|
61
|
+
import_functions imported_database, options
|
62
|
+
import_procedures imported_database, options
|
63
|
+
|
64
|
+
imported_database
|
65
|
+
end
|
66
|
+
|
67
|
+
def import_table table_name
|
68
|
+
@table_importer.import table_name
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def name_included_in_list? name, list
|
74
|
+
included = nil != list.find { |element| (element.is_a? Regexp and name.match(element)) or element == name }
|
75
|
+
included
|
76
|
+
end
|
77
|
+
|
78
|
+
def import_foreign_keys database, table
|
79
|
+
db_table = @db.fetch("select object_id from sys.tables where name = '#{table.name}'").all.first
|
80
|
+
@db.fetch("select object_id, name from sys.foreign_keys where parent_object_id = #{db_table[:object_id]}").all.each do |db_key|
|
81
|
+
db_key_column = @db.fetch("select constraint_column_id, referenced_object_id, referenced_column_id from sys.foreign_key_columns where constraint_object_id = #{db_key[:object_id]}").all.first
|
82
|
+
db_constricted_column = @db.fetch("select name from sys.columns where object_id = #{db_table[:object_id]} and column_id = #{db_key_column[:constraint_column_id]}").all.first
|
83
|
+
db_referenced_table = @db.fetch("select object_id, name from sys.tables where object_id = #{db_key_column[:referenced_object_id]}").all.first
|
84
|
+
db_referenced_column = @db.fetch("select name from sys.columns where object_id = #{db_referenced_table[:object_id]} and column_id = #{db_key_column[:referenced_column_id]}").all.first
|
85
|
+
|
86
|
+
name = db_key[:name]
|
87
|
+
constricted_column = table[db_constricted_column[:name]]
|
88
|
+
referenced_table = database[db_referenced_table[:name]]
|
89
|
+
referenced_column = referenced_table[db_referenced_column[:name]]
|
90
|
+
table.add Dbsketch::Model::ForeignKey.new name, constricted_column, referenced_table, referenced_column
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def import_functions database, options
|
95
|
+
@db.fetch("select name, object_definition(object_id) as definition from sys.objects where type in ('FN', 'FT')").all.each do |db_function|
|
96
|
+
if (options[:operation_white_list].empty? or name_included_in_list? db_function[:name], options[:operation_white_list]) and not name_included_in_list? db_function[:name], options[:operation_black_list]
|
97
|
+
name = db_function[:name]
|
98
|
+
definition = db_function[:definition].strip
|
99
|
+
|
100
|
+
begin_index = definition.index("begin")
|
101
|
+
raw_arguments_line = definition[0..begin_index - 1].sub(/^create function [\[\w\.\]]+\s*\(/, '').sub(/\s*as\s*$/, '')
|
102
|
+
returns_index = raw_arguments_line.index("returns")
|
103
|
+
raw_arguments = raw_arguments_line[0..returns_index - 1].strip
|
104
|
+
|
105
|
+
returns = raw_arguments_line[returns_index, (raw_arguments_line.length - raw_arguments.length)].sub(/^returns\s*/, '')
|
106
|
+
|
107
|
+
raw_arguments = raw_arguments.sub(/\)$/, '').strip.split(/,\s?@/)
|
108
|
+
arguments = raw_arguments.map { |arg| arg.start_with?('@') ? arg : "@#{arg}" }
|
109
|
+
|
110
|
+
matches = definition.match /begin(.*)end/m
|
111
|
+
algo = matches[1].strip.gsub(/\r\n/m, "\n")
|
112
|
+
|
113
|
+
database.add Dbsketch::Model::Function.new(name, :arguments => arguments, :returns => returns, :algo => algo)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def import_indexes database, table, db_table, options
|
119
|
+
@db.fetch("select index_id, name from sys.indexes where object_id = #{db_table[:object_id]} and is_primary_key = 0 and is_unique_constraint = 0").all.each do |db_index|
|
120
|
+
if (options[:index_white_list].empty? or name_included_in_list? db_index[:name], options[:index_white_list]) and not name_included_in_list? db_index[:name], options[:index_black_list]
|
121
|
+
name = db_index[:name]
|
122
|
+
columns = []
|
123
|
+
if nil != name
|
124
|
+
@db.fetch("select column_id from sys.index_columns where object_id = #{db_table[:object_id]} and index_id = #{db_index[:index_id]}").all.each do |db_index_col|
|
125
|
+
db_column = @db.fetch("select name from sys.columns where object_id = #{db_table[:object_id]} and column_id = #{db_index_col[:column_id]}").all.first
|
126
|
+
columns << table[db_column[:name]]
|
127
|
+
end
|
128
|
+
database.add Dbsketch::Model::Index.new name, table, columns
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def import_procedures database, options
|
135
|
+
@db.fetch("select name, object_definition(object_id) as definition from sys.procedures").all.each do |db_proc|
|
136
|
+
if (options[:operation_white_list].empty? or name_included_in_list? db_proc[:name], options[:operation_white_list]) and not name_included_in_list? db_proc[:name], options[:operation_black_list]
|
137
|
+
name = db_proc[:name]
|
138
|
+
definition = db_proc[:definition].strip.gsub(/\r\n/m, "\n")
|
139
|
+
|
140
|
+
begin_index = definition.index("begin")
|
141
|
+
raw_arguments = definition[0..begin_index - 1].sub(/^create procedure [\[\w\.\]]+/, '').sub(/\s*as\s*$/, '')
|
142
|
+
raw_arguments = raw_arguments.strip.split(/,\s?@/)
|
143
|
+
arguments = raw_arguments.map { |arg| arg.start_with?('@') ? arg : "@#{arg}" }
|
144
|
+
|
145
|
+
matches = definition.match /begin(.*)end/m
|
146
|
+
algo = matches[1].strip
|
147
|
+
|
148
|
+
database.add Dbsketch::Model::Procedure.new(name, :arguments => arguments, :algo => algo)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def import_triggers database, table, db_table
|
154
|
+
@db.fetch("select name, object_definition(object_id) as definition from sys.triggers where parent_id = #{db_table[:object_id]}").all.each do |db_trigger|
|
155
|
+
name = db_trigger[:name]
|
156
|
+
definition = db_trigger[:definition].strip
|
157
|
+
matches = definition.match /^create trigger (\[?[\w.]*\]?) on (\[?[\w.]*\]?) (for|after|instead of) (insert)?,? ?(update)?,? ?(delete)?/
|
158
|
+
if nil != matches and matches.captures.count > 5
|
159
|
+
activation_time = matches[3] + " " + matches[4..6].compact.join(", ")
|
160
|
+
else
|
161
|
+
activation_time = "for ???"
|
162
|
+
end
|
163
|
+
matches = definition.match /begin(.*)end/m
|
164
|
+
if nil != matches and matches.captures.count > 0
|
165
|
+
algo = matches[1].strip.gsub(/\r\n/m, "\n")
|
166
|
+
else
|
167
|
+
algo = "???"
|
168
|
+
end
|
169
|
+
database.add Dbsketch::Model::Trigger.new name, table, activation_time, :algo => algo, :dependencies => table
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def import_views database, options
|
174
|
+
@db.fetch("select table_name, view_definition from information_schema.views").all.each do |db_view|
|
175
|
+
if (options[:view_white_list].empty? or name_included_in_list? db_view[:table_name], options[:view_white_list]) and not name_included_in_list? db_view[:table_name], options[:view_black_list]
|
176
|
+
database.add Dbsketch::Model::View.new db_view[:table_name], db_view[:view_definition].strip.sub(/^create view \w* as (.*);$/, '\1')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Provide connection facilities to database on server
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require 'sequel'
|
6
|
+
|
7
|
+
require_relative 'automation_error'
|
8
|
+
require_relative 'database_connection_details'
|
9
|
+
|
10
|
+
module Dbsketch
|
11
|
+
module Automation
|
12
|
+
|
13
|
+
class DatabaseProxy
|
14
|
+
def initialize connection_details, output_pathame = nil
|
15
|
+
### Preconditions
|
16
|
+
raise ArgumentError, "connection_details is not a Dbsketch::Automation::DatabaseConnectionDetails" unless connection_details.is_a? DatabaseConnectionDetails
|
17
|
+
###
|
18
|
+
@connection_details = connection_details
|
19
|
+
@db = nil
|
20
|
+
@execution_output_pathname = output_pathame + "execution_result.txt" if nil != output_pathame
|
21
|
+
end
|
22
|
+
|
23
|
+
def establish_connection
|
24
|
+
@db = Sequel.connect(
|
25
|
+
:adapter => 'ado', #mssql
|
26
|
+
:host => @connection_details.full_host,
|
27
|
+
:database => @connection_details.database,
|
28
|
+
:user => @connection_details.user, #not needed via SSO
|
29
|
+
:password => @connection_details.password #not needed via SSO
|
30
|
+
#:encoding => Encoding::UTF_8, #only MySQL
|
31
|
+
)
|
32
|
+
@db.test_connection #force exception if problem occured
|
33
|
+
end
|
34
|
+
|
35
|
+
def call_procedure procedure, args = {}
|
36
|
+
@db.call_mssql_sproc procedure, :args => args
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](table_name)
|
40
|
+
### Preconditions
|
41
|
+
raise ArgumentError, "table_name is not a Symbol. If you want to use a qualified name (schema.name), please use the fetch function." unless table_name.is_a? Symbol
|
42
|
+
# @db[object] returns all records if object is a string, regardless of further filter or where statements (in: @db['name'].where(condition), where(condition) is ignored)
|
43
|
+
###
|
44
|
+
@db[table_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch query
|
48
|
+
### Preconditions
|
49
|
+
raise ArgumentError, "query is not a String" unless query.is_a? String
|
50
|
+
###
|
51
|
+
@db.fetch query
|
52
|
+
end
|
53
|
+
|
54
|
+
def run query
|
55
|
+
### Preconditions
|
56
|
+
raise ArgumentError, "query is not a String" unless query.is_a? String
|
57
|
+
###
|
58
|
+
@db.run query # raise an error if something goes wrong, otherwise returns nil
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute_file file_pathname
|
63
|
+
cmd =
|
64
|
+
"sqlcmd -d #{@connection_details.database} -S #{@connection_details.full_host} -i #{file_pathname.to_s.gsub(/\//, '\\')}" +
|
65
|
+
" -P #{@connection_details.password} -U #{@connection_details.user} -o #{@execution_output_pathname.to_s.gsub(/\//, '\\')}"
|
66
|
+
|
67
|
+
status = system cmd
|
68
|
+
output = File.readlines(@execution_output_pathname)
|
69
|
+
|
70
|
+
if not status or output.find { |o| o.match(/Msg \d+, Level \d+, State \d+/) } then
|
71
|
+
raise AutomationError, "Error while executing file #{file_pathname}! Extract from #{@execution_output_pathname}:\n #{output[0].strip}\n #{output[1].strip}"
|
72
|
+
end
|
73
|
+
status
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Import table into memory (Dbsketch::Model structure)
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative '../model/table'
|
6
|
+
require_relative 'automation_error'
|
7
|
+
require_relative 'database_proxy'
|
8
|
+
|
9
|
+
module Dbsketch
|
10
|
+
module Automation
|
11
|
+
|
12
|
+
class TableImporter
|
13
|
+
def initialize db
|
14
|
+
### Preconditions
|
15
|
+
raise ArgumentError, "database is not a Dbsketch::Automation::DatabaseProxy" unless db.is_a? DatabaseProxy
|
16
|
+
###
|
17
|
+
@db = db
|
18
|
+
end
|
19
|
+
|
20
|
+
def import table_name
|
21
|
+
### Preconditions
|
22
|
+
raise ArgumentError, "table_name '#{table_name}' is not a Symbol or a String" unless table_name.is_a? String or table_name.is_a? Symbol
|
23
|
+
###
|
24
|
+
db_table = @db.fetch("select object_id, name from sys.tables where name = '#{table_name.to_s}'").all.first
|
25
|
+
### Preconditions
|
26
|
+
raise AutomationError, "table '#{table_symbol}' not found in database" if nil == db_table
|
27
|
+
###
|
28
|
+
table = Dbsketch::Model::Table.new db_table[:name]
|
29
|
+
@db.fetch("select * from information_schema.columns where table_name = '#{db_table[:name]}'").all.each do |db_column|
|
30
|
+
table.add(parse_column db_table, db_column)
|
31
|
+
end
|
32
|
+
|
33
|
+
primary_key = import_primary_key db_table, table
|
34
|
+
table.add primary_key if nil != primary_key
|
35
|
+
|
36
|
+
@db.fetch("select name, definition from sys.check_constraints where parent_object_id = '#{db_table[:object_id]}'").all.each do |db_constraint|
|
37
|
+
table.add(Dbsketch::Model::CheckConstraint.new db_constraint[:name], db_constraint[:definition])
|
38
|
+
end
|
39
|
+
|
40
|
+
@db.fetch("select name from sys.key_constraints where parent_object_id = #{db_table[:object_id]} and type = 'UQ'").all.each do |db_constraint|
|
41
|
+
table.add(import_unique_constraint db_table, table, db_constraint[:name])
|
42
|
+
end
|
43
|
+
|
44
|
+
table
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse_type db_column
|
50
|
+
sizes = []
|
51
|
+
if nil != db_column[:character_maximum_length]
|
52
|
+
sizes << db_column[:character_maximum_length]
|
53
|
+
else
|
54
|
+
sizes << db_column[:numeric_precision] if 'int' != db_column[:data_type] and nil != db_column[:numeric_precision]
|
55
|
+
sizes << db_column[:numeric_scale] if 'int' != db_column[:data_type] and nil != db_column[:numeric_scale] and 0 != db_column[:numeric_scale]
|
56
|
+
end
|
57
|
+
Dbsketch::Model::Type.new db_column[:data_type], sizes
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_column_default db_default, type
|
61
|
+
default = db_default
|
62
|
+
if nil != default
|
63
|
+
if :numeric == type.category or (:boolean == type.category and default.match(/\(\([01]\)\)/))
|
64
|
+
default = default.gsub(/[\D]*/, '').to_i
|
65
|
+
elsif default.is_a? String
|
66
|
+
default = "'#{default.gsub(/^\('(.*)'\)$/, '\1')}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
default
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_column db_table, db_column
|
73
|
+
column_name = db_column[:column_name].to_s.strip
|
74
|
+
db_sys_column = @db.fetch("select is_nullable, is_computed, is_identity from sys.columns where object_id = #{db_table[:object_id]} and name = '#{column_name}'").all.first
|
75
|
+
nullable = db_sys_column[:is_nullable]
|
76
|
+
order = db_column[:ordinal_position]
|
77
|
+
computed = db_sys_column[:is_computed]
|
78
|
+
if computed
|
79
|
+
db_sys_computed_column = @db.fetch("select definition, is_persisted from sys.computed_columns where object_id = #{db_table[:object_id]} and name = '#{column_name}'").all.first
|
80
|
+
query = db_sys_computed_column[:definition]
|
81
|
+
Dbsketch::Model::ComputedColumn.new column_name, query, :nullable => nullable, :order => order, :persisted => db_sys_computed_column[:is_persisted]
|
82
|
+
else
|
83
|
+
type = parse_type db_column
|
84
|
+
identity = db_sys_column[:is_identity]
|
85
|
+
default = parse_column_default db_column[:column_default], type
|
86
|
+
Dbsketch::Model::Column.new column_name, type, :nullable => nullable, :order => order, :identity => identity, :default => default
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def import_primary_key db_table, table
|
91
|
+
primary_key = nil
|
92
|
+
db_sys_key = @db.fetch("select name from sys.key_constraints where parent_object_id = #{db_table[:object_id]} and type = 'PK'").all.first
|
93
|
+
if nil != db_sys_key
|
94
|
+
name = db_sys_key[:name]
|
95
|
+
pkey_columns = []
|
96
|
+
@db.fetch("select column_name from information_schema.constraint_column_usage where constraint_name = '#{name}'").all.each do |db_col|
|
97
|
+
pkey_columns << table[db_col[:column_name]]
|
98
|
+
end
|
99
|
+
primary_key = Dbsketch::Model::PrimaryKey.new name, pkey_columns
|
100
|
+
end
|
101
|
+
primary_key
|
102
|
+
end
|
103
|
+
|
104
|
+
def import_unique_constraint db_table, table, constraint_name
|
105
|
+
columns = []
|
106
|
+
@db.fetch("select column_name from information_schema.constraint_column_usage where constraint_name = '#{constraint_name}'").all.each do |db_col|
|
107
|
+
columns << table[db_col[:column_name]]
|
108
|
+
end
|
109
|
+
Dbsketch::Model::UniqueConstraint.new constraint_name, columns
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|