masamune 0.11.0 → 0.11.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4aa0472dd6fa4bb472d79caf9a6c17a4570baad2
4
- data.tar.gz: 853b036996129473d7f9f8dee3129f198988face
3
+ metadata.gz: 06ac5ed8ef2f9949a444065bef219d2efd144b94
4
+ data.tar.gz: bf620007bd3016250133cda30feb6c03182ab1d4
5
5
  SHA512:
6
- metadata.gz: b48a634347ae17d3a214be9ee13b021a7099c992b6e23382347ab44019b7d5209e6c3d73af0957067fff8abb9b3365c6a40fe880ef88cb56f67f4f7cfcab62cc
7
- data.tar.gz: 861919413965988c50128a731f05e0ba9fd1bc9a5fc7a410038b23ce1e2e06fb4624889b11fb0142f321d0d2a8cc657e15a22f06a7efbc646674a4e99c228c16
6
+ metadata.gz: 589c931738aec24331ada2bab69a52175be9d27dc8a11e3a695ca7abbd38762582a5e3c93dafc61a6bbc7fac7f76d19ed6206a55a8e333795e6c113f0d64761c
7
+ data.tar.gz: cb8d8062f36d5ebc0bc72c3a8f3ba3c8acff5afa2f14186ddcf8b6844ca77cc9fc333b3802cab27443843ec8c8ed191e10f1274aea196b5be4c1acce7c8e8fbf
data/lib/masamune.rb CHANGED
@@ -26,7 +26,6 @@ module Masamune
26
26
  require 'masamune/io'
27
27
  require 'masamune/template'
28
28
  require 'masamune/commands'
29
- require 'masamune/accumulate'
30
29
  require 'masamune/last_element'
31
30
  require 'masamune/actions'
32
31
  require 'masamune/helpers'
@@ -34,5 +34,6 @@ module Masamune
34
34
  require 'masamune/actions/elastic_mapreduce'
35
35
  require 'masamune/actions/execute'
36
36
  require 'masamune/actions/transform'
37
+ require 'masamune/actions/invoke_parallel'
37
38
  end
38
39
  end
@@ -46,7 +46,7 @@ module Masamune::Actions
46
46
  end if block_given?
47
47
 
48
48
  command = Masamune::Commands::Shell.new(klass.new(self), {fail_fast: false}.merge(opts))
49
- command.execute
49
+ opts.fetch(:interactive, true) ? command.execute : command.replace(opts)
50
50
  end
51
51
  end
52
52
  end
@@ -0,0 +1,73 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'active_support/concern'
24
+ require 'parallel'
25
+
26
+ module Masamune::Actions
27
+ module InvokeParallel
28
+ extend ActiveSupport::Concern
29
+
30
+ include Masamune::Actions::Execute
31
+
32
+ included do |base|
33
+ base.class_option :max_tasks, aliases: '-p', :type => :numeric, :desc => 'Maximum number of tasks to execute in parallel', :default => 4
34
+ end
35
+
36
+ def invoke_parallel(*task_group)
37
+ opts = task_group.last.is_a?(Hash) ? task_group.pop.dup : {}
38
+ max_tasks = [opts.delete(:max_tasks), task_group.count].min
39
+ console("Setting max_tasks to #{max_tasks}")
40
+ bail_fast task_group, opts if opts[:version]
41
+ Parallel.each(task_group, in_processes: max_tasks) do |task_name|
42
+ begin
43
+ execute(thor_wrapper, task_name, *task_args(opts), interactive: false, detach: false)
44
+ rescue SystemExit
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def thor_wrapper
52
+ 'thor'
53
+ end
54
+
55
+ def bail_fast(task_group, opts = {})
56
+ task_name = task_group.first
57
+ execute($PROGRAM_NAME, task_name, *task_args(opts))
58
+ exit
59
+ end
60
+
61
+ def task_args(opts = {})
62
+ opts.map do |k,v|
63
+ case v
64
+ when true
65
+ "--#{k.to_s.gsub('_', '-')}"
66
+ when false
67
+ else
68
+ ["--#{k.to_s.gsub('_', '-')}", v]
69
+ end
70
+ end.flatten.compact
71
+ end
72
+ end
73
+ end
@@ -22,8 +22,6 @@
22
22
 
23
23
  module Masamune
24
24
  class CachedFilesystem < SimpleDelegator
25
- include Masamune::Accumulate
26
-
27
25
  def initialize(filesystem)
28
26
  super filesystem
29
27
  @filesystem = filesystem
@@ -38,12 +36,12 @@ module Masamune
38
36
  @cache.key?(file) || glob(file).include?(file) || @cache.key?(file)
39
37
  end
40
38
 
41
- def glob(file_or_glob, &block)
39
+ def glob(file_or_glob)
40
+ return Set.new(to_enum(:glob, file_or_glob)) unless block_given?
42
41
  glob_stat(file_or_glob) do |entry|
43
42
  yield entry.name unless entry.name == dirname(file_or_glob)
44
43
  end
45
44
  end
46
- method_accumulate :glob
47
45
 
48
46
  def stat(file_or_dir)
49
47
  raise ArgumentError, 'cannot contain wildcard' if file_or_dir.include?('*')
@@ -41,13 +41,13 @@ module Masamune::Commands
41
41
  @stderr_line_no = 0
42
42
  end
43
43
 
44
- def replace
44
+ def replace(opts = {})
45
45
  logger.debug('replace: ' + command_args.join(' '))
46
46
  before_execute
47
47
  around_execute do
48
48
  pid = Process.fork
49
49
  if pid
50
- STDIN.close; STDOUT.close; STDERR.close
50
+ detach if opts.fetch(:detach, true)
51
51
  Process.waitpid(pid)
52
52
  exit
53
53
  else
@@ -198,5 +198,9 @@ module Masamune::Commands
198
198
  return code unless status
199
199
  status.exitstatus
200
200
  end
201
+
202
+ def detach
203
+ STDIN.close; STDOUT.close; STDERR.close
204
+ end
201
205
  end
202
206
  end
@@ -23,7 +23,6 @@
23
23
  class Masamune::DataPlan::Elem
24
24
  MISSING_MODIFIED_AT = Time.new(0)
25
25
 
26
- include Masamune::Accumulate
27
26
  include Comparable
28
27
 
29
28
  attr_reader :rule, :options
@@ -69,39 +68,39 @@ class Masamune::DataPlan::Elem
69
68
  end || MISSING_MODIFIED_AT
70
69
  end
71
70
 
72
- def explode(&block)
71
+ def explode
72
+ return Set.new(to_enum(__method__)) unless block_given?
73
73
  if rule.for_path?
74
74
  file_glob = path
75
75
  file_glob += '/' unless path.include?('*') || path.include?('.')
76
76
  file_glob += '*' unless path.include?('*')
77
- rule.engine.filesystem.glob(file_glob) do |new_path|
77
+ rule.engine.filesystem.glob(file_glob).each do |new_path|
78
78
  yield rule.bind_input(new_path)
79
79
  end
80
80
  elsif rule.for_table_with_partition?
81
81
  yield self if exists?
82
82
  end
83
83
  end
84
- method_accumulate :explode
85
84
 
86
- def targets(&block)
85
+ def targets
87
86
  return Masamune::DataPlan::Set::EMPTY if @rule.for_targets?
88
- rule.engine.targets_for_source(rule.name, self) do |target|
87
+ return Masamune::DataPlan::Set.new(rule.engine.get_target_rule(rule.name), to_enum(__method__)) unless block_given?
88
+ rule.engine.targets_for_source(rule.name, self).each do |target|
89
89
  yield target
90
90
  end
91
91
  end
92
- method_accumulate :targets, lambda { |elem| Masamune::DataPlan::Set.new(elem.rule.engine.get_target_rule(elem.rule.name)) }
93
92
 
94
93
  def target
95
94
  targets.first
96
95
  end
97
96
 
98
- def sources(&block)
97
+ def sources
99
98
  return Masamune::DataPlan::Set::EMPTY if @rule.for_sources?
100
- rule.engine.sources_for_target(rule.name, self) do |source|
99
+ return Masamune::DataPlan::Set.new(rule.engine.get_source_rule(rule.name), to_enum(__method__)) unless block_given?
100
+ rule.engine.sources_for_target(rule.name, self).each do |source|
101
101
  yield source
102
102
  end
103
103
  end
104
- method_accumulate :sources, lambda { |elem| Masamune::DataPlan::Set.new(elem.rule.engine.get_source_rule(elem.rule.name)) }
105
104
 
106
105
  def source
107
106
  sources.first
@@ -27,7 +27,6 @@ class Masamune::DataPlan::Engine
27
27
  MAX_DEPTH = 10
28
28
 
29
29
  include Masamune::HasEnvironment
30
- include Masamune::Accumulate
31
30
 
32
31
  def initialize
33
32
  @target_rules = Hash.new
@@ -80,34 +79,34 @@ class Masamune::DataPlan::Engine
80
79
  end
81
80
  end
82
81
 
83
- def targets_for_date_range(rule, start, stop, &block)
82
+ def targets_for_date_range(rule, start, stop)
83
+ return Masamune::DataPlan::Set.new(get_target_rule(rule), to_enum(:targets_for_date_range, rule, start, stop)) unless block_given?
84
84
  target_template = @target_rules[rule]
85
85
  return unless target_template
86
- target_template.generate(start.to_time.utc, stop.to_time.utc) do |target|
86
+ target_template.generate(start.to_time.utc, stop.to_time.utc).each do |target|
87
87
  yield target
88
88
  end
89
89
  end
90
- method_accumulate :targets_for_date_range, lambda { |engine, rule, _, _| Masamune::DataPlan::Set.new(engine.get_target_rule(rule)) }
91
90
 
92
- def targets_for_source(rule, source, &block)
91
+ def targets_for_source(rule, source)
92
+ return Masamune::DataPlan::Set.new(get_target_rule(rule), to_enum(:targets_for_source, rule, source)) unless block_given?
93
93
  source_template = @source_rules[rule]
94
94
  target_template = @target_rules[rule]
95
95
  source_instance = source_template.bind_input(source)
96
- source_template.generate_via_unify(source_instance, target_template) do |target|
96
+ source_template.generate_via_unify(source_instance, target_template).each do |target|
97
97
  yield target
98
98
  end
99
99
  end
100
- method_accumulate :targets_for_source, lambda { |engine, rule, source| Masamune::DataPlan::Set.new(engine.get_target_rule(rule)) }
101
100
 
102
- def sources_for_target(rule, target, &block)
101
+ def sources_for_target(rule, target)
102
+ return Masamune::DataPlan::Set.new(get_source_rule(rule), to_enum(:sources_for_target, rule, target)) unless block_given?
103
103
  source_template = @source_rules[rule]
104
104
  target_template = @target_rules[rule]
105
105
  target_instance = target_template.bind_input(target)
106
- target_template.generate_via_unify(target_instance, source_template) do |source|
106
+ target_template.generate_via_unify(target_instance, source_template).each do |source|
107
107
  yield source
108
108
  end
109
109
  end
110
- method_accumulate :sources_for_target, lambda { |engine, rule, target| Masamune::DataPlan::Set.new(engine.get_source_rule(rule)) }
111
110
 
112
111
  def targets(rule)
113
112
  @set_cache[:targets_for_rule][rule] ||= @targets[rule].union(@sources[rule].targets)
@@ -146,7 +145,7 @@ class Masamune::DataPlan::Engine
146
145
  @current_depth > 0
147
146
  end
148
147
 
149
- def constrain_max_depth(rule, &block)
148
+ def constrain_max_depth(rule)
150
149
  @current_depth += 1
151
150
  raise "Max depth of #{MAX_DEPTH} exceeded for rule '#{rule}'" if @current_depth > MAX_DEPTH
152
151
  yield
@@ -31,8 +31,6 @@ require 'date'
31
31
  class Masamune::DataPlan::Rule
32
32
  TERMINAL = nil
33
33
 
34
- include Masamune::Accumulate
35
-
36
34
  attr_reader :engine, :name, :type, :options
37
35
 
38
36
  def initialize(engine, name, type, options = {})
@@ -128,7 +126,8 @@ class Masamune::DataPlan::Rule
128
126
  rule.bind_date(elem.start_time)
129
127
  end
130
128
 
131
- def generate(start_time, stop_time, &block)
129
+ def generate(start_time, stop_time)
130
+ return Set.new(to_enum(:generate, start_time, stop_time)) unless block_given?
132
131
  instance = bind_date(start_time)
133
132
 
134
133
  begin
@@ -136,9 +135,9 @@ class Masamune::DataPlan::Rule
136
135
  instance = instance.next
137
136
  end while instance.start_time <= stop_time
138
137
  end
139
- method_accumulate :generate
140
138
 
141
- def generate_via_unify(elem, rule, &block)
139
+ def generate_via_unify(elem, rule)
140
+ return Set.new(to_enum(:generate_via_unify, elem, rule)) unless block_given?
142
141
  instance = unify(elem, rule)
143
142
 
144
143
  stop_time = instance.start_time.advance(time_step => 1)
@@ -147,7 +146,6 @@ class Masamune::DataPlan::Rule
147
146
  instance = instance.next
148
147
  end while instance.start_time < stop_time
149
148
  end
150
- method_accumulate :generate_via_unify
151
149
 
152
150
  def tz
153
151
  ActiveSupport::TimeZone[@options.fetch(:tz, 'UTC')]
@@ -199,6 +197,7 @@ class Masamune::DataPlan::Rule
199
197
  end
200
198
 
201
199
  def adjacent_matches(instance)
200
+ return Set.new(to_enum(:adjacent_matches, instance)) unless block_given?
202
201
  (-window .. -1).each do |i|
203
202
  yield instance.prev(i.abs)
204
203
  end
@@ -207,7 +206,6 @@ class Masamune::DataPlan::Rule
207
206
  yield instance.next(i)
208
207
  end
209
208
  end
210
- method_accumulate :adjacent_matches
211
209
 
212
210
  def inspect
213
211
  {type: type, pattern: pattern, options: options}.to_s
@@ -25,8 +25,6 @@ require 'set'
25
25
  class Masamune::DataPlan::Set < Set
26
26
  EMPTY = self.new
27
27
 
28
- include Masamune::Accumulate
29
-
30
28
  attr_reader :rule
31
29
 
32
30
  def initialize(rule, enum = nil)
@@ -46,50 +44,51 @@ class Masamune::DataPlan::Set < Set
46
44
  super convert_elem(elem)
47
45
  end
48
46
 
49
- def missing(&block)
47
+ def missing
48
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
50
49
  self.each do |elem|
51
- yield elem if elem.explode.empty?
50
+ yield elem if elem.explode.count < 1
52
51
  end
53
52
  end
54
- method_accumulate :missing, lambda { |set| set.class.new(set.rule) }
55
53
 
56
- def existing(&block)
54
+ def existing
55
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
57
56
  self.each do |elem|
58
- elem.explode do |new_elem|
57
+ elem.explode.each do |new_elem|
59
58
  yield new_elem
60
59
  end
61
60
  end
62
61
  end
63
- method_accumulate :existing, lambda { |set| set.class.new(set.rule) }
64
62
 
65
- def adjacent(&block)
63
+ def adjacent
64
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
66
65
  self.each do |elem|
67
- @rule.adjacent_matches(elem) do |adj_elem|
66
+ @rule.adjacent_matches(elem).each do |adj_elem|
68
67
  yield adj_elem
69
68
  end
70
69
  end
71
70
  end
72
- method_accumulate :adjacent, lambda { |set| set.class.new(set.rule) }
73
71
 
74
- def stale(&block)
72
+ def stale
75
73
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_sources?
74
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
76
75
  self.each do |target|
77
76
  yield target if target.sources.existing.any? { |source| target_stale?(source, target) }
78
77
  end
79
78
  end
80
- method_accumulate :stale, lambda { |set| set.class.new(set.rule) }
81
79
 
82
- def incomplete(&block)
80
+ def incomplete
83
81
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_sources?
82
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
84
83
  set = Set.new
85
84
  self.each do |target|
86
85
  yield target if set.add?(target) unless target.complete?
87
86
  end
88
87
  end
89
- method_accumulate :incomplete, lambda { |set| set.class.new(set.rule) }
90
88
 
91
- def actionable(&block)
89
+ def actionable
92
90
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_sources?
91
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
93
92
  set = Set.new
94
93
  missing.each do |target|
95
94
  yield target if set.add?(target)
@@ -101,41 +100,42 @@ class Masamune::DataPlan::Set < Set
101
100
  yield target if set.add?(target)
102
101
  end
103
102
  end
104
- method_accumulate :actionable, lambda { |set| set.class.new(set.rule) }
105
103
 
106
- def updateable(&block)
104
+ def updateable
107
105
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_sources?
106
+ return self.class.new(rule, to_enum(__method__)) unless block_given?
108
107
  set = Set.new
109
108
  actionable.each do |target|
110
109
  yield target if set.add?(target) && target.sources.existing.any?
111
110
  end
112
111
  end
113
- method_accumulate :updateable, lambda { |set| set.class.new(set.rule) }
114
112
 
115
113
  # TODO detect & warn or correct if coarser grain set is incomplete
116
- def with_grain(grain, &block)
114
+ def with_grain(grain)
115
+ return self.class.new(rule.round(grain), to_enum(:with_grain, grain)) unless block_given?
117
116
  seen = Set.new
118
117
  self.each do |elem|
119
118
  granular_elem = elem.round(grain)
120
119
  yield granular_elem if seen.add?(granular_elem)
121
120
  end
122
121
  end
123
- method_accumulate :with_grain, lambda { |set, grain| set.class.new(set.rule.round(grain)) }
124
122
 
125
123
  def targets
126
124
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_targets?
127
- self.class.new(self.first.targets.rule).tap do |set|
128
- self.each do |elem|
129
- set.merge elem.targets
125
+ return self.class.new(self.first.targets.rule, to_enum(__method__)) unless block_given?
126
+ self.each do |elem|
127
+ elem.targets.each do |target|
128
+ yield target
130
129
  end
131
130
  end
132
131
  end
133
132
 
134
133
  def sources
135
134
  return Masamune::DataPlan::Set::EMPTY if empty? || @rule.for_sources?
136
- self.class.new(self.first.sources.rule).tap do |set|
137
- self.each do |elem|
138
- set.merge elem.sources
135
+ return self.class.new(self.first.sources.rule, to_enum(__method__)) unless block_given?
136
+ self.each do |elem|
137
+ elem.sources.each do |source|
138
+ yield source
139
139
  end
140
140
  end
141
141
  end
@@ -159,7 +159,7 @@ class Masamune::DataPlan::Set < Set
159
159
  when nil
160
160
  when Array
161
161
  enum.flatten.uniq
162
- when Set, self.class
162
+ when Set, self.class, Enumerator
163
163
  enum
164
164
  when String
165
165
  [enum]
@@ -89,9 +89,7 @@ module Masamune
89
89
  end
90
90
 
91
91
  def logger
92
- @logger ||= Logger.new(log_file_io).tap do
93
- symlink_latest_log
94
- end
92
+ @logger ||= Logger.new(log_file_io)
95
93
  end
96
94
 
97
95
  def console(*a)
@@ -151,14 +149,5 @@ module Masamune
151
149
  configuration.debug ? $stderr : nil
152
150
  end
153
151
  end
154
-
155
- def symlink_latest_log
156
- return unless filesystem.has_path?(:log_dir)
157
- latest = filesystem.path(:log_dir, 'latest')
158
- FileUtils.rm(latest) if File.exists?(latest)
159
- FileUtils.ln_s(log_file_name, latest)
160
- rescue => e
161
- logger.error(e)
162
- end
163
152
  end
164
153
  end
@@ -25,7 +25,6 @@ require 'masamune/has_environment'
25
25
  module Masamune
26
26
  class Filesystem
27
27
  include Masamune::HasEnvironment
28
- include Masamune::Accumulate
29
28
  include Masamune::Actions::S3Cmd
30
29
  include Masamune::Actions::HadoopFilesystem
31
30
 
@@ -88,7 +87,7 @@ module Masamune
88
87
  path[0] != '/'
89
88
  end
90
89
 
91
- def parent_paths(path, &block)
90
+ def parent_paths(path)
92
91
  if prefix = remote_prefix(path)
93
92
  node = path.split(prefix).last
94
93
  else
@@ -96,25 +95,26 @@ module Masamune
96
95
  node = path
97
96
  end
98
97
 
99
- return if prefix.blank? && node.blank?
98
+ return [] if prefix.blank? && node.blank?
100
99
  parent_paths = node ? File.expand_path(node, '/').split('/') : []
101
100
  parent_paths.reject! { |x| x.blank? }
102
101
  parent_paths.prepend('/') if node =~ %r{\A/}
103
102
  tmp = []
103
+ result = []
104
104
  parent_paths.each do |part|
105
105
  tmp << part
106
106
  current_path = prefix + File.join(tmp)
107
107
  break if current_path == path
108
- yield current_path
108
+ result << current_path
109
109
  end
110
+ result
110
111
  end
111
- method_accumulate :parent_paths
112
112
 
113
113
  def root_path?(path)
114
114
  raise ArgumentError, 'path cannot be nil' if path.nil?
115
115
  raise ArgumentError, 'path cannot be blank' if path.blank?
116
116
  raise ArgumentError, 'path cannot be relative' if relative_path?(path)
117
- parent_paths(path).length < 1
117
+ parent_paths(path).count < 1
118
118
  end
119
119
 
120
120
  def resolve_file(paths = [])
@@ -167,7 +167,8 @@ module Masamune
167
167
  end
168
168
  end
169
169
 
170
- def glob_stat(pattern, &block)
170
+ def glob_stat(pattern)
171
+ return Set.new(to_enum(:glob_stat, pattern)) unless block_given?
171
172
  case type(pattern)
172
173
  when :hdfs
173
174
  hadoop_fs('-ls', '-R', pattern, safe: true) do |line|
@@ -193,7 +194,6 @@ module Masamune
193
194
  end
194
195
  end
195
196
  end
196
- method_accumulate :glob_stat
197
197
 
198
198
  def stat(file_or_dir)
199
199
  raise ArgumentError, 'cannot contain wildcard' if file_or_dir.include?('*')
@@ -219,7 +219,8 @@ module Masamune
219
219
  end
220
220
  end
221
221
 
222
- def glob(pattern, &block)
222
+ def glob(pattern)
223
+ return Set.new(to_enum(:glob, pattern)) unless block_given?
223
224
  case type(pattern)
224
225
  when :hdfs
225
226
  file_glob, file_regexp = glob_split(pattern)
@@ -245,7 +246,6 @@ module Masamune
245
246
  end
246
247
  end
247
248
  end
248
- method_accumulate :glob
249
249
 
250
250
  def glob_sort(pattern, options = {})
251
251
  result = glob(pattern)
@@ -115,7 +115,7 @@ module Masamune::Schema
115
115
  @stores[store_id.to_sym]
116
116
  end
117
117
 
118
- def table(id, options = {}, &block)
118
+ def table(id, options = {})
119
119
  @context.push(options)
120
120
  yield if block_given?
121
121
  @context.tables[id] ||= Masamune::Schema::Table.new(@context.options.merge(id: id))
@@ -124,7 +124,7 @@ module Masamune::Schema
124
124
  @context.pop
125
125
  end
126
126
 
127
- def dimension(id, options = {}, &block)
127
+ def dimension(id, options = {})
128
128
  @context.push(options)
129
129
  yield if block_given?
130
130
  @context.dimensions[id] ||= Masamune::Schema::Dimension.new(@context.options.merge(id: id))
@@ -133,7 +133,7 @@ module Masamune::Schema
133
133
  @context.pop
134
134
  end
135
135
 
136
- def column(id, options = {}, &block)
136
+ def column(id, options = {})
137
137
  @context.options[:columns] << dereference_column(id, options)
138
138
  end
139
139
 
@@ -152,7 +152,7 @@ module Masamune::Schema
152
152
  @context.options[:rows] << Masamune::Schema::Row.new(attributes)
153
153
  end
154
154
 
155
- def fact(id, options = {}, &block)
155
+ def fact(id, options = {})
156
156
  @context.push(options)
157
157
  grain = Array.wrap(options.delete(:grain) || [])
158
158
  fact_attributes(grain).each do |attributes|
@@ -164,15 +164,15 @@ module Masamune::Schema
164
164
  @context.pop
165
165
  end
166
166
 
167
- def partition(id, options = {}, &block)
167
+ def partition(id, options = {})
168
168
  @context.options[:columns] << Masamune::Schema::Column.new(options.merge(id: id, partition: true))
169
169
  end
170
170
 
171
- def measure(id, options = {}, &block)
171
+ def measure(id, options = {})
172
172
  @context.options[:columns] << Masamune::Schema::Column.new(options.merge(id: id, measure: true))
173
173
  end
174
174
 
175
- def file(id, options = {}, &block)
175
+ def file(id, options = {})
176
176
  format_options = options.extract!(:format, :headers)
177
177
  @context.push(options)
178
178
  yield if block_given?
@@ -182,7 +182,7 @@ module Masamune::Schema
182
182
  @context.pop
183
183
  end
184
184
 
185
- def event(id, options = {}, &block)
185
+ def event(id, options = {})
186
186
  @context.push(options)
187
187
  yield if block_given?
188
188
  @context.events[id] = HasMap.new Masamune::Schema::Event.new(@context.options.merge(id: id))
@@ -190,7 +190,7 @@ module Masamune::Schema
190
190
  @context.pop
191
191
  end
192
192
 
193
- def attribute(id, options = {}, &block)
193
+ def attribute(id, options = {})
194
194
  @context.options[:attributes] << Masamune::Schema::Event::Attribute.new(options.merge(id: id))
195
195
  end
196
196
 
@@ -376,7 +376,7 @@ module Masamune::Schema
376
376
 
377
377
  # TODO: Add ELEMENT REFERENCES
378
378
  def reference_constraint
379
- return if parent.temporary?
379
+ return if parent && parent.temporary?
380
380
  return if degenerate?
381
381
  return if array_value?
382
382
  if reference && reference.surrogate_key.type == type
@@ -465,7 +465,7 @@ module Masamune::Schema
465
465
  if reference
466
466
  !(reference.null || reference.default)
467
467
  else
468
- surrogate_key || natural_key
468
+ surrogate_key || natural_key || !(null || default)
469
469
  end
470
470
  end
471
471
 
@@ -126,7 +126,8 @@ module Masamune::Schema
126
126
  # TODO: Default to GIN for array columns
127
127
  def index_columns
128
128
  index_column_map.map do |_, column_names|
129
- [column_names, reverse_unique_constraints_map.key?(column_names.sort), short_md5(column_names)]
129
+ unique_index = reverse_unique_constraints_map.key?(column_names.sort)
130
+ [column_names, unique_index, short_md5(column_names)]
130
131
  end
131
132
  end
132
133
 
@@ -292,7 +293,7 @@ module Masamune::Schema
292
293
  column.unique.each do |unique|
293
294
  map[unique] << column.name
294
295
  end
295
- end
296
+ end unless temporary?
296
297
  Hash[map.sort_by { |k, v| v.length }]
297
298
  end
298
299
  end
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Masamune
24
- VERSION = '0.11.0'
24
+ VERSION = '0.11.1'
25
25
  end
@@ -20,41 +20,29 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- module Masamune
24
- module Accumulate
25
- def accumulate(method, accumulator, *args)
26
- accumulator.call(self, *args).tap do |elems|
27
- send(method, *args) do |elem|
28
- elems << elem
29
- end
30
- end
23
+ require 'spec_helper'
24
+
25
+ describe Masamune::Actions::InvokeParallel do
26
+ let(:klass) do
27
+ Class.new(Thor) do
28
+ include Masamune::HasEnvironment
29
+ include Masamune::Actions::InvokeParallel
31
30
  end
31
+ end
32
32
 
33
- module ClassMethods
34
- def method_accumulate(method, accumulator = lambda { |_, *args| Array.new })
35
- self.class_eval do
36
- new_method = "#{method}_with_accumulate".to_sym
37
- old_method = "#{method}_without_accumulate".to_sym
38
- alias_method old_method, method
33
+ let(:instance) { klass.new }
39
34
 
40
- define_method(new_method) do |*args, &block|
41
- if block
42
- instance = accumulator.call(self, *args)
43
- send(old_method, *args) do |elem|
44
- next if instance.respond_to?(:add?) && !instance.add?(elem)
45
- block.call elem
46
- end
47
- else
48
- accumulate(old_method, accumulator, *args)
49
- end
50
- end
51
- alias_method method, new_method
52
- end
35
+ describe '.invoke_parallel' do
36
+ context 'with a simple thor command' do
37
+ before do
38
+ mock_command(/\Athor list/, mock_success)
39
+ end
40
+
41
+ subject do
42
+ instance.invoke_parallel('list', max_tasks: 1)
53
43
  end
54
- end
55
44
 
56
- def self.included(base)
57
- base.extend ClassMethods
45
+ it { expect { subject }.to_not raise_error }
58
46
  end
59
47
  end
60
48
  end
@@ -40,8 +40,8 @@ describe Masamune::CachedFilesystem do
40
40
  expect(cached_filesystem.exists?('/a/b/c/3.txt')).to eq(true)
41
41
  expect(cached_filesystem.exists?('/a/b/c/4.txt')).to eq(false)
42
42
  expect(cached_filesystem.exists?('/a/b/c')).to eq(true)
43
- expect(cached_filesystem.glob('/a/b/c/*')).not_to be_empty
44
- expect(cached_filesystem.glob('/a/b/c/*.txt')).not_to be_empty
43
+ expect(cached_filesystem.glob('/a/b/c/*').count).to eq(3)
44
+ expect(cached_filesystem.glob('/a/b/c/*.txt').count).to eq(3)
45
45
  expect(cached_filesystem.stat('/a/b/c/1.txt')).to_not be_nil
46
46
  expect(cached_filesystem.stat('/a/b/c/2.txt')).to_not be_nil
47
47
  expect(cached_filesystem.stat('/a/b/c/3.txt')).to_not be_nil
@@ -60,7 +60,9 @@ describe Masamune::CachedFilesystem do
60
60
  end
61
61
 
62
62
  it 'calls Filesystem#glob_stat once for multiple calls' do
63
- expect(cached_filesystem.glob('/a/b/*')).to eq(['/a/b/c/1.txt', '/a/b/c/2.txt', '/a/b/c/3.txt'])
63
+ expect(cached_filesystem.glob('/a/b/*')).to include '/a/b/c/1.txt'
64
+ expect(cached_filesystem.glob('/a/b/*')).to include '/a/b/c/2.txt'
65
+ expect(cached_filesystem.glob('/a/b/*')).to include '/a/b/c/3.txt'
64
66
  expect(cached_filesystem.exists?('/a/b/c/1.txt')).to eq(true)
65
67
  expect(cached_filesystem.exists?('/a/b/c/2.txt')).to eq(true)
66
68
  expect(cached_filesystem.exists?('/a/b/c/3.txt')).to eq(true)
@@ -87,8 +89,8 @@ describe Masamune::CachedFilesystem do
87
89
  expect(cached_filesystem.exists?('/y=2013/m=1/d=22/00000')).to eq(true)
88
90
  expect(cached_filesystem.exists?('/y=2013/m=1/d=22')).to eq(true)
89
91
  expect(cached_filesystem.exists?('/y=2013/m=1/d=2')).to eq(false)
90
- expect(cached_filesystem.glob('/y=2013/m=1/*')).not_to be_empty
91
- expect(cached_filesystem.glob('/y=2013/m=1/d=22/*')).not_to be_empty
92
+ expect(cached_filesystem.glob('/y=2013/m=1/*').count).to eq(1)
93
+ expect(cached_filesystem.glob('/y=2013/m=1/d=22/*').count).to eq(1)
92
94
  expect(cached_filesystem.stat('/y=2013/m=1/d=22/00000')).not_to be_nil
93
95
  expect(cached_filesystem.stat('/y=2013/m=1/d=22')).not_to be_nil
94
96
  expect(cached_filesystem.stat('/y=2013/m=1')).not_to be_nil
@@ -113,15 +115,15 @@ describe Masamune::CachedFilesystem do
113
115
  expect(cached_filesystem.exists?('/logs/box4_123.txt')).to eq(false)
114
116
  expect(cached_filesystem.exists?('/logs/box4_456.txt')).to eq(false)
115
117
  expect(cached_filesystem.exists?('/logs/box')).to eq(false)
116
- expect(cached_filesystem.glob('/logs/*')).not_to be_empty
117
- expect(cached_filesystem.glob('/logs/*.txt')).not_to be_empty
118
- expect(cached_filesystem.glob('/logs/box1_*.txt')).not_to be_empty
119
- expect(cached_filesystem.glob('/logs/box2_*.txt')).not_to be_empty
120
- expect(cached_filesystem.glob('/logs/box3_*.txt')).not_to be_empty
121
- expect(cached_filesystem.glob('/logs/box*.txt').size).to eq(3)
122
- expect(cached_filesystem.glob('/logs/box*.csv')).to be_empty
123
- expect(cached_filesystem.glob('/logs/box')).to be_empty
124
- expect(cached_filesystem.glob('/logs/box/*')).to be_empty
118
+ expect(cached_filesystem.glob('/logs/*').count).to eq(3)
119
+ expect(cached_filesystem.glob('/logs/*.txt').count).to eq(3)
120
+ expect(cached_filesystem.glob('/logs/box1_*.txt').count).to eq(1)
121
+ expect(cached_filesystem.glob('/logs/box2_*.txt').count).to eq(1)
122
+ expect(cached_filesystem.glob('/logs/box3_*.txt').count).to eq(1)
123
+ expect(cached_filesystem.glob('/logs/box*.txt').count).to eq(3)
124
+ expect(cached_filesystem.glob('/logs/box*.csv').count).to eq(0)
125
+ expect(cached_filesystem.glob('/logs/box').count).to eq(0)
126
+ expect(cached_filesystem.glob('/logs/box/*').count).to eq(0)
125
127
  expect(cached_filesystem.stat('/logs/box1_123.txt')).to_not be_nil
126
128
  expect(cached_filesystem.stat('/logs/box1_456.txt')).to be_nil
127
129
  expect(cached_filesystem.stat('/logs/box2_123.txt')).to_not be_nil
@@ -149,13 +151,13 @@ describe Masamune::CachedFilesystem do
149
151
  expect(cached_filesystem.exists?('/a/b/c')).to eq(true)
150
152
  expect(cached_filesystem.exists?('/a/b')).to eq(true)
151
153
  expect(cached_filesystem.exists?('/a')).to eq(true)
152
- expect(cached_filesystem.glob('/a')).to be_empty
153
- expect(cached_filesystem.glob('/a/*')).not_to be_empty
154
- expect(cached_filesystem.glob('/a/b')).to be_empty
155
- expect(cached_filesystem.glob('/a/b/*')).not_to be_empty
156
- expect(cached_filesystem.glob('/a/b/c')).not_to be_empty
157
- expect(cached_filesystem.glob('/a/b/c/*')).to be_empty
158
- expect(cached_filesystem.glob('/a/b/c/*.txt')).to be_empty
154
+ expect(cached_filesystem.glob('/a').count).to eq(0)
155
+ expect(cached_filesystem.glob('/a/*').count).to eq(1)
156
+ expect(cached_filesystem.glob('/a/b').count).to eq(0)
157
+ expect(cached_filesystem.glob('/a/b/*').count).to eq(1)
158
+ expect(cached_filesystem.glob('/a/b/c').count).to eq(1)
159
+ expect(cached_filesystem.glob('/a/b/c/*').count).to eq(0)
160
+ expect(cached_filesystem.glob('/a/b/c/*.txt').count).to eq(0)
159
161
  expect(cached_filesystem.stat('/a/b/c/1.txt')).to be_nil
160
162
  expect(cached_filesystem.stat('/a/b/c/2.txt')).to be_nil
161
163
  expect(cached_filesystem.stat('/a/b/c/3.txt')).to be_nil
@@ -43,7 +43,7 @@ describe Masamune::DataPlan::Set do
43
43
  end
44
44
 
45
45
  context 'when all missing' do
46
- it { expect(missing.size).to eq(3) }
46
+ it { expect(missing.count).to eq(3) }
47
47
  it { expect(missing).to include '/table/y=2013/m=01/d=01' }
48
48
  it { expect(missing).to include '/table/y=2013/m=01/d=02' }
49
49
  it { expect(missing).to include '/table/y=2013/m=01/d=03' }
@@ -53,7 +53,7 @@ describe Masamune::DataPlan::Set do
53
53
  before do
54
54
  fs.touch!('/table/y=2013/m=01/d=01', '/table/y=2013/m=01/d=02')
55
55
  end
56
- it { expect(missing.size).to eq(3) }
56
+ it { expect(missing.count).to eq(3) }
57
57
  it { expect(missing).to include '/table/y=2013/m=01/d=01' }
58
58
  it { expect(missing).to include '/table/y=2013/m=01/d=02' }
59
59
  it { expect(missing).to include '/table/y=2013/m=01/d=03' }
@@ -63,7 +63,7 @@ describe Masamune::DataPlan::Set do
63
63
  before do
64
64
  fs.touch!('/table/y=2013/m=01/d=01/0000', '/table/y=2013/m=01/d=02/0000')
65
65
  end
66
- it { expect(missing.size).to eq(1) }
66
+ it { expect(missing.count).to eq(1) }
67
67
  it { expect(missing).to include '/table/y=2013/m=01/d=03' }
68
68
  end
69
69
 
@@ -93,7 +93,7 @@ describe Masamune::DataPlan::Set do
93
93
  before do
94
94
  fs.touch!('/log/20130101.random_1.log', '/log/20130101.random_2.log')
95
95
  end
96
- it { expect(existing.size).to eq(1) }
96
+ it { expect(existing.count).to eq(1) }
97
97
  it { expect(existing).to include '/log/20130101.random_1.log' }
98
98
  end
99
99
 
@@ -102,7 +102,7 @@ describe Masamune::DataPlan::Set do
102
102
  fs.touch!('/log/20130101.random_1.log', '/log/20130101.random_2.log')
103
103
  fs.touch!('/log/20130102.random_1.log', '/log/20130102.random_2.log')
104
104
  end
105
- it { expect(existing.size).to eq(2) }
105
+ it { expect(existing.count).to eq(2) }
106
106
  it { expect(existing).to include '/log/20130101.random_1.log' }
107
107
  it { expect(existing).to include '/log/20130102.random_1.log' }
108
108
  end
@@ -119,7 +119,7 @@ describe Masamune::DataPlan::Set do
119
119
  before do
120
120
  fs.touch!('/log/20130101.random_1.log', '/log/20130101.random_2.log')
121
121
  end
122
- it { expect(existing.size).to eq(2) }
122
+ it { expect(existing.count).to eq(2) }
123
123
  it { expect(existing).to include '/log/20130101.random_1.log' }
124
124
  it { expect(existing).to include '/log/20130101.random_2.log' }
125
125
  end
@@ -129,7 +129,7 @@ describe Masamune::DataPlan::Set do
129
129
  fs.touch!('/log/20130101.random_1.log', '/log/20130101.random_2.log')
130
130
  fs.touch!('/log/20130102.random_1.log', '/log/20130102.random_2.log')
131
131
  end
132
- it { expect(existing.size).to eq(4) }
132
+ it { expect(existing.count).to eq(4) }
133
133
  it { expect(existing).to include '/log/20130101.random_1.log' }
134
134
  it { expect(existing).to include '/log/20130101.random_2.log' }
135
135
  it { expect(existing).to include '/log/20130102.random_1.log' }
@@ -156,7 +156,7 @@ describe Masamune::DataPlan::Set do
156
156
  allow(instance.rule).to receive(:window) { 1 }
157
157
  end
158
158
 
159
- it { expect(sources.size).to eq(3) }
159
+ it { expect(sources.count).to eq(3) }
160
160
  it { expect(sources).to include '/log/20121231.*.log' }
161
161
  it { expect(sources).to include '/log/20130101.*.log' }
162
162
  it { expect(sources).to include '/log/20130102.*.log' }
@@ -169,7 +169,7 @@ describe Masamune::DataPlan::Set do
169
169
  before do
170
170
  fs.touch!('/log/20130101.random_1.log', '/log/20130101.random_2.log')
171
171
  end
172
- it { expect(existing.size).to eq(2) }
172
+ it { expect(existing.count).to eq(2) }
173
173
  it { expect(existing).to include '/log/20130101.random_1.log' }
174
174
  it { expect(existing).to include '/log/20130101.random_2.log' }
175
175
  end
@@ -181,7 +181,7 @@ describe Masamune::DataPlan::Set do
181
181
  fs.touch!('/log/20130102.random_1.log', '/log/20130102.random_2.log')
182
182
  fs.touch!('/log/20130103.random_1.log', '/log/20130103.random_2.log')
183
183
  end
184
- it { expect(existing.size).to eq(6) }
184
+ it { expect(existing.count).to eq(6) }
185
185
  it { expect(existing).to include '/log/20121231.random_1.log' }
186
186
  it { expect(existing).to include '/log/20121231.random_2.log' }
187
187
  it { expect(existing).to include '/log/20130101.random_1.log' }
@@ -230,7 +230,7 @@ describe Masamune::DataPlan::Set do
230
230
  fs.touch!('/log/20130101.random_1.log', mtime: future_time)
231
231
  end
232
232
 
233
- it { expect(stale_targets.size).to eq(1) }
233
+ it { expect(stale_targets.count).to eq(1) }
234
234
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=01' }
235
235
  end
236
236
 
@@ -239,7 +239,7 @@ describe Masamune::DataPlan::Set do
239
239
  fs.touch!('/log/20130101.random_2.log', mtime: future_time)
240
240
  end
241
241
 
242
- it { expect(stale_targets.size).to eq(1) }
242
+ it { expect(stale_targets.count).to eq(1) }
243
243
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=01' }
244
244
  end
245
245
 
@@ -248,7 +248,7 @@ describe Masamune::DataPlan::Set do
248
248
  fs.touch!('/log/20130101.random_1.log', mtime: present_time)
249
249
  end
250
250
 
251
- it { expect(stale_targets.size).to eq(1) }
251
+ it { expect(stale_targets.count).to eq(1) }
252
252
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=01' }
253
253
  end
254
254
 
@@ -260,7 +260,7 @@ describe Masamune::DataPlan::Set do
260
260
  fs.touch!('/log/20130103.random_2.log', mtime: future_time)
261
261
  end
262
262
 
263
- it { expect(stale_targets.size).to eq(3) }
263
+ it { expect(stale_targets.count).to eq(3) }
264
264
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=01' }
265
265
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=02' }
266
266
  it { expect(stale_targets).to include '/table/y=2013/m=01/d=03' }
@@ -288,7 +288,7 @@ describe Masamune::DataPlan::Set do
288
288
  context 'when :day' do
289
289
  let(:grain) { :day }
290
290
  it 'has 6 items' do
291
- expect(subject.size).to eq(6)
291
+ expect(subject.count).to eq(6)
292
292
  end
293
293
  it { is_expected.to include '/table/y=2012/m=12/d=29' }
294
294
  it { is_expected.to include '/table/y=2012/m=12/d=30' }
@@ -301,7 +301,7 @@ describe Masamune::DataPlan::Set do
301
301
  context 'when :month' do
302
302
  let(:grain) { :month }
303
303
  it 'has 3 items' do
304
- expect(subject.size).to eq(3)
304
+ expect(subject.count).to eq(3)
305
305
  end
306
306
  it { is_expected.to include '/table/y=2012/m=12' }
307
307
  it { is_expected.to include '/table/y=2013/m=01' }
@@ -311,7 +311,7 @@ describe Masamune::DataPlan::Set do
311
311
  context 'when :year' do
312
312
  let(:grain) { :year }
313
313
  it 'has 2 items' do
314
- expect(subject.size).to eq(2)
314
+ expect(subject.count).to eq(2)
315
315
  end
316
316
  it { is_expected.to include '/table/y=2012' }
317
317
  it { is_expected.to include '/table/y=2013' }
@@ -366,7 +366,7 @@ describe Masamune::DataPlan::Set do
366
366
  end
367
367
 
368
368
  context 'when all incomplete' do
369
- it { expect(incomplete.size).to eq(2) }
369
+ it { expect(incomplete.count).to eq(2) }
370
370
  it { expect(incomplete).to include '/table/y=2014/m=01' }
371
371
  it { expect(incomplete).to include '/table/y=2014/m=02' }
372
372
  end
@@ -378,7 +378,7 @@ describe Masamune::DataPlan::Set do
378
378
  end
379
379
  end
380
380
 
381
- it { expect(incomplete.size).to eq(1) }
381
+ it { expect(incomplete.count).to eq(1) }
382
382
  it { expect(incomplete).to include '/table/y=2014/m=02' }
383
383
  end
384
384
 
@@ -390,7 +390,7 @@ describe Masamune::DataPlan::Set do
390
390
  end
391
391
  end
392
392
 
393
- it { expect(incomplete.size).to eq(0) }
393
+ it { expect(incomplete.count).to eq(0) }
394
394
  end
395
395
  end
396
396
 
@@ -432,7 +432,7 @@ describe Masamune::DataPlan::Set do
432
432
  it 'actionable is equivalent to stale' do
433
433
  expect(actionable).to eq(instance.stale)
434
434
  end
435
- it { expect(updateable.size).to eq(3) }
435
+ it { expect(updateable.count).to eq(3) }
436
436
  it { expect(updateable).to include '/table/y=2013/m=01/d=01' }
437
437
  it { expect(updateable).to include '/table/y=2013/m=01/d=02' }
438
438
  it { expect(updateable).to include '/table/y=2013/m=01/d=03' }
@@ -464,7 +464,7 @@ describe Masamune::DataPlan::Set do
464
464
  it 'actionable is equivalent to missing' do
465
465
  expect(actionable).to eq(instance.missing)
466
466
  end
467
- it { expect(updateable.size).to eq(1) }
467
+ it { expect(updateable.count).to eq(1) }
468
468
  it { expect(updateable).to include '/table/y=2013/m=01/d=01' }
469
469
  end
470
470
 
@@ -477,7 +477,7 @@ describe Masamune::DataPlan::Set do
477
477
  it 'actionable is equivalent to missing' do
478
478
  expect(actionable).to eq(instance.missing)
479
479
  end
480
- it { expect(updateable.size).to eq(3) }
480
+ it { expect(updateable.count).to eq(3) }
481
481
  it { expect(updateable).to include '/table/y=2013/m=01/d=01' }
482
482
  it { expect(updateable).to include '/table/y=2013/m=01/d=02' }
483
483
  it { expect(updateable).to include '/table/y=2013/m=01/d=03' }
@@ -495,9 +495,9 @@ describe Masamune::DataPlan::Set do
495
495
 
496
496
  context 'when sources are missing' do
497
497
  it 'should chain expectedly' do
498
- expect(instance.targets.size).to eq(2)
499
- expect(instance.targets.sources.existing.size).to eq(0)
500
- expect(instance.targets.sources.existing.targets.size).to eq(0)
498
+ expect(instance.targets.count).to eq(2)
499
+ expect(instance.targets.sources.existing.count).to eq(0)
500
+ expect(instance.targets.sources.existing.targets.count).to eq(0)
501
501
  end
502
502
  end
503
503
 
@@ -507,10 +507,10 @@ describe Masamune::DataPlan::Set do
507
507
  end
508
508
 
509
509
  it 'should chain expectedly' do
510
- expect(instance.targets.size).to eq(2)
511
- expect(instance.targets.sources.existing.size).to eq(4)
512
- expect(instance.targets.sources.existing.targets.size).to eq(2)
513
- expect(instance.targets.sources.existing.targets.sources.existing.size).to eq(4)
510
+ expect(instance.targets.count).to eq(2)
511
+ expect(instance.targets.sources.existing.count).to eq(4)
512
+ expect(instance.targets.sources.existing.targets.count).to eq(2)
513
+ expect(instance.targets.sources.existing.targets.sources.existing.count).to eq(4)
514
514
  end
515
515
  end
516
516
  end
@@ -590,20 +590,20 @@ shared_examples_for 'Filesystem' do
590
590
 
591
591
  context 'local no matches' do
592
592
  let(:pattern) { File.join(new_dir, '*') }
593
- it { is_expected.to be_empty }
593
+ it { expect(subject.count).to eq(0) }
594
594
  it { expect { |b| instance.glob(pattern, &b) }.to_not yield_control }
595
595
  end
596
596
 
597
597
  context 'local one matches' do
598
598
  let(:pattern) { File.join(File.dirname(old_file), '*') }
599
- it { is_expected.not_to be_empty }
599
+ it { expect(subject.count).to eq(1) }
600
600
  it { expect { |b| instance.glob(pattern, &b) }.to yield_with_args(old_file) }
601
601
  end
602
602
 
603
603
  context 'local one matches (recursive)' do
604
604
  let(:pattern) { File.join(tmp_dir, '*') }
605
605
  it 'has 2 items' do
606
- expect(subject.size).to eq(2)
606
+ expect(subject.count).to eq(2)
607
607
  end
608
608
  it { is_expected.to include old_dir }
609
609
  it { is_expected.to include old_file }
@@ -613,7 +613,7 @@ shared_examples_for 'Filesystem' do
613
613
  context 'local one matches (with suffix)' do
614
614
  let(:pattern) { File.join(File.dirname(old_file), '*.txt') }
615
615
  it 'has 1 item' do
616
- expect(subject.size).to eq(1)
616
+ expect(subject.count).to eq(1)
617
617
  end
618
618
  it { is_expected.to include old_file }
619
619
  it { expect { |b| instance.glob(pattern, &b) }.to yield_with_args(old_file) }
@@ -629,7 +629,7 @@ shared_examples_for 'Filesystem' do
629
629
  and_yield('')
630
630
  end
631
631
  let(:pattern) { File.join(new_dir, '*') }
632
- it { is_expected.to be_empty }
632
+ it { expect(subject.count).to eq(0) }
633
633
  it { expect { |b| instance.glob('file://' + pattern, &b) }.to_not yield_control }
634
634
  end
635
635
 
@@ -643,7 +643,7 @@ shared_examples_for 'Filesystem' do
643
643
  and_yield("drwxrwxrwt - root wheel 68 2015-02-24 12:09 #{old_file}")
644
644
  end
645
645
  let(:pattern) { File.join(File.dirname(old_file), '*') }
646
- it { is_expected.not_to be_empty }
646
+ it { expect(subject.count).to eq(1) }
647
647
  it { expect { |b| instance.glob('file://' + pattern, &b) }.to yield_with_args('file://' + old_file) }
648
648
  end
649
649
 
@@ -655,7 +655,7 @@ shared_examples_for 'Filesystem' do
655
655
  expect(filesystem).to receive(:s3cmd).with('ls', '--recursive', "s3://bucket/dir/*", safe: true).at_most(:once)
656
656
  end
657
657
 
658
- it { is_expected.to be_empty }
658
+ it { expect(subject.count).to eq(0) }
659
659
  end
660
660
 
661
661
  context 's3 no matches with implicit glob results' do
@@ -667,7 +667,7 @@ shared_examples_for 'Filesystem' do
667
667
  and_yield(%q(2013-05-24 18:53 2912 s3://bucket/dir/02.txt))
668
668
  end
669
669
 
670
- it { is_expected.to be_empty }
670
+ it { expect(subject.count).to eq(0) }
671
671
  end
672
672
 
673
673
  context 's3 one matches' do
@@ -652,6 +652,16 @@ describe Masamune::Schema::Column do
652
652
 
653
653
  context 'by default' do
654
654
  let(:column) { described_class.new id: 'name', type: :string }
655
+ it { is_expected.to eq(true) }
656
+ end
657
+
658
+ context 'when :null true' do
659
+ let(:column) { described_class.new id: 'name', type: :string, null: true }
660
+ it { is_expected.to eq(false) }
661
+ end
662
+
663
+ context 'when column has default' do
664
+ let(:column) { described_class.new id: 'name', type: :string, default: 'missing' }
655
665
  it { is_expected.to eq(false) }
656
666
  end
657
667
 
@@ -82,6 +82,9 @@ describe Masamune::Schema::Table do
82
82
  end
83
83
 
84
84
  it { expect(table.name).to eq('user_table') }
85
+ it { expect(table.unique_columns).to include :tenant_id }
86
+ it { expect(table.unique_columns).to include :user_id }
87
+ it { expect(table.stage_table.unique_columns).to be_empty }
85
88
  end
86
89
 
87
90
  context 'with enum column' do
@@ -23,8 +23,6 @@
23
23
  require 'delegate'
24
24
 
25
25
  class Masamune::MockFilesystem < Delegator
26
- include Masamune::Accumulate
27
-
28
26
  def initialize
29
27
  @filesystem = Masamune::Filesystem.new
30
28
  @filesystem.add_path :root_dir, File.expand_path('../../../', __FILE__)
@@ -42,19 +40,20 @@ class Masamune::MockFilesystem < Delegator
42
40
  @files.keys.include?(file)
43
41
  end
44
42
 
45
- def glob(pattern, &block)
43
+ def glob(pattern)
44
+ return Set.new(to_enum(:glob, pattern)) unless block_given?
46
45
  file_regexp = glob_to_regexp(pattern)
47
46
  @files.keys.each do |name|
48
47
  yield name if name =~ file_regexp
49
48
  end
50
49
  end
51
- method_accumulate :glob
52
50
 
53
51
  def glob_sort(pattern, options = {})
54
52
  glob(pattern)
55
53
  end
56
54
 
57
- def glob_stat(pattern, &block)
55
+ def glob_stat(pattern)
56
+ return Set.new(to_enum(:glob_stat, pattern)) unless block_given?
58
57
  file_regexp = glob_to_regexp(pattern, recursive: true)
59
58
  @files.each do |name, stat|
60
59
  yield stat if name =~ file_regexp
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: masamune
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Andrews
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-09 00:00:00.000000000 Z
11
+ date: 2015-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: parallel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: pry
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -183,7 +197,6 @@ files:
183
197
  - bin/masamune-psql
184
198
  - bin/masamune-shell
185
199
  - lib/masamune.rb
186
- - lib/masamune/accumulate.rb
187
200
  - lib/masamune/actions.rb
188
201
  - lib/masamune/actions/data_flow.rb
189
202
  - lib/masamune/actions/date_parse.rb
@@ -193,6 +206,7 @@ files:
193
206
  - lib/masamune/actions/hadoop_filesystem.rb
194
207
  - lib/masamune/actions/hadoop_streaming.rb
195
208
  - lib/masamune/actions/hive.rb
209
+ - lib/masamune/actions/invoke_parallel.rb
196
210
  - lib/masamune/actions/postgres.rb
197
211
  - lib/masamune/actions/postgres_admin.rb
198
212
  - lib/masamune/actions/s3cmd.rb
@@ -291,6 +305,7 @@ files:
291
305
  - spec/masamune/actions/hadoop_filesystem_spec.rb
292
306
  - spec/masamune/actions/hadoop_streaming_spec.rb
293
307
  - spec/masamune/actions/hive_spec.rb
308
+ - spec/masamune/actions/invoke_parallel_spec.rb
294
309
  - spec/masamune/actions/postgres_admin_spec.rb
295
310
  - spec/masamune/actions/postgres_spec.rb
296
311
  - spec/masamune/actions/s3cmd_spec.rb
@@ -393,6 +408,7 @@ test_files:
393
408
  - spec/masamune/actions/hadoop_filesystem_spec.rb
394
409
  - spec/masamune/actions/hadoop_streaming_spec.rb
395
410
  - spec/masamune/actions/hive_spec.rb
411
+ - spec/masamune/actions/invoke_parallel_spec.rb
396
412
  - spec/masamune/actions/postgres_admin_spec.rb
397
413
  - spec/masamune/actions/postgres_spec.rb
398
414
  - spec/masamune/actions/s3cmd_spec.rb