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
@@ -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