gadget 0.5.4 → 0.6.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/lib/gadget.rb +221 -22
- data/lib/gadget/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2bf83e177e0c22d39fbdd338c5df052bcf7ea6e
|
4
|
+
data.tar.gz: 092f282fbacd8cb1858bbf96fff28e4cd74033e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4dd82afa75702c5c8953d4dceb7086cde36b787365d61e0a6441f6edd25e79fc3322ac92654a60972279ff9988b3f470618f95bf78883ecccf66296cfd7c564
|
7
|
+
data.tar.gz: ee495b40222d0a17d18864d526ea84af4e2721204b016fe6639e8870093423dc49c974872ecdda070162cad9271bb536d475a4af3ad8d9c8877dab293944fc25
|
data/lib/gadget.rb
CHANGED
@@ -5,6 +5,24 @@ require 'tsort'
|
|
5
5
|
|
6
6
|
require 'gadget/version'
|
7
7
|
|
8
|
+
## monkey patch Hash to gain .extractable_options?
|
9
|
+
class Hash
|
10
|
+
def extractable_options?
|
11
|
+
instance_of?(Hash)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
## monkey patch Array to gain .extract_options!
|
16
|
+
class Array
|
17
|
+
def extract_options!
|
18
|
+
if last.is_a?(Hash) && last.extractable_options?
|
19
|
+
pop
|
20
|
+
else
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
8
26
|
module Gadget
|
9
27
|
|
10
28
|
class TsortableHash < Hash
|
@@ -15,7 +33,21 @@ module Gadget
|
|
15
33
|
end
|
16
34
|
end
|
17
35
|
|
18
|
-
|
36
|
+
# Return a collection enumerating the tables in a database.
|
37
|
+
#
|
38
|
+
# ==== Usage
|
39
|
+
# tables = Gadget.tables(conn)
|
40
|
+
#
|
41
|
+
# ==== Parameters
|
42
|
+
# * +conn+ - a +PG::Connection+ to the database
|
43
|
+
#
|
44
|
+
# ==== Returns
|
45
|
+
# * a Hash:
|
46
|
+
# table name:: a Hash:
|
47
|
+
# +:oid+:: the table's OID
|
48
|
+
def self.tables(conn, *args)
|
49
|
+
_ = args.extract_options!
|
50
|
+
|
19
51
|
sql = <<-END_OF_SQL
|
20
52
|
SELECT c.oid, t.tablename
|
21
53
|
FROM pg_catalog.pg_tables t
|
@@ -33,15 +65,34 @@ WHERE t.schemaname='public'
|
|
33
65
|
tuples
|
34
66
|
end
|
35
67
|
|
36
|
-
|
68
|
+
# Return a collection enumerating the columns in a table or database.
|
69
|
+
#
|
70
|
+
# ==== Usage
|
71
|
+
# columns = Gadget.columns(conn)
|
72
|
+
# columns_in_table = Gadget.columns(conn, 'tablename')
|
73
|
+
#
|
74
|
+
# ==== Parameters
|
75
|
+
# * +conn+ - a +PG::Connection+ to the database
|
76
|
+
# * +tablename - if given, return columns only for the named table
|
77
|
+
#
|
78
|
+
# ==== Returns
|
79
|
+
# * a Hash:
|
80
|
+
# table name:: a Hash:
|
81
|
+
# +:columns+:: an Array of column names
|
82
|
+
def self.columns(conn, *args)
|
83
|
+
options = args.extract_options!
|
84
|
+
tablename = args.shift
|
85
|
+
|
37
86
|
sql = <<-END_OF_SQL
|
38
87
|
SELECT t.tablename, a.attname
|
39
88
|
FROM pg_catalog.pg_attribute a
|
40
89
|
INNER JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
41
90
|
INNER JOIN pg_catalog.pg_tables t ON c.relname = t.tablename
|
42
91
|
WHERE a.attnum >= 0
|
43
|
-
AND a.attisdropped IS FALSE
|
44
92
|
END_OF_SQL
|
93
|
+
unless options[:include_dropped]
|
94
|
+
sql += " AND a.attisdropped IS FALSE"
|
95
|
+
end
|
45
96
|
if tablename.nil?
|
46
97
|
rs = conn.exec(sql)
|
47
98
|
else
|
@@ -57,7 +108,28 @@ AND a.attisdropped IS FALSE
|
|
57
108
|
tuples
|
58
109
|
end
|
59
110
|
|
60
|
-
|
111
|
+
# Return a collection enumerating the foreign keys in a table or database.
|
112
|
+
#
|
113
|
+
# ==== Usage
|
114
|
+
# fks = Gadget.foreign_keys(conn)
|
115
|
+
# fks_in_table = Gadget.foreign_keys(conn, 'tablename')
|
116
|
+
#
|
117
|
+
# ==== Parameters
|
118
|
+
# * +conn+ - a +PG::Connection+ to the database
|
119
|
+
# * +tablename - if given, return foreign keys only for the named table
|
120
|
+
#
|
121
|
+
# ==== Returns
|
122
|
+
# * a Hash:
|
123
|
+
# table name:: a Hash:
|
124
|
+
# +:refs+:: an Array of Hashes:
|
125
|
+
# +:name+:: the name of the foreign key
|
126
|
+
# +:cols+:: the columns in _this table_ that make up the foreign key
|
127
|
+
# +:ref_name+:: the name of the table referred to by this foreign key
|
128
|
+
# +:ref_cols+:: the columns in _the other table_ that are referred to by this foreign key
|
129
|
+
def self.foreign_keys(conn, *args)
|
130
|
+
_ = args.extract_options!
|
131
|
+
tablename = args.shift
|
132
|
+
|
61
133
|
sql = <<-END_OF_SQL
|
62
134
|
SELECT pg_constraint.conname AS name,
|
63
135
|
t1.tablename AS tablename, pg_constraint.conkey AS cols,
|
@@ -80,13 +152,13 @@ AND pg_constraint.contype = 'f'
|
|
80
152
|
tuples = rs.reduce({}) do | h, row |
|
81
153
|
name = row['tablename']
|
82
154
|
h[name] ||= { :refs => [] }
|
83
|
-
col_names = self.columns(conn, name)[name][:columns]
|
84
|
-
refcol_names = self.columns(conn, row['refname'])[row['refname']][:columns]
|
155
|
+
col_names = self.columns(conn, name, :include_dropped => true)[name][:columns]
|
156
|
+
refcol_names = self.columns(conn, row['refname'], :include_dropped => true)[row['refname']][:columns]
|
85
157
|
new_ref = {
|
86
158
|
:name => row['name'],
|
87
159
|
:cols => row['cols'].sub(/\A\{|\}\z/, '').split(',').map { | idx | col_names[idx.to_i - 1] },
|
88
|
-
:
|
89
|
-
:
|
160
|
+
:ref_name => row['refname'],
|
161
|
+
:ref_cols => row['refcols'].sub(/\A\{|\}\z/, '').split(',').map { | idx | refcol_names[idx.to_i - 1] },
|
90
162
|
}
|
91
163
|
h[name][:refs] << new_ref
|
92
164
|
h
|
@@ -95,7 +167,32 @@ AND pg_constraint.contype = 'f'
|
|
95
167
|
tuples
|
96
168
|
end
|
97
169
|
|
98
|
-
|
170
|
+
# Return a collection enumerating the constraints in a table or database.
|
171
|
+
#
|
172
|
+
# ==== Usage
|
173
|
+
# constraints = Gadget.constraints(conn)
|
174
|
+
# constraints_in_table = Gadget.constraints(conn, 'tablename')
|
175
|
+
#
|
176
|
+
# ==== Parameters
|
177
|
+
# * +conn+ - a +PG::Connection+ to the database
|
178
|
+
# * +tablename - if given, return constraints only for the named table
|
179
|
+
#
|
180
|
+
# ==== Returns
|
181
|
+
# * a Hash:
|
182
|
+
# table name:: a Hash:
|
183
|
+
# +:constraints+:: an Array of Hashes:
|
184
|
+
# +:name+:: the name of the constraint
|
185
|
+
# +:kind+:: the kind of the constraint:
|
186
|
+
# * +check+
|
187
|
+
# * +exclusion+
|
188
|
+
# * +foreign key+
|
189
|
+
# * +primary key+
|
190
|
+
# * +trigger+
|
191
|
+
# * +unique+
|
192
|
+
def self.constraints(conn, *args)
|
193
|
+
_ = args.extract_options!
|
194
|
+
tablename = args.shift
|
195
|
+
|
99
196
|
sql = <<-END_OF_SQL
|
100
197
|
SELECT pg_constraint.conname AS name,
|
101
198
|
pg_constraint.contype AS constrainttype,
|
@@ -140,24 +237,61 @@ WHERE t.schemaname = 'public'
|
|
140
237
|
tuples
|
141
238
|
end
|
142
239
|
|
143
|
-
|
240
|
+
# Return a collection enumerating the dependencies between tables in a database.
|
241
|
+
# A table +a+ is considered to be dependent on a table +b+ if +a+ has a foreign key constraint
|
242
|
+
# that refers to +b+.
|
243
|
+
#
|
244
|
+
# ==== Usage
|
245
|
+
# dependencies = Gadget.dependencies(conn)
|
246
|
+
#
|
247
|
+
# ==== Parameters
|
248
|
+
# * +conn+ - a +PG::Connection+ to the database
|
249
|
+
#
|
250
|
+
# ==== Returns
|
251
|
+
# * a Hash:
|
252
|
+
# table name:: an Array of table names
|
253
|
+
def self.dependencies(conn, *args)
|
254
|
+
_ = args.extract_options!
|
255
|
+
|
144
256
|
tables = self.tables(conn)
|
145
257
|
foreign_keys = self.foreign_keys(conn)
|
146
|
-
|
258
|
+
tables.reduce({}) do | h, (tablename, _) |
|
147
259
|
h[tablename] = []
|
148
260
|
refs = foreign_keys[tablename]
|
149
261
|
unless refs.nil?
|
150
|
-
refs[:refs].each { | ref | h[tablename] << ref[:
|
262
|
+
refs[:refs].each { | ref | h[tablename] << ref[:ref_name] }
|
151
263
|
end
|
152
264
|
h
|
153
265
|
end
|
154
266
|
end
|
155
267
|
|
156
|
-
|
268
|
+
# Return a collection enumerating the tables in a database in dependency order.
|
269
|
+
# If a table +a+ is dependent on a table +b+, then +a+ will appear _after_ +b+ in the collection.
|
270
|
+
#
|
271
|
+
# ==== Usage
|
272
|
+
# dependencies = Gadget.dependencies(conn)
|
273
|
+
#
|
274
|
+
# ==== Parameters
|
275
|
+
# * +conn+ - a +PG::Connection+ to the database
|
276
|
+
#
|
277
|
+
# ==== Returns
|
278
|
+
# * a Hash:
|
279
|
+
# table name:: an Array of table names
|
280
|
+
def self.tables_in_dependency_order(conn, *args)
|
281
|
+
_ = args.extract_options!
|
157
282
|
self.dependencies(conn).reduce(TsortableHash.new) { | h, (k, v) | h[k] = v; h }.tsort
|
158
283
|
end
|
159
284
|
|
160
|
-
|
285
|
+
# Print dot (Graphviz data format) describing the dependency graph for a database to stdout.
|
286
|
+
#
|
287
|
+
# ==== Usage
|
288
|
+
# Gadget.dependency_graph(conn)
|
289
|
+
#
|
290
|
+
# ==== Parameters
|
291
|
+
# * +conn+ - a +PG::Connection+ to the database
|
292
|
+
def self.dependency_graph(conn, *args)
|
293
|
+
_ = args.extract_options!
|
294
|
+
|
161
295
|
puts "digraph dependencies {"
|
162
296
|
self.dependencies(conn).each do | tablename, deps |
|
163
297
|
if deps.empty?
|
@@ -169,7 +303,22 @@ WHERE t.schemaname = 'public'
|
|
169
303
|
puts "}"
|
170
304
|
end
|
171
305
|
|
172
|
-
|
306
|
+
# Return a collection enumerating the functions in a database.
|
307
|
+
#
|
308
|
+
# ==== Usage
|
309
|
+
# functions = Gadget.functions(conn)
|
310
|
+
#
|
311
|
+
# ==== Parameters
|
312
|
+
# * +conn+ - a +PG::Connection+ to the database
|
313
|
+
#
|
314
|
+
# ==== Returns
|
315
|
+
# * a Hash:
|
316
|
+
# function name:: a Hash:
|
317
|
+
# +:oid+:: the function's OID
|
318
|
+
# +:arg_types+:: the type IDs for the arguments to the function
|
319
|
+
def self.functions(conn, *args)
|
320
|
+
_ = args.extract_options!
|
321
|
+
|
173
322
|
rs = conn.exec(<<-END_OF_SQL)
|
174
323
|
SELECT p.oid, p.proname, p.proargtypes
|
175
324
|
FROM pg_catalog.pg_proc p
|
@@ -188,7 +337,21 @@ WHERE n.nspname = 'public'
|
|
188
337
|
tuples
|
189
338
|
end
|
190
339
|
|
191
|
-
|
340
|
+
# Return a collection enumerating the sequences in a database.
|
341
|
+
#
|
342
|
+
# ==== Usage
|
343
|
+
# sequences = Gadget.sequences(conn)
|
344
|
+
#
|
345
|
+
# ==== Parameters
|
346
|
+
# * +conn+ - a +PG::Connection+ to the database
|
347
|
+
#
|
348
|
+
# ==== Returns
|
349
|
+
# * a Hash:
|
350
|
+
# sequence name:: a Hash:
|
351
|
+
# +:oid+:: the sequence's OID
|
352
|
+
def self.sequences(conn, *args)
|
353
|
+
_ = args.extract_options!
|
354
|
+
|
192
355
|
sql = <<-END_OF_SQL
|
193
356
|
SELECT c.oid, c.relname
|
194
357
|
FROM pg_catalog.pg_class c
|
@@ -207,8 +370,25 @@ AND n.nspname = 'public'
|
|
207
370
|
tuples
|
208
371
|
end
|
209
372
|
|
210
|
-
|
211
|
-
|
373
|
+
# Return a collection enumerating the triggers in a database.
|
374
|
+
#
|
375
|
+
# ==== Usage
|
376
|
+
# triggers = Gadget.triggers(conn)
|
377
|
+
#
|
378
|
+
# ==== Parameters
|
379
|
+
# * +conn+ - a +PG::Connection+ to the database
|
380
|
+
#
|
381
|
+
# ==== Returns
|
382
|
+
# * a Hash:
|
383
|
+
# trigger name:: a Hash:
|
384
|
+
# +:oid+:: the trigger's OID
|
385
|
+
# +:table_name+:: the table on which the trigger is defined
|
386
|
+
# +:function_name+:: the name of the trigger's function
|
387
|
+
def self.triggers(conn, *args)
|
388
|
+
_ = args.extract_options!
|
389
|
+
tablename = args.shift
|
390
|
+
|
391
|
+
sql = <<-END_OF_SQL
|
212
392
|
SELECT tg.oid, tg.tgname, t.tablename, p.proname
|
213
393
|
FROM pg_catalog.pg_trigger tg
|
214
394
|
INNER JOIN pg_catalog.pg_class c ON tg.tgrelid = c.oid
|
@@ -216,12 +396,17 @@ INNER JOIN pg_catalog.pg_tables t ON c.relname = t.tablename
|
|
216
396
|
INNER JOIN pg_catalog.pg_proc p ON tg.tgfoid = p.oid
|
217
397
|
WHERE tg.tgconstrrelid = 0
|
218
398
|
END_OF_SQL
|
219
|
-
|
399
|
+
if tablename.nil?
|
400
|
+
rs = conn.exec(sql)
|
401
|
+
else
|
402
|
+
sql += " AND t.tablename = $1"
|
403
|
+
rs = conn.exec_params(sql, [ tablename ])
|
404
|
+
end
|
220
405
|
tuples = rs.reduce({}) do | h, row |
|
221
406
|
h[row['tgname']] = {
|
222
407
|
:oid => row['oid'].to_i,
|
223
|
-
:
|
224
|
-
:
|
408
|
+
:table_name => row['tablename'],
|
409
|
+
:function_name => row['proname'],
|
225
410
|
}
|
226
411
|
h
|
227
412
|
end
|
@@ -229,7 +414,21 @@ WHERE tg.tgconstrrelid = 0
|
|
229
414
|
tuples
|
230
415
|
end
|
231
416
|
|
232
|
-
|
417
|
+
# Return a collection enumerating the types in a database.
|
418
|
+
#
|
419
|
+
# ==== Usage
|
420
|
+
# types = Gadget.types(conn)
|
421
|
+
#
|
422
|
+
# ==== Parameters
|
423
|
+
# * +conn+ - a +PG::Connection+ to the database
|
424
|
+
#
|
425
|
+
# ==== Returns
|
426
|
+
# * a Hash:
|
427
|
+
# type name:: a Hash:
|
428
|
+
# +:oid+:: the type's OID
|
429
|
+
def self.types(conn, *args)
|
430
|
+
_ = args.extract_options!
|
431
|
+
|
233
432
|
rs = conn.exec(<<-END_OF_SQL)
|
234
433
|
SELECT t.oid, t.typname
|
235
434
|
FROM pg_catalog.pg_type t
|
data/lib/gadget/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gadget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Craig S. Cottingham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|