meta_db 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b95c7477345548208fedec3a8b23fec8f99976679d2a7cda7a862da2a55e065
4
- data.tar.gz: b1fd9306b6a6fa4ea129e2774b00a9cba4498fdcdb0f7bc9292009c8e8a8318a
3
+ metadata.gz: a51836f5ebbe95799043394f44b151d2e722eecaae0f5be6a993c3a35eb353cc
4
+ data.tar.gz: 0f81dcdfdec3f3033fa7726fae89f734da23e783ce473b737b2e2b3150c67c1b
5
5
  SHA512:
6
- metadata.gz: 69e015dab2b969d70e665b07963d860ab8d0791dc24d3e9c13ab1e424a6e8c1194cb2f58b2c222185f6ee7cc45ff36a3bedf5422a48196dc9257b0972c9cafa5
7
- data.tar.gz: 69b2dd7664171a1be23993da78dde65720d2bee94e126fd77839d5f921fc432697d439e2a88aea00d0ce555489adc1949ad1dfb02bb9815c0a8c97357f98bad5
6
+ metadata.gz: b916d8bb0995189452ba838199bd8c887b56aba3d5d63399fcd6fb4a270ed8fbe506127edc98a1c7bf82c331d57e5ded69aa0c69eaaf8e93ab88838b5bd21f47
7
+ data.tar.gz: ccafad6e13a9d0eab5d130935f94d24a294d7cd85462fd871e3ef02fb901a5d60ecbb71feb852e9d82ae99237c79687ac47766525341ae48d584d8f00cb18d37
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.6.6
1
+ ruby-2.7.0
data/lib/meta_db.rb CHANGED
@@ -51,12 +51,12 @@ private
51
51
  where d.datname = '#{conn.database}'
52
52
  )
53
53
  r.size == 1 or raise "Internal error"
54
- db = MetaDb::Database.new(conn.database, r.each_array.first)
54
+ db = MetaDb::Database.new(conn.database, r.value)
55
55
 
56
56
  # Build schemas
57
57
  conn.select(%(
58
- select schema_name::varchar as name,
59
- schema_owner::varchar as owner
58
+ select schema_name::text as name,
59
+ schema_owner::text as owner
60
60
  from information_schema.schemata
61
61
  where schema_name !~ '^pg_' AND schema_name <> 'information_schema'
62
62
  )).each_hash { |row|
@@ -66,45 +66,47 @@ private
66
66
 
67
67
  # Build tables
68
68
  conn.select(%(
69
- select table_schema::varchar as schema,
70
- table_name::varchar as name,
71
- table_type::varchar as type,
69
+ select table_schema::text as schema,
70
+ table_name::text as name,
71
+ table_type::text as type,
72
72
  is_insertable_into = 'YES' as "insertable?",
73
73
  is_typed = 'YES' as "typed?"
74
74
  from information_schema.tables
75
75
  where table_schema !~ '^pg_' AND table_schema <> 'information_schema'
76
76
  )).each_hash { |row|
77
- row[:schema] = db.dot row[:schema]
77
+ row[:schema] = db.schemas[row[:schema]]
78
78
  klass = (row[:type] == 'VIEW' ? MetaDb::View : MetaDb::Table)
79
79
  klass.init(row)
80
80
  }
81
81
 
82
82
  # Build columns
83
83
  conn.select(%(
84
- select table_schema::varchar || '.' || table_name as table,
84
+ select table_schema::text as schema,
85
+ table_name::text as table,
86
+ column_name::text as name,
85
87
  ordinal_position as ordinal,
86
- column_name::varchar as name,
87
- data_type::varchar as type,
88
+ data_type::text as type,
88
89
  column_default as default,
89
90
  is_identity = 'YES' as "identity?",
90
- is_generated = 'YES' as "generated?",
91
+ is_generated = 'ALWAYS' as "generated?",
91
92
  is_nullable = 'YES' as "nullable?",
92
93
  is_updatable = 'YES' as "updatable?"
93
94
  from information_schema.columns
94
95
  where table_schema !~ '^pg_' AND table_schema <> 'information_schema'
95
96
  )).each_hash { |row|
96
- row[:table] = db.dot row[:table]
97
+ row[:table] = db.schemas[row[:schema]].tables[row[:table]]
97
98
  MetaDb::Column.init(row)
98
99
  }
99
100
 
100
101
  # Build simple constraints
101
102
  conn.select(%(
102
- select c.constraint_type::varchar,
103
- c.table_schema || '.' || c.table_name as table,
104
- c.constraint_name::varchar as name,
103
+ select c.table_schema::text as schema,
104
+ c.table_name::text as table,
105
+ c.constraint_type::text,
106
+ c.constraint_name::text as name,
105
107
  cc.check_clause as expression,
106
108
  (
107
- select array_agg(column_name::varchar)
109
+ select array_agg(column_name::text)
108
110
  from information_schema.constraint_column_usage ccu
109
111
  where ccu.table_schema = c.table_schema
110
112
  and ccu.table_name = c.table_name
@@ -112,13 +114,13 @@ private
112
114
  and ccu.constraint_name = c.constraint_name
113
115
  ) as columns
114
116
  from information_schema.table_constraints c
115
- left join information_schema.check_constraints cc
116
- on cc.constraint_schema = c.table_schema and
117
- cc.constraint_name = c.constraint_name
117
+ left join information_schema.check_constraints cc
118
+ on cc.constraint_schema = c.table_schema and
119
+ cc.constraint_name = c.constraint_name
118
120
  where c.table_schema !~ '^pg_' AND c.table_schema <> 'information_schema'
119
121
  and c.constraint_type in ('PRIMARY KEY', 'UNIQUE', 'CHECK')
120
122
  )).each_hash { |row|
121
- row[:table] = db.dot row[:table]
123
+ row[:table] = db.schemas[row[:schema]].tables[row[:table]]
122
124
  row[:columns] = lookup_columns(row[:table], row[:columns] || [])
123
125
  case row[:constraint_type]
124
126
  when "PRIMARY KEY"; MetaDb::PrimaryKeyConstraint.init(row)
@@ -139,22 +141,24 @@ private
139
141
  # key (TODO: Can this be omitted?)
140
142
  #
141
143
  conn.select(%(
142
- select rc.constraint_schema::varchar as schema,
143
- rc.constraint_name::varchar as name,
144
- cu_refing.table_schema || '.' || cu_refing.table_name as "referencing_table",
144
+ select rc.constraint_schema::text as schema,
145
+ rc.constraint_name::text as name,
146
+ cu_refing.table_schema::text as "referencing_schema",
147
+ cu_refing.table_name::text as "referencing_table",
145
148
  (
146
- select array_agg(column_name::varchar order by ordinal_position)
149
+ select array_agg(column_name::text order by ordinal_position)
147
150
  from information_schema.key_column_usage kcu
148
151
  where kcu.constraint_name = rc.constraint_name
149
152
  ) as "referencing_columns",
150
- cu_refed.table_schema || '.' || cu_refed.table_name || '.' || cu_refed.constraint_name
151
- as "referenced_constraint"
153
+ cu_refed.table_schema::text as referenced_schema,
154
+ cu_refed.table_name::text as referenced_table,
155
+ cu_refed.constraint_name::text as referenced_constraint
152
156
  from information_schema.referential_constraints rc
153
- join information_schema.key_column_usage cu_refing on
154
- cu_refing.constraint_schema = rc.constraint_schema
157
+ join information_schema.key_column_usage cu_refing
158
+ on cu_refing.constraint_schema = rc.constraint_schema
155
159
  and cu_refing.constraint_name = rc.constraint_name
156
- join information_schema.key_column_usage cu_refed on
157
- cu_refed.constraint_schema = rc.unique_constraint_schema
160
+ join information_schema.key_column_usage cu_refed
161
+ on cu_refed.constraint_schema = rc.unique_constraint_schema
158
162
  and cu_refed.constraint_name = rc.unique_constraint_name
159
163
  where cu_refing.table_schema !~ '^pg_' AND cu_refing.table_schema <> 'information_schema'
160
164
  group by
@@ -166,17 +170,81 @@ private
166
170
  cu_refed.table_name,
167
171
  cu_refed.constraint_name
168
172
  )).each_hash { |row|
169
- row[:schema] = db.dot row[:schema]
170
- row[:referencing_table] = db.dot row[:referencing_table]
173
+ row[:schema] = db.schemas[row[:schema]]
174
+ row[:referencing_table] = row[:schema].tables[row[:referencing_table]]
171
175
  row[:referencing_columns] = lookup_columns(row[:referencing_table], row[:referencing_columns])
172
- row[:referenced_constraint] = db.dot row[:referenced_constraint]
176
+ row[:referenced_constraint] =
177
+ db.schemas[row[:referenced_schema]] \
178
+ .tables[row[:referenced_table]] \
179
+ .constraints[row[:referenced_constraint]]
173
180
  MetaDb::ReferentialConstraint.init(row)
174
181
  }
182
+
183
+ # Build functions and procedures
184
+ conn.select(%(
185
+ select s.nspname::text as "schema",
186
+ pg_get_userbyid(p.proowner)::text as "owner",
187
+ format('%I(%s)', p.proname, oidvectortypes(p.proargtypes))
188
+ || ': '
189
+ || format_type(p.prorettype, null) as "name",
190
+ case format_type(p.prorettype, null)
191
+ when 'void' then 'procedure'
192
+ else 'function'
193
+ end as "kind",
194
+ case
195
+ when prosecdef then 'definer'
196
+ else 'invoker'
197
+ end as "security"
198
+ from pg_proc p
199
+ join pg_namespace s on (p.pronamespace = s.oid)
200
+ where s.nspname !~ '^pg_' AND s.nspname <> 'information_schema'
201
+ )).each_hash { |row|
202
+ row[:schema] = db.schemas[row[:schema]]
203
+ klass = (row[:kind] == 'function' ? MetaDb::Function : MetaDb::Procedure)
204
+ klass.init(row)
205
+ }
206
+
207
+ # Build user-defined triggers
208
+ conn.select(%(
209
+ select n.nspname::text as "schema",
210
+ c.relname::text as "table",
211
+ t.tgname::text as "name",
212
+ fn.nspname::text as "function_schema",
213
+ format('%I(%s)', p.proname::text, oidvectortypes(p.proargtypes))
214
+ || ': '
215
+ || format_type(p.prorettype, null) as "function_name",
216
+ case when (tgtype::int::bit(7) & b'0000001')::int = 0 then 'stmt' else 'row' end as "level",
217
+ coalesce(
218
+ case when (tgtype::int::bit(7) & b'0000010')::int = 0 then null else 'before' end,
219
+ case when (tgtype::int::bit(7) & b'0000010')::int = 0 then 'after' else null end,
220
+ case when (tgtype::int::bit(7) & b'1000000')::int = 0 then null else 'instead' end,
221
+ ''
222
+ )::text as "timing",
223
+ (case when (tgtype::int::bit(7) & b'0000100')::int = 0 then '' else ' insert' end) ||
224
+ (case when (tgtype::int::bit(7) & b'0001000')::int = 0 then '' else ' delete' end) ||
225
+ (case when (tgtype::int::bit(7) & b'0010000')::int = 0 then '' else ' update' end) ||
226
+ (case when (tgtype::int::bit(7) & b'0100000')::int = 0 then '' else ' truncate' end)
227
+ as "events"
228
+ from pg_trigger t
229
+ join pg_proc p on t.tgfoid = p.oid
230
+ join pg_class c on c.oid = t.tgrelid
231
+ join pg_namespace n on n.oid = c.relnamespace
232
+ join pg_namespace fn on fn.oid = p.pronamespace
233
+ where not t.tgisinternal
234
+ )).each_hash { |row|
235
+ schema = db.schemas[row[:schema]]
236
+ row[:table] = schema.tables[row[:table]]
237
+ function_schema = db.schemas[row[:function_schema]]
238
+ row[:function] = function_schema.functions[row[:function_name]]
239
+ MetaDb::Trigger.init(row)
240
+ }
241
+
242
+ # Return database object
175
243
  db
176
244
  end
177
245
 
178
246
  def self.lookup_columns(table, column_names)
179
- column_names.map { |n| table.children[n] }
247
+ column_names.map { |n| table.columns[n] }
180
248
  end
181
249
  end
182
250
 
@@ -1,5 +1,8 @@
1
1
 
2
2
  module MetaDb
3
+
4
+ # FIXME Why on earth did I do this?
5
+ #
3
6
  class Connection
4
7
  # In derived classes, no initialization may take place after the call to
5
8
  # super because otherwise #initialize would call the user-supplied block
@@ -43,6 +46,20 @@ module MetaDb
43
46
  class Result
44
47
  def initialize(result) @result = result end
45
48
  def size() raise end
49
+
50
+ # Returns the value of the first field of the first record in the result
51
+ # set
52
+ def value() each_tuple.first.first end
53
+
54
+ # Returns a hash of the first record in the result set
55
+ def record() each_hash.first end
56
+
57
+ # Returns an array of the first record in the result set
58
+ def tuple() each_tupe.first end
59
+
60
+ def each_record() each_hash end
61
+ def each_tuple() each_array end
62
+
46
63
  def each_hash() raise end
47
64
  def each_array() raise end
48
65
  def to_a() each_array end
@@ -3,9 +3,8 @@ require 'meta_db/dump.rb'
3
3
 
4
4
  module MetaDb
5
5
  class DbObject
6
- # Used to multi-assign values and for dump
7
- #
8
- # TODO: rename db_attr or 'column', and create read/writer methods
6
+ # ::attrs defines the set of members of an object. It is used in
7
+ # initialization (in #init) and in #dump
9
8
  def self.attrs(*attrs)
10
9
  attrs = Array(attrs)
11
10
  if attrs.empty?
@@ -15,102 +14,38 @@ module MetaDb
15
14
  end
16
15
  end
17
16
 
17
+ # Initialize a DbObject. row is a hash with symbolic keys (typically the
18
+ # result of a database query). Values of keys included in ::attrs are
19
+ # extracted from the row and passed to the #initialize method of the object
20
+ # in the same order as in ::attrs
18
21
  def self.init(row)
19
22
  h = []
20
- self.attrs.each { |a| h << row[a] if row.key?(a) }
23
+ (@attrs ||= []).each { |a| h << row[a] if row.key?(a) }
21
24
  self.new(*h)
22
25
  end
23
26
 
24
- attrs :parent, :name, :children
25
-
26
- # Name of object. Unique within contianing object. Not nil
27
- attr_reader :name
28
-
29
- # Parent DbObject. Non-nil except for the top-level MetaDb::Database object
30
- attr_reader :parent
31
-
32
- # Hash from name to contained objects
33
- attr_reader :children
34
-
35
- def initialize(parent, name)
36
- @name, @parent = name, parent
37
- @children = {}
38
- parent&.send(:attach, self)
39
- end
40
-
41
- # Unique dot-separated list of names leading from the top-level MetaDb::Database object to
42
- # self. Omits the first path-element if strip is true
43
- def path(strip: false)
44
- r = []
45
- node = self
46
- while node
47
- r << node.name
48
- node = node.parent
49
- end
50
- r.pop if strip
51
- r.reverse.compact.join(".")
52
- end
53
-
54
- # Return child object with the given name
55
- def [](name) @children[name] end
56
-
57
- # Recursively lookup object by dot-separated list of names. If strip is true, the first element
58
- # will be stripped from the path
59
- def dot(path, strip: false)
60
- elems = path.split(".")
61
- elems.shift if strip
62
- elems.inject(self) { |a,e| a[e] } or raise "Can't lookup '#{path}' in #{self.path}"
63
- end
64
-
65
- # Compare two objects by identity
66
- def <=>(r) path <=> r.path end
67
-
68
- # Mostly for debug
69
- def inspect
70
- string = StringIO.new
71
- stdout = $stdout
72
- begin
73
- $stdout = string
74
- dump
75
- ensure
76
- $stdout = stdout
77
- end
78
- string.string
79
- end
80
-
81
- protected
82
- def constrain_children(klass)
83
- children.values.select { |v| v.is_a?(klass) }
84
- end
27
+ # Unique name within context. This is usually what we understand as the
28
+ # name of an object but functions have the full signature as "name"
29
+ attr_reader :name
85
30
 
86
- private
87
- def attach(child)
88
- !@children.key?(child.name) or raise "Duplicate child key: #{child.name.inspect}"
89
- child.instance_variable_set(:@parent, self)
90
- @children[child.name] = child
91
- end
92
-
93
- def detach(child)
94
- @children.key?(child.name) or raise "Non-existing child key: #{child.name.inspect}"
95
- child.instance_variable_set(:@parent, nil)
96
- @children.delete(child.name)
31
+ def initialize(name)
32
+ @name = name
97
33
  end
98
34
  end
99
35
 
100
36
  class Database < DbObject
101
37
  attrs :name, :owner, :schemas
102
38
 
103
- # List of schemas
104
- def schemas()
105
- @schemas ||= children.values
106
- end
107
-
108
39
  # Owner of the database
109
40
  attr_reader :owner
110
41
 
42
+ # Hash of schemas
43
+ attr_reader :schemas
44
+
111
45
  def initialize(name, owner)
112
- super(nil, name)
46
+ super(name)
113
47
  @owner = owner
48
+ @schemas = {}
114
49
  end
115
50
  end
116
51
 
@@ -118,26 +53,31 @@ module MetaDb
118
53
  attrs :database, :name, :owner, :tables, :views, :functions, :procedures
119
54
 
120
55
  # Database of the schema
121
- alias_method :database, :parent
56
+ attr_reader :database
122
57
 
123
58
  # Owner of the schema
124
59
  attr_reader :owner
125
60
 
126
- # List of tables (and views)
127
- def tables() @tables ||= constrain_children(MetaDb::Table) end
61
+ # Hash of tables (and views)
62
+ attr_reader :tables
128
63
 
129
- # List of views
130
- def views() @views ||= constrain_children(MetaDb::View) end
64
+ # Hash of views
65
+ def views() @views ||= @tables.select { |_, table| table.view? } end
131
66
 
132
- # List of functions
133
- def functions() @functions ||= constrain_children(MetaDb::Function) end
67
+ # Hash of functions
68
+ attr_reader :functions
134
69
 
135
- # List of procedures
136
- def procedures() @procedures ||= constrain_children(MetaDb::Procedure) end
70
+ # Hash of procedures
71
+ attr_reader :procedures
137
72
 
138
73
  def initialize(database, name, owner)
139
- super(database, name)
74
+ super(name)
75
+ @database = database
140
76
  @owner = owner
77
+ @tables = {}
78
+ @functions = {}
79
+ @procedures = {}
80
+ @database.schemas[name] = self
141
81
  end
142
82
  end
143
83
 
@@ -146,8 +86,8 @@ module MetaDb
146
86
  :schema, :name, :type, :table?, :view?, :insertable?, :typed?, :columns,
147
87
  :primary_key_columns, :constraints, :referential_constraints, :triggers
148
88
 
149
- # Schema of the table. Redefines #parent
150
- alias_method :schema, :parent
89
+ # Schema of the table
90
+ attr_reader :schema
151
91
 
152
92
  # Type of table. Either 'BASE TABLE' or 'VIEW'
153
93
  attr_reader :type
@@ -164,8 +104,8 @@ module MetaDb
164
104
  # True if the table/view is typed
165
105
  def typed?() @is_typed end
166
106
 
167
- # List of columns. Columns are sorted by ordinal
168
- def columns() @columns ||= constrain_children(MetaDb::Column).sort end
107
+ # Hash of columns
108
+ attr_reader :columns
169
109
 
170
110
  # The primary key column. nil if the table has multiple primary key columns
171
111
  def primary_key_column
@@ -182,39 +122,56 @@ module MetaDb
182
122
  # Note: Assigned by PrimaryKeyConstraint#initialize
183
123
  attr_reader :primary_key_columns
184
124
 
185
- # List of constraints
186
- def constraints()
187
- @constraints ||= constrain_children(MetaDb::Constraint)
188
- end
125
+ # Hash of all constraints
126
+ attr_reader :constraints
189
127
 
190
- # List of referential constraints
191
- def referential_constraints()
192
- @referential_constraints ||= constrain_children(MetaDb::ReferentialConstraint)
193
- end
128
+ # List of primary key constraints (there is only one element)
129
+ attr_reader :primary_key_constraints
194
130
 
195
- # List of triggers
196
- def triggers() @triggers ||= constrain_children(MetaDb::Trigger) end
131
+ # Hash of unique constraints
132
+ attr_reader :unique_constraints
133
+
134
+ # Hash of check constraints
135
+ attr_reader :check_constraints
136
+
137
+ # Hash of referential constraints
138
+ attr_reader :referential_constraints
139
+
140
+ # Hash of triggers
141
+ attr_reader :triggers
197
142
 
198
143
  def initialize(schema, name, type, is_insertable, is_typed)
199
- super(schema, name)
144
+ super(name)
145
+ @schema = schema
200
146
  @type, @is_insertable, @is_typed = type, is_insertable, is_typed
147
+ @columns = {}
201
148
  @primary_key_column = :undefined
202
149
  @primary_key_columns = []
150
+ @constraints = {}
151
+ @primary_key_constraints = []
152
+ @unique_constraints = {}
153
+ @check_constraints = {}
154
+ @referential_constraints = {}
155
+ @triggers = {}
156
+ @schema.tables[name] = self
203
157
  end
204
-
205
158
  end
206
159
 
207
160
  class View < Table
161
+ attrs \
162
+ :schema, :name, :type, :table?, :view?, :insertable?, :typed?, :columns,
163
+ :primary_key_columns, :constraints, :referential_constraints, :triggers
164
+
208
165
  def table?() false end
209
166
  end
210
167
 
211
168
  class Column < DbObject
212
169
  attrs \
213
- :table, :ordinal, :name, :type, :default, :identity?, :generated?,
170
+ :table, :name, :ordinal, :type, :default, :identity?, :generated?,
214
171
  :nullable?, :updatable?, :primary_key?
215
172
 
216
173
  # Table of the column
217
- alias_method :table, :parent
174
+ attr_reader :table
218
175
 
219
176
  # Ordinal number of the column
220
177
  attr_reader :ordinal
@@ -237,17 +194,42 @@ module MetaDb
237
194
  # True if column is updatable
238
195
  def updatable?() @is_updatable end
239
196
 
240
- # True if column is the single primary key of the table. Always false for tables
197
+ # True if column is unique (not that this information is not stored in the
198
+ # Column but in a table constraint)
199
+ def unique?()
200
+ table.unique_constraints.values.any? { |constraint| constraint.columns == [self] }
201
+ end
202
+
203
+ # True if column is the single primary key of the table and false otherwise. Always nil for tables
241
204
  # with multiple primary keys
242
- def primary_key?() table.table? && self == table.primary_key_column end
205
+ def primary_key?()
206
+ return nil if table.primary_key_columns.size != 1
207
+ table.table? && self == table.primary_key_column
208
+ end
243
209
 
244
210
  # True is column is (part of) the primary key of the table
245
211
  def primary_key_column?() table.table? && table.primary_key_columns.include?(self) end
246
212
 
247
- def initialize(table, ordinal, name, type, default, is_identity, is_generated, is_nullable, is_updatable)
248
- super(table, name)
213
+ # True if column is referencing another record
214
+ def reference?()
215
+ @reference ||= !references.empty?
216
+ end
217
+
218
+ # List of referential constraints that involve this column
219
+ def references
220
+ @references ||= begin
221
+ table.referential_constraints.values.select { |constraint|
222
+ constraint.referencing_columns.include?(self)
223
+ }
224
+ end
225
+ end
226
+
227
+ def initialize(table, name, ordinal, type, default, is_identity, is_generated, is_nullable, is_updatable)
228
+ super(name)
229
+ @table = table
249
230
  @type, @ordinal, @default, @is_identity, @is_generated, @is_nullable, @is_updatable =
250
231
  type, ordinal, default, is_identity, is_generated, is_nullable, is_updatable
232
+ @table.columns[name] = self
251
233
  end
252
234
 
253
235
  # Compare columns by table and ordinal
@@ -264,7 +246,13 @@ module MetaDb
264
246
  attrs :table, :name, :columns
265
247
 
266
248
  # Table of the constraint
267
- alias_method :table, :parent
249
+ attr_reader :table
250
+
251
+ # Constraint column. Raise an error if the constraint is multi-column
252
+ def column
253
+ columns.size == 1 or raise "Multicolumn constraint"
254
+ columns.first
255
+ end
268
256
 
269
257
  # List of columns in the constraint. Empty for CheckConstraint objects
270
258
  attr_reader :columns
@@ -273,22 +261,28 @@ module MetaDb
273
261
  def kind() CONSTRAINT_KINDS[self.class] end
274
262
 
275
263
  def initialize(table, name, columns)
276
- super(table, name)
264
+ super(name)
265
+ @table = table
277
266
  @columns = columns
267
+ @table.constraints[name] = self
278
268
  end
279
269
  end
280
270
 
281
- class PrimaryKeyConstraint < Constraint
271
+ class PrimaryKeyConstraint < Constraint
282
272
  attrs :table, :name, :columns
283
273
 
284
274
  def initialize(table, name, columns)
285
275
  super
286
276
  columns.each { |c| c.table.primary_key_columns << c }
277
+ table.primary_key_constraints << self
287
278
  end
288
279
  end
289
280
 
290
281
  class UniqueConstraint < Constraint
291
282
  attrs :table, :name, :columns
283
+ def initialize(table, name, columns)
284
+ table.unique_constraints[name] = self
285
+ end
292
286
  end
293
287
 
294
288
  # Note that #columns is always empty for check constraints
@@ -306,13 +300,14 @@ module MetaDb
306
300
  def initialize(table, name, expression)
307
301
  super(table, name, [])
308
302
  @expression = expression
303
+ @table.check_constraints[name] = self
309
304
  end
310
305
  end
311
306
 
312
307
  class ReferentialConstraint < Constraint
313
308
  attrs :referencing_table, :name, :referencing_columns, :referenced_constraint
314
309
 
315
- # The referencing tabla
310
+ # The referencing table
316
311
  alias_method :referencing_table, :table
317
312
 
318
313
  # The referencing columns. Can't be empty
@@ -325,21 +320,83 @@ module MetaDb
325
320
  def referenced_table() referenced_constraint.table end
326
321
 
327
322
  # The referenced columns
328
- def referenced_columns() referenced_constraints.columns end
323
+ def referenced_columns() referenced_constraint.columns end
329
324
 
330
325
  def initialize(referencing_table, name, referencing_columns, referenced_constraint)
331
326
  super(referencing_table, name, referencing_columns)
332
327
  @referenced_constraint = referenced_constraint
328
+ table.referential_constraints[name] = self
333
329
  end
334
330
  end
335
331
 
336
332
  class Function < DbObject
333
+ attrs :schema, :name, :owner, :security
334
+
335
+ # Schema of the function
336
+ attr_reader :schema
337
+
338
+ # Owner of the function
339
+ attr_reader :owner
340
+
341
+ # Security (:definer or :invoker)
342
+ attr_reader :security
343
+
344
+ # True if security is 'definer'
345
+ def suid?() security == 'definer' end
346
+
347
+ # True if this is a function
348
+ def function?() true end
349
+
350
+ # True if this is a procedure
351
+ def procedure?() !function? end
352
+
353
+ def initialize(schema, name, owner, security)
354
+ super(name)
355
+ @schema = schema
356
+ @owner = owner
357
+ @security = security.to_sym
358
+ if function?
359
+ schema.functions[name] = self
360
+ else
361
+ schema.procedures[name] = self
362
+ end
363
+ end
337
364
  end
338
365
 
339
- class Procedure < DbObject
366
+ class Procedure < Function
367
+ attrs :schema, :name, :owner, :security
368
+
369
+ def function?() false end
340
370
  end
341
371
 
342
372
  class Trigger < DbObject
373
+ attrs :table, :name, :function, :level, :timing, :events
374
+
375
+ # Table of trigger
376
+ attr_reader :table
377
+
378
+ # Trigger function
379
+ attr_reader :function
380
+
381
+ # Trigger level (:stmt or :row)
382
+ attr_reader :level
383
+
384
+ # When trigger is fired (:before, :after, or :instead)
385
+ attr_reader :timing
386
+
387
+ # Array of events (:insert, :update, :delete, or :truncate) causing the trigger to fire
388
+ attr_reader :events
389
+
390
+ def initialize(table, name, function, level, timing, events)
391
+ super(name)
392
+ @table = table
393
+ @name = name
394
+ @function = function
395
+ @level = level.to_sym
396
+ @timing = timing.to_sym
397
+ @events = events.split.map(&:to_sym)
398
+ @table.triggers[name] = self
399
+ end
343
400
  end
344
401
 
345
402
  CONSTRAINT_KINDS = {
data/lib/meta_db/dump.rb CHANGED
@@ -1,14 +1,16 @@
1
1
 
2
2
  require 'indented_io'
3
3
 
4
+ require 'meta_db/db_object'
5
+
4
6
  module MetaDb
5
- class DbObject
6
- def dump
7
+ class DbObject
8
+ def dump(short = false)
7
9
  puts self.class.to_s
8
- dump_attrs
10
+ dump_attrs(short)
9
11
  end
10
12
 
11
- def dump_attrs(*attrs)
13
+ def dump_attrs(short, *attrs)
12
14
  attrs = Array(attrs)
13
15
  attrs = self.class.attrs if attrs.empty?
14
16
  indent {
@@ -16,8 +18,23 @@ module MetaDb
16
18
  value = self.send(attr)
17
19
  case value
18
20
  when Array
19
- puts "#{attr}:"
20
- indent { value.each { |v| v.dump } }
21
+ if value.first.is_a?(Symbol)
22
+ puts "#{attr}: #{value.inspect}"
23
+ else
24
+ if short
25
+ puts "#{attr}: [#{value.map(&:name).join(", ")}]"
26
+ else
27
+ puts "#{attr}: "
28
+ indent { value.each { |v| v.dump } }
29
+ end
30
+ end
31
+ when Hash
32
+ if short
33
+ puts "#{attr}: [#{value.values.map(&:name).join(", ")}]"
34
+ else
35
+ puts "#{attr}: "
36
+ indent { value.values.each { |v| v.dump } }
37
+ end
21
38
  when DbObject
22
39
  puts "#{attr}: #{value.name} (#{value.class})"
23
40
  else
@@ -27,5 +44,11 @@ module MetaDb
27
44
  }
28
45
  end
29
46
  end
47
+
48
+ class Constraint < DbObject
49
+ def dump(short = true)
50
+ super(short)
51
+ end
52
+ end
30
53
  end
31
54
 
@@ -1,3 +1,3 @@
1
1
  module MetaDb
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meta_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-05 00:00:00.000000000 Z
11
+ date: 2021-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  requirements: []
125
- rubygems_version: 3.0.8
125
+ rubygems_version: 3.1.2
126
126
  signing_key:
127
127
  specification_version: 4
128
128
  summary: Meta model of database