bricolage 5.30.0 → 6.0.0beta5

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
  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