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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gadget.rb +221 -22
  3. data/lib/gadget/version.rb +1 -1
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18e3c423cd66791b21ebc7240290b6698656370f
4
- data.tar.gz: 11c63cb1a1c7cdef27a822cc5c535d1eb6e5c759
3
+ metadata.gz: b2bf83e177e0c22d39fbdd338c5df052bcf7ea6e
4
+ data.tar.gz: 092f282fbacd8cb1858bbf96fff28e4cd74033e6
5
5
  SHA512:
6
- metadata.gz: d2288ae89e92612f0c95a7167a0525b135768183558be94b12ca9dd4149181eb1230665b4b5c403ebf6ef955633e8c04fdbc3d0aa45b5ae3082b55389b63fc5d
7
- data.tar.gz: f8b3889fb07d5c4a7e50cddc663b8f40391044990437a3a9ddfecf3e77e0ec8b31a74b719e6f9564134b49ad0091d1e622f05e4d819b45edc307f247607688c7
6
+ metadata.gz: e4dd82afa75702c5c8953d4dceb7086cde36b787365d61e0a6441f6edd25e79fc3322ac92654a60972279ff9988b3f470618f95bf78883ecccf66296cfd7c564
7
+ data.tar.gz: ee495b40222d0a17d18864d526ea84af4e2721204b016fe6639e8870093423dc49c974872ecdda070162cad9271bb536d475a4af3ad8d9c8877dab293944fc25
@@ -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
- def self.tables(conn)
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
- def self.columns(conn, tablename = nil)
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
- def self.foreign_keys(conn, tablename = nil)
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
- :refname => row['refname'],
89
- :refcols => row['refcols'].sub(/\A\{|\}\z/, '').split(',').map { | idx | refcol_names[idx.to_i - 1] },
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
- def self.constraints(conn, tablename = nil)
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
- def self.dependencies(conn)
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
- dependencies = tables.reduce({}) do | h, (tablename, _) |
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[:refname] }
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
- def self.tables_in_dependency_order(conn)
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
- def self.dependency_graph(conn)
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
- def self.functions(conn)
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
- def self.sequences(conn)
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
- def self.triggers(conn)
211
- rs = conn.exec(<<-END_OF_SQL)
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
- :tablename => row['tablename'],
224
- :functionname => row['proname'],
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
- def self.types(conn)
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
@@ -1,3 +1,3 @@
1
1
  module Gadget
2
- VERSION = "0.5.4"
2
+ VERSION = "0.6.0"
3
3
  end
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.5.4
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-25 00:00:00.000000000 Z
11
+ date: 2014-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg