dbsketch 0.0.1

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