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
@@ -0,0 +1,23 @@
1
+ ########################################################################################################################
2
+ # Defines database function
3
+ ########################################################################################################################
4
+
5
+ require_relative 'operation'
6
+
7
+ module Dbsketch
8
+ module Model
9
+
10
+ class Function < Operation
11
+ def initialize name, meaning: nil, comment: nil, dependencies: [], arguments: [], returns:, algo:
12
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies, :arguments => arguments, :algo => algo
13
+ ### Preconditions
14
+ raise ArgumentError, "returns is not a String" unless returns.is_a? String
15
+ ###
16
+ @returns = returns
17
+ end
18
+
19
+ attr_reader :returns
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ ########################################################################################################################
2
+ # Defines database column
3
+ ########################################################################################################################
4
+
5
+ require_relative 'abstract_column'
6
+ require_relative 'computed_column'
7
+ require_relative 'database_object'
8
+ require_relative 'table'
9
+ require_relative 'model_error'
10
+ require_relative 'view'
11
+
12
+ module Dbsketch
13
+ module Model
14
+
15
+ class Index < Database_Object
16
+ def initialize name, target, columns, meaning: nil, comment: nil, dependencies: []
17
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies
18
+ @columns = columns.is_a?(Array) ? columns : [columns]
19
+ ### Preconditions
20
+ raise ArgumentError, "target is not a Dbsketch::Model::Table or a View" unless target.is_a? Table or target.is_a? View
21
+ @columns.each_with_index do |column, index|
22
+ raise ArgumentError, "columns[#{index}] is not a Dbsketch::Model::AbstractColumn" unless column.is_a? AbstractColumn
23
+ raise ModelError, "computed column #{column.name} is not persisted" if column.is_a? ComputedColumn and not column.persisted
24
+ end
25
+ ###
26
+ @target = target
27
+ end
28
+
29
+ attr_reader :target, :columns
30
+
31
+ def has_column? column_name
32
+ ### Preconditions
33
+ raise ArgumentError, "column_name is not a String" unless column_name.is_a? String
34
+ ###
35
+ nil != @columns.find { |c| c.name.downcase == column_name.downcase }
36
+ end
37
+ end
38
+
39
+ end
40
+ 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 Model
7
+
8
+ class ModelError < StandardError
9
+ def initialize message
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ ########################################################################################################################
2
+ # Defines database operation (function and procedure common stuff)
3
+ ########################################################################################################################
4
+
5
+ require_relative 'database_object'
6
+
7
+ module Dbsketch
8
+ module Model
9
+
10
+ class Operation < Database_Object
11
+ def initialize name, meaning: nil, comment: nil, dependencies: [], arguments: [], algo:
12
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies
13
+ arguments = arguments.is_a?(Array) ? arguments : [arguments]
14
+ ### Preconditions
15
+ arguments.each_with_index do |arg, index|
16
+ raise ArgumentError, "arguments[#{index}] is not a String" unless arg.is_a? String
17
+ end
18
+ raise ArgumentError, "algo is not a String" unless algo.is_a? String
19
+ ###
20
+ @arguments = arguments
21
+ @algo = algo
22
+ end
23
+
24
+ attr_reader :algo, :arguments
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ ########################################################################################################################
2
+ # Primary Key
3
+ ########################################################################################################################
4
+
5
+ require_relative 'column'
6
+ require_relative 'database_object'
7
+
8
+ module Dbsketch
9
+ module Model
10
+
11
+ class PrimaryKey < Database_Object
12
+ def initialize name, columns, meaning: nil, comment: nil
13
+ super name, :meaning => meaning, :comment => comment
14
+ @columns = columns.is_a?(Array) ? columns : [columns]
15
+ ### Preconditions
16
+ @columns.each_with_index do |column, index|
17
+ raise ArgumentError, "columns[#{index}] is not a Dbsketch::Model::Column" unless column.is_a? Column
18
+ end
19
+ ###
20
+ end
21
+
22
+ attr_reader :columns
23
+
24
+ def has_column? column_name
25
+ ### Preconditions
26
+ raise ArgumentError, "column_name is not a String" unless column_name.is_a? String
27
+ ###
28
+ nil != @columns.find { |c| c.name.downcase == column_name.downcase }
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ ########################################################################################################################
2
+ # Defines database stored procedure
3
+ ########################################################################################################################
4
+
5
+ require_relative 'operation'
6
+
7
+ module Dbsketch
8
+ module Model
9
+
10
+ class Procedure < Operation
11
+ def initialize name, meaning: nil, comment: nil, dependencies: [], arguments: [], algo:
12
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies, :arguments => arguments, :algo => algo
13
+ # TODO check arguments are one of [out, output, readonly]
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,171 @@
1
+ ########################################################################################################################
2
+ # Defines database table structure
3
+ ########################################################################################################################
4
+
5
+ require_relative 'abstract_column'
6
+ require_relative 'check_constraint'
7
+ require_relative 'column'
8
+ require_relative 'database_object'
9
+ require_relative 'foreign_key'
10
+ require_relative 'model_error'
11
+ require_relative 'primary_key'
12
+ require_relative 'unique_constraint'
13
+
14
+ module Dbsketch
15
+ module Model
16
+
17
+ class Table < Database_Object
18
+ def initialize name, meaning: nil, comment: nil, dependencies: [], columns: [], p_key_columns: []
19
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies
20
+ columns = columns.is_a?(Array) ? columns.flatten : [columns]
21
+ p_key_columns = p_key_columns.is_a?(Array) ? p_key_columns : [p_key_columns]
22
+ ### Preconditions
23
+ columns.each_with_index do |column, index|
24
+ raise ArgumentError, "columns[#{index}] is not a Dbsketch::Model::AbstractColumn" unless column.is_a? AbstractColumn
25
+ end
26
+ p_key_columns.each_with_index do |column, index|
27
+ raise ArgumentError, "p_key_columns[#{index}] is not a String nor a Dbsketch::Model::AbstractColumn" unless column.is_a? String or column.is_a? AbstractColumn
28
+ end
29
+ ###
30
+ @columns = []
31
+ columns.each { |c| add_column c }
32
+ @primary_key = nil
33
+ primary_key_columns = p_key_columns.map { |c| (c.is_a? AbstractColumn) ? c : self[c] }
34
+ set_primary_key PrimaryKey.new "PK_#{@name}", primary_key_columns if not primary_key_columns.empty?
35
+ @check_constraints = []
36
+ @foreign_keys = []
37
+ @unique_constraints = []
38
+ end
39
+
40
+ attr_reader :columns, :primary_key, :check_constraints, :foreign_keys, :unique_constraints
41
+
42
+ def add object
43
+ objects = object.is_a?(Array) ? object : [object]
44
+ ### Preconditions
45
+ objects.each_with_index do |o, index|
46
+ raise ArgumentError, "object n°#{index + 1} is not a valid Dbsketch::Model object" unless o.is_a? AbstractColumn or o.is_a? PrimaryKey or o.is_a? CheckConstraint or o.is_a? ForeignKey or o.is_a? UniqueConstraint
47
+ end
48
+ ###
49
+ objects.each do |o|
50
+ if o.is_a? AbstractColumn
51
+ add_column o
52
+ elsif o.is_a? PrimaryKey
53
+ set_primary_key o
54
+ elsif o.is_a? CheckConstraint
55
+ add_check_constraint o
56
+ elsif o.is_a? ForeignKey
57
+ add_foreign_key o
58
+ elsif o.is_a? UniqueConstraint
59
+ add_unique_constraint o
60
+ end
61
+ end
62
+ end
63
+
64
+ def has_column? column_name
65
+ ### Preconditions
66
+ raise ArgumentError, "column_name is not a String" unless column_name.is_a? String
67
+ ###
68
+ nil != @columns.find { |c| c.name.downcase == column_name.downcase }
69
+ end
70
+
71
+ def has_check_constraint? constraint_name
72
+ ### Preconditions
73
+ raise ArgumentError, "constraint_name is not a String" unless constraint_name.is_a? String
74
+ ###
75
+ nil != @check_constraints.find { |c| c.name.downcase == constraint_name.downcase }
76
+ end
77
+
78
+ def has_foreign_key? key_name
79
+ ### Preconditions
80
+ raise ArgumentError, "key_name is not a String" unless key_name.is_a? String
81
+ ###
82
+ nil != @foreign_keys.find { |c| c.name.downcase == key_name.downcase }
83
+ end
84
+
85
+ def has_unique_constraint? constraint_name
86
+ ### Preconditions
87
+ raise ArgumentError, "constraint_name is not a String" unless constraint_name.is_a? String
88
+ ###
89
+ nil != @unique_constraints.find { |c| c.name.downcase == constraint_name.downcase }
90
+ end
91
+
92
+ def [] column_name
93
+ column = @columns.find { |c| c.name.downcase == column_name.downcase }
94
+ raise ArgumentError, "column #{column_name} not found in table #{@name}" if nil == column
95
+ column
96
+ end
97
+
98
+ def check_constraint constraint_name
99
+ constraint = @check_constraints.find { |c| c.name.downcase == constraint_name.downcase }
100
+ raise ArgumentError, "check constraint #{constraint_name} not found in table #{@name}" if nil == constraint
101
+ constraint
102
+ end
103
+
104
+ def foreign_key key_name
105
+ key = @foreign_keys.find { |c| c.name.downcase == key_name.downcase }
106
+ raise ArgumentError, "foreign key #{key_name} not found in table #{@name}" if nil == key
107
+ key
108
+ end
109
+
110
+ def unique_constraint constraint_name
111
+ constraint = @unique_constraints.find { |c| c.name.downcase == constraint_name.downcase }
112
+ raise ArgumentError, "unique constraint #{constraint_name} not found in table #{@name}" if nil == constraint
113
+ constraint
114
+ end
115
+
116
+ private
117
+
118
+ def add_column column
119
+ ### Preconditions
120
+ raise ModelError, "column #{column.name} already exists in table #{@name}" if nil != (@columns.find { |c| c.name.downcase == column.name.downcase })
121
+ raise ModelError, "column #{column.name} has an order but column without order exists in table #{@name}" if nil != column.order and nil != (@columns.find { |c| nil == c.order })
122
+ raise ModelError, "a column of order #{column.order} already exists in table #{@name}" if nil != (@columns.find { |c| nil != c.order and c.order == column.order })
123
+ ###
124
+ @columns << column
125
+ end
126
+
127
+ def set_primary_key key
128
+ ### Preconditions
129
+ raise ModelError, "a primary key already exists in table #{@name}" if nil != @primary_key
130
+ key.columns.each do |pk_c|
131
+ raise ModelError, "primary key column #{pk_c.name} does not exists in table #{@name}" if nil == (@columns.find { |c| c.name.downcase == pk_c.name.downcase })
132
+ end
133
+ ###
134
+ @primary_key = key
135
+ end
136
+
137
+ def constraint_exist? constraint
138
+ (nil != (@check_constraints.find { |c| c.name.downcase == constraint.name.downcase }) or
139
+ nil != (@foreign_keys.find { |k| k.name.downcase == constraint.name.downcase }) or
140
+ nil != (@unique_constraints.find { |c| c.name.downcase == constraint.name.downcase }))
141
+ end
142
+
143
+ def add_check_constraint constraint
144
+ ### Preconditions
145
+ raise ModelError, "constraint #{constraint.name} already exists in table #{@name}" if constraint_exist? constraint
146
+ ###
147
+ @check_constraints << constraint
148
+ end
149
+
150
+ def add_foreign_key constraint
151
+ ### Preconditions
152
+ raise ModelError, "constraint #{constraint.name} already exists in table #{@name}" if constraint_exist? constraint
153
+ raise ModelError, "constraint #{constraint.name} column #{constraint.constricted_column.name} does not exists in table #{@name}" if nil == (@columns.find { |c| c.name.downcase == constraint.constricted_column.name.downcase })
154
+ ###
155
+ @foreign_keys << constraint
156
+ add_dependencies constraint.referenced_table
157
+ end
158
+
159
+ def add_unique_constraint constraint
160
+ ### Preconditions
161
+ raise ModelError, "constraint #{constraint.name} already exists in table #{@name}" if constraint_exist? constraint
162
+ constraint.columns.each do |uc_c|
163
+ raise ModelError, "constraint column #{uc_c.name} does not exists in table #{@name}" if nil == (@columns.find { |c| c.name.downcase == uc_c.name.downcase })
164
+ end
165
+ ###
166
+ @unique_constraints << constraint
167
+ end
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,32 @@
1
+ ########################################################################################################################
2
+ # Defines database trigger
3
+ ########################################################################################################################
4
+
5
+ require_relative 'database_object'
6
+ require_relative 'model_error'
7
+ require_relative 'table'
8
+ require_relative 'view'
9
+
10
+ module Dbsketch
11
+ module Model
12
+
13
+ class Trigger < Database_Object
14
+ def initialize name, target, activation_time, algo:, meaning: nil, comment: nil, dependencies: []
15
+ super name, :meaning => meaning, :comment => comment, :dependencies => dependencies
16
+ ### Preconditions
17
+ raise ArgumentError, "target is not a Dbsketch::Model::Table or View" unless target.is_a? Table or target.is_a? View
18
+ raise ArgumentError, "activation_time is not a String" unless activation_time.is_a? String
19
+ raise ModelError, "activation_time does not begins 'for', 'after' or 'instead of' (#{activation_time})" if nil == activation_time.downcase.match(/^(for)|(after)|(instead of)/)
20
+ raise ArgumentError, "algo is not a String" unless algo.is_a? String
21
+ ###
22
+ @target = target
23
+ @activation_time = activation_time
24
+ @algo = algo
25
+ add_dependencies target
26
+ end
27
+
28
+ attr_reader :target, :activation_time, :algo
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,92 @@
1
+ ########################################################################################################################
2
+ # Represents database type, allow to check compatibility
3
+ ########################################################################################################################
4
+
5
+ module Dbsketch
6
+ module Model
7
+
8
+ class Type
9
+ def initialize(sql_type, sizes = [])
10
+ @sizes = sizes.is_a?(Array) ? sizes : [sizes]
11
+ ### Preconditions
12
+ raise ArgumentError, "sql_type is not a String" unless sql_type.is_a? String
13
+ @sizes.each do |size|
14
+ if 'max' == size
15
+ if not ['varchar', 'nvarchar'].include? sql_type
16
+ raise ArgumentError, "size 'max' allowed only for [n]varchar"
17
+ end
18
+ elsif not size.is_a? Fixnum and not size.is_a? Integer
19
+ raise ArgumentError, "size #{size} of sql type #{sql_type} is not a number"
20
+ end
21
+ end
22
+ raise ArgumentError, "#{sql_type} do not allow defining sizes" if ['datetime', 'datetime2', 'int'].include? sql_type and @sizes.count > 0
23
+ raise ArgumentError, "#{sql_type} do not allow more than one size" if sql_type.match(/(n){0,1}(var){0,1}char/) and @sizes.count > 1
24
+ raise ArgumentError, "#{sql_type} need at least a precision, but no sizes were given" if ['decimal', 'numeric'].include? sql_type and @sizes.count == 0
25
+ raise ArgumentError, "defining more than 2 sizes for #{sql_type}" if @sizes.count > 2
26
+ ###
27
+ @sql_type = sql_type
28
+ end
29
+
30
+ attr_reader :sql_type, :sizes
31
+
32
+ # Return true if current type can be stored in other_type without error nor information loss
33
+ def compatible_with? other_type
34
+ ### Preconditions
35
+ raise ArgumentError, "other_type is not a Dbsketch::Model::Type" unless other_type.is_a? Dbsketch::Model::Type
36
+ ###
37
+ # TODO enhance this function (numeric and int may be compatible with particular sizes)
38
+ compatible = false
39
+
40
+ if :numeric == get_category(@sql_type)
41
+ if :numeric == get_category(other_type.sql_type)
42
+ compatible = size_compatibile_with? other_type.sizes
43
+ end
44
+ elsif :datetime == get_category(@sql_type)
45
+ compatible = (:datetime == get_category(other_type.sql_type))
46
+ elsif :string == get_category(@sql_type)
47
+ compatible = false
48
+ if :string == get_category(other_type.sql_type)
49
+ if (@sql_type.match(/^(var){0,1}char$/) and other_type.sql_type.match(/^(var){0,1}char$/)) or
50
+ (@sql_type.match(/^n(var){0,1}char$/) and other_type.sql_type.match(/^n(var){0,1}char$/))
51
+ compatible = size_compatibile_with? other_type.sizes
52
+ end
53
+ end
54
+ elsif :boolean == get_category(@sql_type)
55
+ compatible = (:boolean == get_category(other_type.sql_type))
56
+ end
57
+ compatible
58
+ end
59
+
60
+ def category
61
+ get_category(@sql_type)
62
+ end
63
+
64
+ private
65
+
66
+ def get_category sql_type
67
+ if ['decimal', 'int', 'numeric'].include? sql_type
68
+ :numeric
69
+ elsif ['datetime', 'datetime2'].include? sql_type
70
+ :datetime
71
+ elsif sql_type.match(/(n){0,1}(var){0,1}char/)
72
+ :string
73
+ elsif 'bit' == sql_type
74
+ :boolean
75
+ else
76
+ :unknown
77
+ end
78
+ end
79
+
80
+ def size_compatibile_with? other_sizes
81
+ compatible = false
82
+ if @sizes.empty? and other_sizes.empty?
83
+ compatible = true
84
+ elsif not @sizes.empty? and not other_sizes.empty?
85
+ compatible = (@sizes.first <= other_sizes.first)
86
+ end
87
+ compatible
88
+ end
89
+ end
90
+
91
+ end
92
+ end