gadget 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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