bricolage 5.30.0 → 6.0.0beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd34b3e85926b0f85f76d05415341066025a5117c77d1b3b644ac9ce3358f0d2
4
- data.tar.gz: 067eed98758a6de824a0ab9133830a637f3cc3790e26974a946d39eaa54032fc
3
+ metadata.gz: 97c5b2453966ea1464172b34c23e7ba3e6f44c715d745f17f07ebbddf361c437
4
+ data.tar.gz: 6044bb40e6dabedd831dc90dd4f61e8fc6614bd4b0885233ba2362b1e1c051b8
5
5
  SHA512:
6
- metadata.gz: d4601475e0c9cbd70e2f54a68ddb06e417288fc52230b3ac553ff8a3f1ef79dafa6d83a8e543470d9c5cfd9d040a98df66217e2cd7a39aec6e1cd4b342640b0f
7
- data.tar.gz: aa8d9bf658e8f36a84eacd18c3d680a18178d7612c995fbdbb8dfd695720331f953607614c1ad2d467c8a211e6fd6bf29f853257455b72700fe93172846375e3
6
+ metadata.gz: 421dd79e8da48871f3a6694f4ca8bc490280acd4dfad11f10c3165854e7c6a33b76b374a5939805304d2b53307c40c048c28c266f57b54514bf9d81226c21304
7
+ data.tar.gz: 95c5f97fdf7851d6def0e87713acf3eaae136ba8d2e5971b85b10cbd7b04b95f41e28925eac4912f63d2359e818e94d09c3fa905c8947e9b38747c2c71417f08
data/.gitignore CHANGED
@@ -17,6 +17,4 @@ tmp
17
17
  _yardoc
18
18
  doc/
19
19
 
20
- /dev
21
- /test/home/config
22
- Gemfile.lock
20
+ /Gemfile.lock
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0
data/README.md CHANGED
@@ -11,6 +11,9 @@ See LICENSES file for details.
11
11
 
12
12
  ## Running Test
13
13
 
14
+ Create bricolage_test database and user on your local machine.
15
+ See config/test/datasource.yml for details. Then type:
16
+
14
17
  % rake test
15
18
 
16
19
  ## How to use
data/RELEASE.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Bricolage Release Note
2
2
 
3
+ ## version 6.0.0 beta 5
4
+
5
+ - [new] Supports Ruby 3.0.
6
+
7
+ ## version 6.0.0 beta 4
8
+
9
+ - [fix] rebuild-rename did not work with ALTER RENAME syntax error.
10
+
11
+ ## version 6.0.0 beta 3
12
+
13
+ - [new] Upgrades pg from 0.18 to 1.2.
14
+
15
+ ## version 6.0.0 beta 2
16
+
17
+ - [fix] Always transmit error messages from jobs in the jobnet.
18
+
19
+ ## version 6.0.0 beta 1
20
+
21
+ - [new] Introduces database queue. Database queue saves job states in the PostgreSQL instance, we now can run bricolage on container environment.
22
+ - [new] Default log level is DEBUG on development environment, INFO on production environment.
23
+ - [new] Only updating query is logged as INFO level. Read-only queries are logged in DEBUG level.
24
+
3
25
  ## version 5.30.0
4
26
 
5
27
  - [new] streaming_load: new option --ctl-ds to change S3 data source for metadata files.
data/Rakefile CHANGED
@@ -1,3 +1,13 @@
1
+ require 'rake/testtask'
2
+
1
3
  task :test do
2
- load "#{__dir__}/test/all.rb"
4
+ desc 'Run tests'
5
+ Rake::TestTask.new do |t|
6
+ # To run test cases of specific file(s), Use:
7
+ # % rake test TEST=test/test_specified_path.rb
8
+ t.libs << "test"
9
+ t.test_files = Dir["test/**/test_*.rb"]
10
+ t.verbose = true
11
+ t.warning = true
12
+ end
3
13
  end
data/bricolage.gemspec CHANGED
@@ -16,11 +16,13 @@ Gem::Specification.new do |s|
16
16
  s.executables = s.files.grep(%r{bin/}).map {|path| File.basename(path) }
17
17
  s.require_path = 'lib'
18
18
 
19
- s.required_ruby_version = '>= 2.0.0'
20
- s.add_dependency 'pg', '~> 0.18.0'
21
- s.add_dependency 'aws-sdk-s3', '~> 1'
22
- s.add_dependency 'aws-sdk-sns', '~> 1'
23
- s.add_development_dependency 'test-unit'
24
- s.add_development_dependency 'rake'
25
- s.add_development_dependency 'mocha'
19
+ s.required_ruby_version = '>= 2.4.0'
20
+ s.add_dependency 'pg', '~> 1.2.3'
21
+ s.add_dependency 'aws-sdk-s3', '~> 1.64'
22
+ s.add_dependency 'aws-sdk-sns', '~> 1.23'
23
+ s.add_dependency 'nokogiri' # aws-sdk-core requires this
24
+ s.add_development_dependency 'test-unit', '~> 3.3'
25
+ s.add_development_dependency 'rake', '~> 13.0'
26
+ s.add_development_dependency 'mocha', '~> 1.11'
27
+ s.add_development_dependency 'pry-byebug', '~> 3.9'
26
28
  end
@@ -0,0 +1,9 @@
1
+ test_db:
2
+ type: psql
3
+ host: localhost
4
+ port: 5432
5
+ database: bricolage_test
6
+ username: bricolage_test
7
+ password: bricolage_test
8
+ encoding: utf8
9
+ sql_log_level: DEBUG
@@ -21,18 +21,18 @@ JobClass.define('rebuild-rename') {
21
21
 
22
22
  script {|params, script|
23
23
  script.task(params['data-source']) {|task|
24
- dest_table = '$dest_table'
25
- prev_table = '${dest_table}_old'
26
- work_table = '${dest_table}_wk'
24
+ dest_table = params['dest-table']
25
+ prev_table = TableSpec.parse("#{dest_table}_old")
26
+ work_table = TableSpec.parse("#{dest_table}_wk")
27
27
 
28
28
  task.transaction {
29
29
  # CREATE
30
30
  task.drop_force prev_table
31
31
  task.drop_force work_table
32
- task.exec params['table-def'].replace(/\$\{?dest_table\}?\b/, work_table)
32
+ task.exec params['table-def'].replace(/\$\{?dest_table\}?\b/, work_table.to_s)
33
33
 
34
34
  # INSERT
35
- task.exec params['sql-file'].replace(/\$\{?dest_table\}?\b/, work_table)
35
+ task.exec params['sql-file'].replace(/\$\{?dest_table\}?\b/, work_table.to_s)
36
36
 
37
37
  # GRANT
38
38
  task.grant_if params['grant'], work_table
@@ -45,8 +45,8 @@ JobClass.define('rebuild-rename') {
45
45
  # RENAME
46
46
  task.transaction {
47
47
  task.create_dummy_table dest_table
48
- task.rename_table dest_table, prev_table
49
- task.rename_table work_table, dest_table
48
+ task.rename_table dest_table, prev_table.name
49
+ task.rename_table work_table, dest_table.name
50
50
  }
51
51
  }
52
52
  }
@@ -144,7 +144,7 @@ class StreamingLoadJobClass < RubyJobClass
144
144
  @ds.open {|conn|
145
145
  execute_update conn, "delete #{log_table_wk};"
146
146
  execute_update conn, load_log_copy_stmt(log_table_wk, log_url, @src.credential_string)
147
- loaded, not_loaded = partition_loaded_objects(conn, objects, log_table_wk)
147
+ loaded, _not_loaded = partition_loaded_objects(conn, objects, log_table_wk)
148
148
  loaded.each do |obj|
149
149
  obj.dequeue(force: true, noop: @noop)
150
150
  end
@@ -212,7 +212,7 @@ class StreamingLoadJobClass < RubyJobClass
212
212
  end
213
213
  @logger.info "creating manifest: #{manifest_name}"
214
214
  json = make_manifest_json(objects)
215
- @logger.info "manifest:\n" + json
215
+ @logger.debug "manifest:\n" + json
216
216
  url = @src.put_control_file(manifest_name, json, noop: @noop)
217
217
  yield url
218
218
  @src.remove_control_file(File.basename(url), noop: @noop) unless @keep_ctl
@@ -241,7 +241,7 @@ class StreamingLoadJobClass < RubyJobClass
241
241
  log_name = "load_log-#{@job_process_id}.csv"
242
242
  @logger.info "creating tmp load log: #{log_name}"
243
243
  csv = make_load_log_csv(objects)
244
- @logger.info "load_log:\n" + csv
244
+ @logger.debug "load_log:\n" + csv
245
245
  url = @src.put_control_file(log_name, csv, noop: @noop)
246
246
  begin
247
247
  yield url
@@ -39,7 +39,7 @@ module Bricolage
39
39
  @hooks.run_before_option_parsing_hooks(opts)
40
40
  opts.parse!(ARGV)
41
41
 
42
- @ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment, global_variables: opts.global_variables)
42
+ @ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment, option_variables: opts.option_variables)
43
43
  opts.merge_saved_options(@ctx.load_system_options)
44
44
 
45
45
  if opts.dump_options?
@@ -294,7 +294,7 @@ module Bricolage
294
294
  @job_file = nil
295
295
  @environment = nil
296
296
  @home = nil
297
- @global_variables = Variables.new
297
+ @option_variables = Variables.new
298
298
  @dry_run = false
299
299
  @explain = false
300
300
  @list_global_variables = false
@@ -351,9 +351,9 @@ Global Options:
351
351
  parser.on('-r', '--require=FEATURE', 'Requires ruby library.') {|feature|
352
352
  require feature
353
353
  }
354
- parser.on('-v', '--variable=NAME=VALUE', 'Set global variable (is different from job-level -v !!).') {|name_value|
354
+ parser.on('-v', '--variable=NAME=VALUE', 'Set option variable.') {|name_value|
355
355
  name, value = name_value.split('=', 2)
356
- @global_variables[name] = value
356
+ @option_variables[name] = value
357
357
  }
358
358
  parser.on('--dump-options', 'Shows option parsing result and quit.') {
359
359
  @dump_options = true
@@ -401,7 +401,7 @@ Global Options:
401
401
  @dump_options
402
402
  end
403
403
 
404
- attr_reader :global_variables
404
+ attr_reader :option_variables
405
405
 
406
406
  def list_global_variables?
407
407
  @list_global_variables
@@ -32,7 +32,7 @@ module Bricolage
32
32
  end
33
33
 
34
34
  def eruby(text, path)
35
- erb = ERB.new(text, nil, '%-')
35
+ erb = ERB.new(text, trim_mode: '%-')
36
36
  erb.filename = path.to_s
37
37
  push_base_dir(path) {
38
38
  erb.result(binding())
@@ -19,7 +19,7 @@ module Bricolage
19
19
  FileSystem.home_path(opt_path)
20
20
  end
21
21
 
22
- def Context.for_application(home_path = nil, job_path_0 = nil, job_path: nil, environment: nil, global_variables: nil, logger: nil)
22
+ def Context.for_application(home_path = nil, job_path_0 = nil, job_path: nil, environment: nil, option_variables: nil, logger: nil)
23
23
  env = environment(environment)
24
24
  if (job_path ||= job_path_0)
25
25
  fs = FileSystem.for_job_path(job_path, env)
@@ -29,24 +29,28 @@ module Bricolage
29
29
  else
30
30
  fs = FileSystem.for_options(home_path, env)
31
31
  end
32
- load(fs, env, global_variables: global_variables, logger: logger)
32
+ load(fs, env, option_variables: option_variables, logger: logger)
33
33
  end
34
34
 
35
- def Context.load(fs, env, global_variables: nil, data_sources: nil, logger: nil)
36
- new(fs, env, global_variables: global_variables, logger: logger).tap {|ctx|
35
+ def Context.load(fs, env, option_variables: nil, data_sources: nil, logger: nil)
36
+ new(fs, env, option_variables: option_variables, logger: logger).tap {|ctx|
37
37
  ctx.load_configurations
38
38
  }
39
39
  end
40
40
  private_class_method :load
41
41
 
42
- def initialize(fs, env, global_variables: nil, data_sources: nil, logger: nil)
43
- @logger = logger || Logger.default
42
+ def initialize(fs, env, option_variables: nil, data_sources: nil, logger: nil)
43
+ @logger = logger || default_logger(env)
44
44
  @filesystem = fs
45
45
  @environment = env
46
- @opt_global_variables = global_variables || Variables.new
46
+ @option_variables = option_variables || Variables.new
47
47
  @data_sources = data_sources
48
48
  end
49
49
 
50
+ private def default_logger(env)
51
+ Logger.new(device: $stderr, level: Logger::INFO)
52
+ end
53
+
50
54
  def load_configurations
51
55
  @filesystem.config_pathes('prelude.rb').each do |path|
52
56
  EmbeddedCodeAPI.module_eval(File.read(path), path.to_s, 1) if path.exist?
@@ -56,6 +60,7 @@ module Bricolage
56
60
 
57
61
  attr_reader :environment
58
62
  attr_reader :logger
63
+ attr_reader :option_variables
59
64
 
60
65
  def get_data_source(type, name)
61
66
  @data_sources.get(type, name)
@@ -63,7 +68,7 @@ module Bricolage
63
68
 
64
69
  def subsystem(id)
65
70
  self.class.new(@filesystem.subsystem(id), @environment,
66
- global_variables: @opt_global_variables,
71
+ option_variables: @option_variables,
67
72
  data_sources: @data_sources,
68
73
  logger: @logger)
69
74
  end
@@ -102,7 +107,6 @@ module Bricolage
102
107
  Variables.union(
103
108
  builtin_variables,
104
109
  load_global_variables,
105
- @opt_global_variables
106
110
  )
107
111
  end
108
112
 
@@ -130,8 +134,11 @@ module Bricolage
130
134
 
131
135
  def load_variables(path)
132
136
  Variables.define {|vars|
133
- @filesystem.config_file_loader.load_yaml(path).each do |name, value|
134
- vars[name] = value
137
+ kvs = @filesystem.config_file_loader.load_yaml(path)
138
+ if kvs
139
+ kvs.each do |name, value|
140
+ vars[name] = value
141
+ end
135
142
  end
136
143
  }
137
144
  end
@@ -0,0 +1,184 @@
1
+ require 'bricolage/exception'
2
+
3
+ module Bricolage
4
+ module DAO
5
+
6
+ class Job
7
+ include SQLUtils
8
+
9
+ Attributes = Struct.new(:id, :subsystem, :job_name, :jobnet_id, :executor_id, keyword_init: true)
10
+
11
+ def Job.for_record(r)
12
+ Attributes.new(
13
+ id: r['job_id']&.to_i,
14
+ subsystem: r['subsystem'],
15
+ job_name: r['job_name'],
16
+ jobnet_id: r['jobnet_id']&.to_i,
17
+ executor_id: r['executor_id']
18
+ )
19
+ end
20
+
21
+ def initialize(datasource)
22
+ @datasource = datasource
23
+ end
24
+
25
+ private def connect(&block)
26
+ @datasource.open_shared_connection(&block)
27
+ end
28
+
29
+ def find_or_create(jobnet_id, job_ref)
30
+ connect {|conn|
31
+ job = find(conn, jobnet_id, job_ref) # optimize the most frequent case
32
+ if job
33
+ job
34
+ else
35
+ begin
36
+ create(conn, jobnet_id, job_ref)
37
+ rescue UniqueViolationException
38
+ find(conn, jobnet_id, job_ref) or raise "[BUG] Could not find/create job record: jobnet_id=#{jobnet_id}, ref=#{job_ref}"
39
+ end
40
+ end
41
+ }
42
+ end
43
+
44
+ private def find(conn, jobnet_id, job_ref)
45
+ record = conn.query_row(<<~EndSQL)
46
+ select
47
+ "job_id"
48
+ , "subsystem"
49
+ , "job_name"
50
+ , "executor_id"
51
+ , jobnet_id
52
+ from
53
+ jobs
54
+ where
55
+ jobnet_id = #{jobnet_id}
56
+ and "subsystem" = #{s job_ref.subsystem}
57
+ and "job_name" = #{s job_ref.name}
58
+ ;
59
+ EndSQL
60
+
61
+ if record
62
+ Job.for_record(record)
63
+ else
64
+ nil
65
+ end
66
+ end
67
+
68
+ private def create(conn, jobnet_id, job_ref)
69
+ record = conn.query_row(<<~EndSQL)
70
+ insert into jobs
71
+ ( "subsystem"
72
+ , "job_name"
73
+ , jobnet_id
74
+ )
75
+ values
76
+ ( #{s job_ref.subsystem}
77
+ , #{s job_ref.name}
78
+ , #{jobnet_id}
79
+ )
80
+ returning "job_id", "subsystem", "job_name", jobnet_id
81
+ ;
82
+ EndSQL
83
+
84
+ Job.for_record(record)
85
+ end
86
+
87
+ def locked?(job_ids)
88
+ count = connect {|conn|
89
+ conn.query_value(<<~EndSQL)
90
+ select
91
+ count(job_id)
92
+ from
93
+ jobs
94
+ where
95
+ job_id in (#{job_ids.join(',')})
96
+ and executor_id is not null
97
+ ;
98
+ EndSQL
99
+ }
100
+
101
+ count.to_i > 0
102
+ end
103
+
104
+ def locked_jobs(jobnet_id)
105
+ records = connect {|conn|
106
+ conn.query_rows(<<~EndSQL)
107
+ select
108
+ "job_id"
109
+ , "subsystem"
110
+ , "job_name"
111
+ , jobnet_id
112
+ , "executor_id"
113
+ from
114
+ jobs
115
+ where
116
+ jobnet_id = #{jobnet_id}
117
+ and executor_id is not null
118
+ ;
119
+ EndSQL
120
+ }
121
+
122
+ if records.empty?
123
+ []
124
+ else
125
+ record.map {|r| Job.for_record(r) }
126
+ end
127
+ end
128
+
129
+ def lock(job_id, executor_id)
130
+ records = connect {|conn|
131
+ conn.execute_update(<<~EndSQL)
132
+ update jobs
133
+ set
134
+ executor_id = #{s executor_id}
135
+ where
136
+ job_id = #{job_id}
137
+ and executor_id is null
138
+ returning job_id
139
+ ;
140
+ EndSQL
141
+ }
142
+
143
+ if records.empty?
144
+ raise DoubleLockError, "Could not lock job: job_id=#{job_id}"
145
+ end
146
+ end
147
+
148
+ # Unlock the job.
149
+ # Returns true if successfully unlocked, otherwise false.
150
+ # FIXME: raise an exception on failing unlock?
151
+ def unlock(job_id, executor_id)
152
+ records = connect {|conn|
153
+ conn.execute_update(<<~EndSQL)
154
+ update jobs
155
+ set
156
+ executor_id = null
157
+ where
158
+ job_id = #{job_id}
159
+ and executor_id = #{s executor_id}
160
+ returning job_id
161
+ ;
162
+ EndSQL
163
+ }
164
+
165
+ not records.empty?
166
+ end
167
+
168
+ def clear_lock_all(jobnet_id)
169
+ connect {|conn|
170
+ conn.execute_update(<<~EndSQL)
171
+ update jobs
172
+ set
173
+ executor_id = null
174
+ where
175
+ jobnet_id = #{jobnet_id}
176
+ ;
177
+ EndSQL
178
+ }
179
+ end
180
+
181
+ end # class Job
182
+
183
+ end
184
+ end