meta_db 0.1.1 → 0.2.0

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