dbsketch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|