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 +4 -4
- data/.ruby-version +1 -1
- data/lib/meta_db.rb +102 -34
- data/lib/meta_db/connection.rb +17 -0
- data/lib/meta_db/db_object.rb +177 -120
- data/lib/meta_db/dump.rb +29 -6
- data/lib/meta_db/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a51836f5ebbe95799043394f44b151d2e722eecaae0f5be6a993c3a35eb353cc
|
4
|
+
data.tar.gz: 0f81dcdfdec3f3033fa7726fae89f734da23e783ce473b737b2e2b3150c67c1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b916d8bb0995189452ba838199bd8c887b56aba3d5d63399fcd6fb4a270ed8fbe506127edc98a1c7bf82c331d57e5ded69aa0c69eaaf8e93ab88838b5bd21f47
|
7
|
+
data.tar.gz: ccafad6e13a9d0eab5d130935f94d24a294d7cd85462fd871e3ef02fb901a5d60ecbb71feb852e9d82ae99237c79687ac47766525341ae48d584d8f00cb18d37
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
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.
|
54
|
+
db = MetaDb::Database.new(conn.database, r.value)
|
55
55
|
|
56
56
|
# Build schemas
|
57
57
|
conn.select(%(
|
58
|
-
select schema_name::
|
59
|
-
schema_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::
|
70
|
-
table_name::
|
71
|
-
table_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.
|
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::
|
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
|
-
|
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 = '
|
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.
|
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.
|
103
|
-
c.
|
104
|
-
c.
|
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::
|
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
|
-
|
116
|
-
|
117
|
-
|
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.
|
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::
|
143
|
-
rc.constraint_name::
|
144
|
-
cu_refing.table_schema
|
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::
|
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
|
151
|
-
|
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
|
154
|
-
|
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
|
157
|
-
|
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.
|
170
|
-
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] =
|
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.
|
247
|
+
column_names.map { |n| table.columns[n] }
|
180
248
|
end
|
181
249
|
end
|
182
250
|
|
data/lib/meta_db/connection.rb
CHANGED
@@ -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
|
data/lib/meta_db/db_object.rb
CHANGED
@@ -3,9 +3,8 @@ require 'meta_db/dump.rb'
|
|
3
3
|
|
4
4
|
module MetaDb
|
5
5
|
class DbObject
|
6
|
-
#
|
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
|
-
|
23
|
+
(@attrs ||= []).each { |a| h << row[a] if row.key?(a) }
|
21
24
|
self.new(*h)
|
22
25
|
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
87
|
-
|
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(
|
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
|
-
|
56
|
+
attr_reader :database
|
122
57
|
|
123
58
|
# Owner of the schema
|
124
59
|
attr_reader :owner
|
125
60
|
|
126
|
-
#
|
127
|
-
|
61
|
+
# Hash of tables (and views)
|
62
|
+
attr_reader :tables
|
128
63
|
|
129
|
-
#
|
130
|
-
def views() @views ||=
|
64
|
+
# Hash of views
|
65
|
+
def views() @views ||= @tables.select { |_, table| table.view? } end
|
131
66
|
|
132
|
-
#
|
133
|
-
|
67
|
+
# Hash of functions
|
68
|
+
attr_reader :functions
|
134
69
|
|
135
|
-
#
|
136
|
-
|
70
|
+
# Hash of procedures
|
71
|
+
attr_reader :procedures
|
137
72
|
|
138
73
|
def initialize(database, name, owner)
|
139
|
-
super(
|
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
|
150
|
-
|
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
|
-
#
|
168
|
-
|
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
|
-
#
|
186
|
-
|
187
|
-
@constraints ||= constrain_children(MetaDb::Constraint)
|
188
|
-
end
|
125
|
+
# Hash of all constraints
|
126
|
+
attr_reader :constraints
|
189
127
|
|
190
|
-
# List of
|
191
|
-
|
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
|
-
#
|
196
|
-
|
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(
|
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, :
|
170
|
+
:table, :name, :ordinal, :type, :default, :identity?, :generated?,
|
214
171
|
:nullable?, :updatable?, :primary_key?
|
215
172
|
|
216
173
|
# Table of the column
|
217
|
-
|
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
|
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?()
|
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
|
-
|
248
|
-
|
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
|
-
|
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(
|
264
|
+
super(name)
|
265
|
+
@table = table
|
277
266
|
@columns = columns
|
267
|
+
@table.constraints[name] = self
|
278
268
|
end
|
279
269
|
end
|
280
270
|
|
281
|
-
|
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
|
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()
|
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 <
|
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
|
-
|
20
|
-
|
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
|
|
data/lib/meta_db/version.rb
CHANGED
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.
|
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:
|
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.
|
125
|
+
rubygems_version: 3.1.2
|
126
126
|
signing_key:
|
127
127
|
specification_version: 4
|
128
128
|
summary: Meta model of database
|