bricolage 5.8.7

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +4 -0
  3. data/bin/bricolage +6 -0
  4. data/bin/bricolage-jobnet +6 -0
  5. data/jobclass/create.rb +21 -0
  6. data/jobclass/exec.rb +17 -0
  7. data/jobclass/insert-delta.rb +31 -0
  8. data/jobclass/insert.rb +33 -0
  9. data/jobclass/load.rb +39 -0
  10. data/jobclass/my-export.rb +40 -0
  11. data/jobclass/my-migrate.rb +103 -0
  12. data/jobclass/noop.rb +13 -0
  13. data/jobclass/rebuild-drop.rb +37 -0
  14. data/jobclass/rebuild-rename.rb +49 -0
  15. data/jobclass/s3-put.rb +19 -0
  16. data/jobclass/sql.rb +29 -0
  17. data/jobclass/td-delete.rb +20 -0
  18. data/jobclass/td-export.rb +30 -0
  19. data/jobclass/unload.rb +30 -0
  20. data/jobclass/wait-file.rb +48 -0
  21. data/lib/bricolage/application.rb +260 -0
  22. data/lib/bricolage/commandutils.rb +52 -0
  23. data/lib/bricolage/configloader.rb +126 -0
  24. data/lib/bricolage/context.rb +108 -0
  25. data/lib/bricolage/datasource.rb +144 -0
  26. data/lib/bricolage/eventhandlers.rb +47 -0
  27. data/lib/bricolage/exception.rb +47 -0
  28. data/lib/bricolage/filedatasource.rb +42 -0
  29. data/lib/bricolage/filesystem.rb +165 -0
  30. data/lib/bricolage/genericdatasource.rb +37 -0
  31. data/lib/bricolage/job.rb +212 -0
  32. data/lib/bricolage/jobclass.rb +98 -0
  33. data/lib/bricolage/jobfile.rb +100 -0
  34. data/lib/bricolage/jobflow.rb +389 -0
  35. data/lib/bricolage/jobnetrunner.rb +264 -0
  36. data/lib/bricolage/jobresult.rb +74 -0
  37. data/lib/bricolage/logger.rb +52 -0
  38. data/lib/bricolage/mysqldatasource.rb +223 -0
  39. data/lib/bricolage/parameters.rb +653 -0
  40. data/lib/bricolage/postgresconnection.rb +78 -0
  41. data/lib/bricolage/psqldatasource.rb +449 -0
  42. data/lib/bricolage/resource.rb +68 -0
  43. data/lib/bricolage/rubyjobclass.rb +42 -0
  44. data/lib/bricolage/s3datasource.rb +144 -0
  45. data/lib/bricolage/script.rb +120 -0
  46. data/lib/bricolage/sqlstatement.rb +351 -0
  47. data/lib/bricolage/taskqueue.rb +156 -0
  48. data/lib/bricolage/tddatasource.rb +116 -0
  49. data/lib/bricolage/variables.rb +208 -0
  50. data/lib/bricolage/version.rb +4 -0
  51. data/lib/bricolage.rb +8 -0
  52. data/libexec/sqldump +9 -0
  53. data/libexec/sqldump.Darwin +0 -0
  54. data/libexec/sqldump.Linux +0 -0
  55. data/test/all.rb +3 -0
  56. data/test/home/config/development/database.yml +57 -0
  57. data/test/home/config/development/password.yml +2 -0
  58. data/test/home/subsys/separated.job +1 -0
  59. data/test/home/subsys/separated.sql +1 -0
  60. data/test/home/subsys/unified.jobnet +1 -0
  61. data/test/home/subsys/unified.sql.job +5 -0
  62. data/test/test_filesystem.rb +19 -0
  63. data/test/test_parameters.rb +401 -0
  64. data/test/test_variables.rb +114 -0
  65. metadata +192 -0
@@ -0,0 +1,653 @@
1
+ require 'bricolage/variables'
2
+ require 'bricolage/sqlstatement'
3
+ require 'bricolage/exception'
4
+ require 'date'
5
+ require 'pathname'
6
+
7
+ module Bricolage
8
+
9
+ class Parameters
10
+
11
+ # Job parameter declarations defined by job class.
12
+ class Declarations
13
+ def initialize
14
+ @decls = {} # {name => Param}
15
+ end
16
+
17
+ def inspect
18
+ "\#<#{self.class} #{@decls.inspect}>"
19
+ end
20
+
21
+ def add(param)
22
+ @decls[param.name] = param
23
+ end
24
+
25
+ def [](key)
26
+ @decls[key]
27
+ end
28
+
29
+ def keys
30
+ @decls.keys
31
+ end
32
+
33
+ def each(&block)
34
+ @decls.each_value(&block)
35
+ end
36
+
37
+ def parse_direct_values(values)
38
+ DirectValueHandler.new(self).parse(values)
39
+ end
40
+
41
+ def parsing_options
42
+ h = CommandLineOptionHandler.new(self)
43
+ yield h
44
+ h.values
45
+ end
46
+
47
+ def union_intermediate_values(*ival_list)
48
+ IntermediateValues.union(self, *ival_list)
49
+ end
50
+ end
51
+
52
+ # Handles *.job file values.
53
+ # Declarations + values -> IntermediateValues
54
+ class DirectValueHandler
55
+ def initialize(decls)
56
+ @decls = decls # Declarations
57
+ end
58
+
59
+ # values :: {String => a}
60
+ def parse(values)
61
+ parsed_values = {}
62
+ vars = Variables.new
63
+ values.each do |name, value|
64
+ if decl = @decls[name]
65
+ val = decl.parse_value(value)
66
+ # nil is equal to "no option given" semantically
67
+ parsed_values[name] = val unless val.nil?
68
+ else
69
+ vars.add Variable.new(name, value)
70
+ end
71
+ end
72
+ IntermediateValues.new(@decls, parsed_values, vars)
73
+ end
74
+ end
75
+
76
+ # Handles param values given by command line options (e.g. --dest-table=t).
77
+ # Declarations + option_args -> IntermediateValues
78
+ class CommandLineOptionHandler
79
+ def initialize(decls)
80
+ @decls = decls # Declarations
81
+ @values = {} # {name => a}
82
+ @vars = Variables.new
83
+ end
84
+
85
+ def define_options(parser)
86
+ @decls.each do |decl|
87
+ desc = (decl.optional? ? '[optional] ' : '') + decl.description
88
+ arg_spec = decl.have_arg? ? "=#{decl.arg_spec}" : ''
89
+ parser.on("--#{decl.option_name}#{arg_spec}", desc) {|arg|
90
+ @values[decl.name] = decl.parse_option_value(arg, @values[decl.name])
91
+ }
92
+ end
93
+ parser.on('-v', '--variable=NAME=VALUE', 'Set variable.') {|name_value|
94
+ name, value = name_value.split('=', 2)
95
+ @vars.add Variable.new(name, value)
96
+ }
97
+ end
98
+
99
+ def values
100
+ IntermediateValues.new(@decls, @values, @vars)
101
+ end
102
+ end
103
+
104
+ # Unified intermediate param values representation.
105
+ # This class is used for both of *.job file and options.
106
+ class IntermediateValues
107
+ def IntermediateValues.union(decls, *vals_list)
108
+ result = empty(decls)
109
+ vals_list.each do |vals|
110
+ result.update vals
111
+ end
112
+ result
113
+ end
114
+
115
+ def IntermediateValues.empty(decls)
116
+ new(decls, {}, Variables.new)
117
+ end
118
+
119
+ def initialize(decls, values, vars)
120
+ @decls = decls # Declarations
121
+ @values = values # {name => a}
122
+ @variables = vars # Variables
123
+ end
124
+
125
+ attr_reader :decls
126
+ attr_reader :values
127
+ attr_reader :variables
128
+
129
+ def [](name)
130
+ @values[name]
131
+ end
132
+
133
+ def []=(name, value)
134
+ @values[name] = value
135
+ end
136
+
137
+ def keys
138
+ @values.keys
139
+ end
140
+
141
+ def update(other)
142
+ unless @decls == other.decls
143
+ raise "[BUG] merging IntermediateValues with different paramdecl: #{self.inspect} - #{other.inspect}"
144
+ end
145
+ @values.update other.values
146
+ @variables.update other.variables
147
+ end
148
+
149
+ def resolve(ctx, vars)
150
+ materialized = materialize(ctx, vars)
151
+ resolved = resolved_variables(materialized, vars)
152
+ Parameters.new(@decls, materialized, resolved, ctx)
153
+ end
154
+
155
+ private
156
+
157
+ def materialize(ctx, vars)
158
+ h = {}
159
+ @decls.each do |decl|
160
+ value = @values[decl.name]
161
+ # value==nil means "no parameter given" or "no option given".
162
+ # Note that false is a *valid* value, "falthy" check is not sufficient here.
163
+ if value.nil?
164
+ raise ParameterError, "parameter not given: #{decl.name}" if decl.required?
165
+ h[decl.name] = decl.default_value(ctx, vars)
166
+ else
167
+ h[decl.name] = decl.materialize(value, ctx, vars)
168
+ end
169
+ end
170
+ h
171
+ end
172
+
173
+ def resolved_variables(materialized, base_vars)
174
+ Variables.define {|vars|
175
+ materialized.each do |name, value|
176
+ decl = @decls[name]
177
+ next unless decl.publish?
178
+ next if value.nil?
179
+ decl.variables(value).each do |var|
180
+ vars.add var
181
+ end
182
+ end
183
+ vars.update @variables
184
+ }.resolve_with(base_vars)
185
+ end
186
+ end
187
+
188
+ # class Parameters
189
+ # Materialized, fixed job parameter values.
190
+
191
+ def initialize(decls, values, vars, ctx)
192
+ @decls = decls # Declarations
193
+ @values = values # {name => a}
194
+ @variables = vars # ResolvedVariables
195
+ @context = ctx # Context
196
+ end
197
+
198
+ def inspect
199
+ "\#<#{self.class} #{@values.inspect}>"
200
+ end
201
+
202
+ def [](key)
203
+ raise ParameterError, "no such parameter: #{key}" unless @values.key?(key)
204
+ @values[key]
205
+ end
206
+
207
+ alias get []
208
+
209
+ def keys
210
+ @values.keys
211
+ end
212
+
213
+ attr_reader :variables
214
+
215
+ # FIXME: remove
216
+ def generic_ds
217
+ @context.get_data_source('generic', 'generic')
218
+ end
219
+
220
+ # FIXME: remove
221
+ def file_ds
222
+ @context.get_data_source('file', 'file')
223
+ end
224
+
225
+ end # class Parameters
226
+
227
+ class Param
228
+ def initialize(name, arg_spec, description, optional: false, publish: false)
229
+ @name = name
230
+ @arg_spec = arg_spec
231
+ @description = description
232
+ @optional = optional
233
+ @publish = publish
234
+ end
235
+
236
+ attr_reader :name
237
+ attr_reader :description
238
+
239
+ def option_name
240
+ name
241
+ end
242
+
243
+ attr_reader :arg_spec
244
+
245
+ def have_arg?
246
+ !!@arg_spec
247
+ end
248
+
249
+ def optional?
250
+ @optional
251
+ end
252
+
253
+ def required?
254
+ not optional?
255
+ end
256
+
257
+ # "published" parameter defines SQL variable.
258
+ def publish?
259
+ @publish
260
+ end
261
+
262
+ def inspect
263
+ attrs = [
264
+ (@optional ? 'optional' : 'required'),
265
+ (@publish ? 'publish' : nil)
266
+ ].compact.join(',')
267
+ "\#<#{self.class} #{name} #{attrs}>"
268
+ end
269
+
270
+ #
271
+ # Value Handling
272
+ #
273
+
274
+ def parse_option_value(arg, acc)
275
+ return nil if arg.nil?
276
+ arg
277
+ end
278
+
279
+ def parse_value(value)
280
+ value
281
+ end
282
+
283
+ # abstract def default_value(ctx, vars)
284
+ # abstract def materialize(value, ctx, vars)
285
+ # abstract def variables(value)
286
+
287
+ private
288
+
289
+ def expand(str, vars)
290
+ Variable.expand_string(str.to_s) {|var|
291
+ vars[var] or raise ParameterError, "undefined variable in parameter #{name}: #{var}"
292
+ }
293
+ end
294
+
295
+ def wrap_variable_value(val)
296
+ [ResolvedVariable.new(name.gsub('-', '_'), val)]
297
+ end
298
+ end
299
+
300
+ class StringParam < Param
301
+ def initialize(name, arg_spec, description, optional: false, publish: false)
302
+ super name, arg_spec, description, optional: optional, publish: publish
303
+ end
304
+
305
+ def default_value(ctx, vars)
306
+ nil
307
+ end
308
+
309
+ def materialize(value, ctx, vars)
310
+ expand(value, vars)
311
+ end
312
+
313
+ def variables(value)
314
+ wrap_variable_value(value)
315
+ end
316
+ end
317
+
318
+ class BoolParam < Param
319
+ def initialize(name, description, publish: false)
320
+ super name, nil, description, publish: publish
321
+ end
322
+
323
+ def default_value(ctx, vars)
324
+ false
325
+ end
326
+
327
+ def materialize(value, ctx, vars)
328
+ !!value
329
+ end
330
+
331
+ def variables(bool)
332
+ wrap_variable_value(bool.to_s)
333
+ end
334
+ end
335
+
336
+ class OptionalBoolParam < Param
337
+ def initialize(name, description, default: false, publish: false)
338
+ super name, nil, description, optional: true, publish: publish
339
+ @default_value = default
340
+ end
341
+
342
+ def default_value(ctx, vars)
343
+ @default_value
344
+ end
345
+
346
+ def materialize(value, ctx, vars)
347
+ !!value
348
+ end
349
+
350
+ def variables(bool)
351
+ wrap_variable_value(bool.to_s)
352
+ end
353
+ end
354
+
355
+ class DateParam < Param
356
+ def initialize(name, arg_spec, description, optional: false, publish: false)
357
+ super name, arg_spec, description, optional: optional, publish: publish
358
+ end
359
+
360
+ def default_value(ctx, vars)
361
+ nil
362
+ end
363
+
364
+ def materialize(value, ctx, vars)
365
+ case value
366
+ when Date
367
+ value
368
+ when String
369
+ begin
370
+ Date.parse(expand(value, vars))
371
+ rescue ArgumentError
372
+ raise ParameterError, "bad date format: #{value.inspect}"
373
+ end
374
+ else
375
+ raise ParameterError, "unknown type for date parameter '#{name}': #{value.class}"
376
+ end
377
+ end
378
+
379
+ def variables(date)
380
+ # "YYYY-MM-DD"
381
+ wrap_variable_value(date.to_s)
382
+ end
383
+ end
384
+
385
+ class EnumParam < Param
386
+ def initialize(name, list, description, default: nil, publish: false)
387
+ super name, 'VALUE', description, optional: (default ? true : false), publish: publish
388
+ @list = list.map {|val| val.to_s.freeze }
389
+ @default_value = default
390
+ end
391
+
392
+ def description
393
+ "#{super} (#{@list.join(', ')})" + (@default ? " [default: #{@default}]" : '')
394
+ end
395
+
396
+ def default_value(ctx, vars)
397
+ @default_value
398
+ end
399
+
400
+ def materialize(value, ctx, vars)
401
+ val = expand(value.to_s, vars)
402
+ unless @list.include?(val)
403
+ raise ParameterError, "unknown value for enum parameter '#{name}': #{val.inspect}"
404
+ end
405
+ val
406
+ end
407
+
408
+ def variables(val)
409
+ wrap_variable_value(val)
410
+ end
411
+ end
412
+
413
+ class DataSourceParam < Param
414
+ def initialize(kind, name = 'data-source', description = 'Main data source.', optional: true, publish: false)
415
+ raise FatalError, "no data source kind declared" unless kind
416
+ super name, 'NAME', description, optional: optional, publish: publish
417
+ @kind = kind
418
+ end
419
+
420
+ def description
421
+ "#{super} [default: #{@kind}]"
422
+ end
423
+
424
+ def default_value(ctx, vars)
425
+ ctx.get_data_source(@kind, nil)
426
+ end
427
+
428
+ def materialize(value, ctx, vars)
429
+ ctx.get_data_source(@kind, expand(value, vars))
430
+ end
431
+
432
+ def variables(ds)
433
+ wrap_variable_value(ds.name)
434
+ end
435
+ end
436
+
437
+ class SQLFileParam < Param
438
+ def initialize(name = 'sql-file', arg_spec = 'PATH', description = 'SQL file.', optional: false, publish: false)
439
+ super name, arg_spec, description, optional: optional, publish: publish
440
+ end
441
+
442
+ def default_value(ctx, vars)
443
+ nil
444
+ end
445
+
446
+ def materialize(name_or_stmt, ctx, vars)
447
+ case name_or_stmt
448
+ when String
449
+ name = name_or_stmt
450
+ SQLStatement.new(ctx.parameter_file(expand(name.to_s, vars), 'sql'))
451
+ when SQLStatement
452
+ name_or_stmt
453
+ else
454
+ raise ParameterError, "unknown type for parameter #{name}: #{name_or_stmt.class}"
455
+ end
456
+ end
457
+
458
+ def variables(stmt)
459
+ wrap_variable_value(stmt.location)
460
+ end
461
+ end
462
+
463
+ class DestTableParam < Param
464
+ def initialize(
465
+ name = 'dest-table',
466
+ arg_spec = '[SCHEMA.]TABLE',
467
+ description = 'Target table name.',
468
+ # [CLUDGE] Default dest_table is provided by SQL parameter declarations,
469
+ # we cannot require the value here. I know this is bad...
470
+ optional: true,
471
+ publish: true
472
+ )
473
+ super name, arg_spec, description, optional: optional, publish: publish
474
+ end
475
+
476
+ def default_value(ctx, vars)
477
+ nil
478
+ end
479
+
480
+ def materialize(spec, ctx, vars)
481
+ TableSpec.parse(expand(spec, vars))
482
+ end
483
+
484
+ def variables(spec)
485
+ wrap_variable_value(spec.to_s)
486
+ end
487
+ end
488
+
489
+ class SrcTableParam < Param
490
+ def initialize(
491
+ name = 'src-tables',
492
+ arg_spec = 'VAR:[SCHEMA.]TABLE',
493
+ description = 'Source table name.',
494
+ optional: true, # Source tables may not exist (e.g. data loading)
495
+ publish: true
496
+ )
497
+ super name, arg_spec, description, optional: optional, publish: publish
498
+ end
499
+
500
+ def option_name
501
+ 'src-table'
502
+ end
503
+
504
+ def parse_option_value(value, h)
505
+ var, spec = value.split(':', 2)
506
+ if not var or var.empty?
507
+ raise ParameterError, "missing variable name: #{value.inspect}"
508
+ end
509
+ (h ||= {})[var] = spec
510
+ h # accumulator
511
+ end
512
+
513
+ def parse_value(h)
514
+ raise ParameterError, "bad type for parameter #{name}: #{h.class}" unless h.kind_of?(Hash)
515
+ h.empty? ? nil : h
516
+ end
517
+
518
+ def default_value(ctx, vars)
519
+ {}
520
+ end
521
+
522
+ def materialize(h, ctx, vars)
523
+ map = {}
524
+ h.each do |name, spec|
525
+ map[name] = TableSpec.parse(expand(spec, vars))
526
+ end
527
+ map
528
+ end
529
+
530
+ def variables(h)
531
+ h.map {|name, value| ResolvedVariable.new(name, value.to_s) }
532
+ end
533
+ end
534
+
535
+ class DestFileParam < Param
536
+ def initialize(name = 'dest-file', arg_spec = 'PATH', description = 'Target file name.',
537
+ optional: false, publish: false)
538
+ super name, arg_spec, description, optional: optional, publish: publish
539
+ end
540
+
541
+ def default_value(ctx, vars)
542
+ nil
543
+ end
544
+
545
+ def materialize(path, ctx, vars)
546
+ Pathname(expand(path, vars))
547
+ end
548
+
549
+ def variables(path)
550
+ wrap_variable_value(path.to_s)
551
+ end
552
+ end
553
+
554
+ class SrcFileParam < Param
555
+ def initialize(name = 'src-file', arg_spec = 'PATH', description = 'Source file name.',
556
+ optional: false, publish: false)
557
+ super name, arg_spec, description, optional: optional, publish: publish
558
+ end
559
+
560
+ def default_value(ctx, vars)
561
+ nil
562
+ end
563
+
564
+ def materialize(path, ctx, vars)
565
+ Pathname(expand(path, vars))
566
+ end
567
+
568
+ def variables(path)
569
+ wrap_variable_value(path.to_s)
570
+ end
571
+ end
572
+
573
+ class StringListParam < Param
574
+ def initialize(name, arg_spec, description, optional: false, publish: false)
575
+ super name, arg_spec, description, optional: optional, publish: publish
576
+ end
577
+
578
+ def parse_option_value(value, list)
579
+ (list ||= []).push value
580
+ list # accumulator
581
+ end
582
+
583
+ def parse_value(vals)
584
+ raise ParameterError, "bad type for parameter #{name}: #{vals.class}" unless vals.kind_of?(Array)
585
+ vals.empty? ? nil : vals
586
+ end
587
+
588
+ def default_value(ctx, vars)
589
+ []
590
+ end
591
+
592
+ def materialize(vals, ctx, vars)
593
+ vals.map {|val| expand(val, vars) }
594
+ end
595
+
596
+ def variables(strs)
597
+ wrap_variable_value(strs.join(' '))
598
+ end
599
+ end
600
+
601
+ class KeyValuePairsParam < Param
602
+ def initialize(name, arg_spec, description, optional: true, default: nil, value_handler: nil)
603
+ super name, arg_spec, description, optional: optional, publish: false
604
+ @default_value = default
605
+ @value_handler = value_handler
606
+ end
607
+
608
+ def parse_option_value(value, h)
609
+ var, spec = value.split(':', 2)
610
+ if not var or var.empty?
611
+ raise ParameterError, "missing variable name: #{value.inspect}"
612
+ end
613
+ (h ||= {})[var] = spec
614
+ h # accumulator
615
+ end
616
+
617
+ def parse_value(h)
618
+ case h
619
+ when Hash
620
+ h.empty? ? nil : h
621
+ when String # FIXME: should be removed after changing all load options
622
+ raise ParameterError, "bad type for parameter #{name}: #{h.class}" unless @value_handler
623
+ h.strip.empty? ? nil : h
624
+ else
625
+ raise ParameterError, "bad type for parameter #{name}: #{h.class}"
626
+ end
627
+ end
628
+
629
+ def default_value(ctx, vars)
630
+ @default_value
631
+ end
632
+
633
+ def materialize(pairs, ctx, vars)
634
+ if @value_handler
635
+ @value_handler.call(pairs, ctx, vars)
636
+ else
637
+ unless pairs.kind_of?(Hash)
638
+ raise "[BUG] bad value type #{pairs.class} for KeyValuePairsParam\#materialize (#{name})"
639
+ end
640
+ h = {}
641
+ pairs.each do |name, value|
642
+ h[name] = expand(value.to_s, vars)
643
+ end
644
+ h
645
+ end
646
+ end
647
+
648
+ def variables(h)
649
+ []
650
+ end
651
+ end
652
+
653
+ end
@@ -0,0 +1,78 @@
1
+ require 'bricolage/exception'
2
+ require 'pg'
3
+
4
+ module Bricolage
5
+
6
+ class PostgreSQLException < SQLException; end
7
+
8
+ class PostgresConnection
9
+ def initialize(connection, ds, logger)
10
+ @connection = connection
11
+ @ds = ds
12
+ @logger = logger
13
+ end
14
+
15
+ def source
16
+ @connection
17
+ end
18
+
19
+ def execute(query)
20
+ @logger.info "[#{@ds.name}] #{query}"
21
+ log_elapsed_time {
22
+ rs = @connection.exec(query)
23
+ result = rs.to_a
24
+ rs.clear
25
+ result
26
+ }
27
+ rescue PG::Error => ex
28
+ raise PostgreSQLException.wrap(ex)
29
+ end
30
+
31
+ alias update execute
32
+
33
+ def drop_table(name)
34
+ execute "drop table #{name} cascade;"
35
+ end
36
+
37
+ def drop_table_force(name)
38
+ drop_table name
39
+ rescue PostgreSQLException => err
40
+ @logger.error err.message
41
+ end
42
+
43
+ def select(table, &block)
44
+ query = "select * from #{table}"
45
+ @logger.info "[#{@ds.name}] #{query}"
46
+ rs = @connection.exec(query)
47
+ begin
48
+ yield rs
49
+ ensure
50
+ rs.clear
51
+ end
52
+ end
53
+
54
+ def vacuum(table)
55
+ execute "vacuum #{table};"
56
+ end
57
+
58
+ def vacuum_sort_only(table)
59
+ execute "vacuum sort only #{table};"
60
+ end
61
+
62
+ def analyze(table)
63
+ execute "analyze #{table};"
64
+ end
65
+
66
+ private
67
+
68
+ def log_elapsed_time
69
+ b = Time.now
70
+ return yield
71
+ ensure
72
+ e = Time.now
73
+ t = e - b
74
+ @logger.info "#{'%.1f' % t} secs"
75
+ end
76
+ end
77
+
78
+ end