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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +1 -0
  4. data/lib/dbsketch.rb +64 -0
  5. data/lib/dbsketch/automation/automation_error.rb +15 -0
  6. data/lib/dbsketch/automation/database_connection_details.rb +32 -0
  7. data/lib/dbsketch/automation/database_importer.rb +184 -0
  8. data/lib/dbsketch/automation/database_proxy.rb +78 -0
  9. data/lib/dbsketch/automation/table_importer.rb +114 -0
  10. data/lib/dbsketch/comparison/check_constraint_comparator.rb +54 -0
  11. data/lib/dbsketch/comparison/column_comparator.rb +65 -0
  12. data/lib/dbsketch/comparison/comparison_error.rb +15 -0
  13. data/lib/dbsketch/comparison/computed_column_comparator.rb +63 -0
  14. data/lib/dbsketch/comparison/database_comparator.rb +115 -0
  15. data/lib/dbsketch/comparison/diff.rb +37 -0
  16. data/lib/dbsketch/comparison/foreign_key_comparator.rb +56 -0
  17. data/lib/dbsketch/comparison/function_comparator.rb +56 -0
  18. data/lib/dbsketch/comparison/index_comparator.rb +65 -0
  19. data/lib/dbsketch/comparison/primary_key_comparator.rb +61 -0
  20. data/lib/dbsketch/comparison/procedure_comparator.rb +54 -0
  21. data/lib/dbsketch/comparison/table_comparator.rb +158 -0
  22. data/lib/dbsketch/comparison/trigger_comparator.rb +53 -0
  23. data/lib/dbsketch/comparison/type_comparator.rb +51 -0
  24. data/lib/dbsketch/comparison/unique_constraint_comparator.rb +58 -0
  25. data/lib/dbsketch/comparison/view_comparator.rb +54 -0
  26. data/lib/dbsketch/model/abstract_column.rb +25 -0
  27. data/lib/dbsketch/model/check_constraint.rb +23 -0
  28. data/lib/dbsketch/model/column.rb +35 -0
  29. data/lib/dbsketch/model/computed_column.rb +27 -0
  30. data/lib/dbsketch/model/custom_code.rb +21 -0
  31. data/lib/dbsketch/model/database.rb +87 -0
  32. data/lib/dbsketch/model/database_object.rb +71 -0
  33. data/lib/dbsketch/model/foreign_key.rb +29 -0
  34. data/lib/dbsketch/model/function.rb +23 -0
  35. data/lib/dbsketch/model/index.rb +40 -0
  36. data/lib/dbsketch/model/model_error.rb +15 -0
  37. data/lib/dbsketch/model/operation.rb +28 -0
  38. data/lib/dbsketch/model/primary_key.rb +33 -0
  39. data/lib/dbsketch/model/procedure.rb +18 -0
  40. data/lib/dbsketch/model/table.rb +171 -0
  41. data/lib/dbsketch/model/trigger.rb +32 -0
  42. data/lib/dbsketch/model/type.rb +92 -0
  43. data/lib/dbsketch/model/unique_constraint.rb +33 -0
  44. data/lib/dbsketch/model/view.rb +23 -0
  45. data/lib/dbsketch/rendering/meta/column_renderer.rb +76 -0
  46. data/lib/dbsketch/rendering/meta/database_renderer.rb +112 -0
  47. data/lib/dbsketch/rendering/meta/foreign_key_renderer.rb +31 -0
  48. data/lib/dbsketch/rendering/meta/index_renderer.rb +30 -0
  49. data/lib/dbsketch/rendering/meta/operation_renderer.rb +66 -0
  50. data/lib/dbsketch/rendering/meta/table_renderer.rb +177 -0
  51. data/lib/dbsketch/rendering/meta/trigger_renderer.rb +31 -0
  52. data/lib/dbsketch/rendering/meta/type_renderer.rb +37 -0
  53. data/lib/dbsketch/rendering/meta/view_renderer.rb +32 -0
  54. data/lib/dbsketch/rendering/sql/column_renderer.rb +75 -0
  55. data/lib/dbsketch/rendering/sql/database_diff_renderer.rb +94 -0
  56. data/lib/dbsketch/rendering/sql/database_renderer.rb +139 -0
  57. data/lib/dbsketch/rendering/sql/foreign_key_renderer.rb +24 -0
  58. data/lib/dbsketch/rendering/sql/index_renderer.rb +31 -0
  59. data/lib/dbsketch/rendering/sql/operation_renderer.rb +64 -0
  60. data/lib/dbsketch/rendering/sql/table_renderer.rb +148 -0
  61. data/lib/dbsketch/rendering/sql/trigger_renderer.rb +31 -0
  62. data/lib/dbsketch/rendering/sql/type_renderer.rb +28 -0
  63. data/lib/dbsketch/rendering/sql/view_renderer.rb +31 -0
  64. data/lib/dbsketch/version.rb +3 -0
  65. 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