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,260 @@
1
+ require 'bricolage/context'
2
+ require 'bricolage/job'
3
+ require 'bricolage/jobclass'
4
+ require 'bricolage/jobresult'
5
+ require 'bricolage/variables'
6
+ require 'bricolage/datasource'
7
+ require 'bricolage/eventhandlers'
8
+ require 'bricolage/logger'
9
+ require 'bricolage/exception'
10
+ require 'bricolage/version'
11
+ require 'pathname'
12
+ require 'optparse'
13
+
14
+ module Bricolage
15
+
16
+ class Application
17
+ def Application.main
18
+ new.main
19
+ end
20
+
21
+ def initialize
22
+ Signal.trap('PIPE', 'IGNORE')
23
+ @hooks = Bricolage
24
+ end
25
+
26
+ def main
27
+ opts = GlobalOptions.new(self)
28
+ @hooks.run_before_option_parsing_hooks(opts)
29
+ opts.parse ARGV
30
+ @ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment, global_variables: opts.global_variables)
31
+ if opts.list_global_variables?
32
+ list_variables @ctx.global_variables.resolve
33
+ exit 0
34
+ end
35
+ job = load_job(@ctx, opts)
36
+ process_job_options job, opts
37
+ job.compile
38
+ if opts.list_declarations?
39
+ list_declarations job.declarations
40
+ exit 0
41
+ end
42
+ if opts.list_variables?
43
+ list_variables job.variables
44
+ exit 0
45
+ end
46
+ if opts.dry_run?
47
+ puts job.script_source
48
+ exit 0
49
+ end
50
+ if opts.explain?
51
+ job.explain
52
+ exit 0
53
+ end
54
+
55
+ @hooks.run_before_all_jobs_hooks(BeforeAllJobsEvent.new(job.id, [job]))
56
+ @hooks.run_before_job_hooks(BeforeJobEvent.new(job))
57
+ result = job.execute
58
+ @hooks.run_after_job_hooks(AfterJobEvent.new(result))
59
+ @hooks.run_after_all_jobs_hooks(AfterAllJobsEvent.new(result.success?, [job]))
60
+ exit result.status
61
+ rescue OptionError => ex
62
+ raise if $DEBUG
63
+ usage_exit ex.message, opts.help
64
+ rescue ApplicationError => ex
65
+ raise if $DEBUG
66
+ error_exit ex.message
67
+ end
68
+
69
+ def load_job(ctx, opts)
70
+ if opts.file_mode?
71
+ Job.load_file(opts.job_file, ctx)
72
+ else
73
+ usage_exit "no job class given", opts.help if ARGV.empty?
74
+ job_class_id = ARGV.shift
75
+ Job.instantiate(nil, job_class_id, ctx)
76
+ end
77
+ rescue ParameterError => ex
78
+ raise if $DEBUG
79
+ usage_exit ex.message, opts.help
80
+ end
81
+
82
+ def process_job_options(job, opts)
83
+ parser = OptionParser.new
84
+ parser.banner = "Usage: #{program_name} #{job.class_id} [job_class_options]"
85
+ job.parsing_options {|job_opt_defs|
86
+ job_opt_defs.define_options parser
87
+ parser.on_tail('--help', 'Shows this message and quit.') {
88
+ puts parser.help
89
+ exit 0
90
+ }
91
+ parser.on_tail('--version', 'Shows program version and quit.') {
92
+ puts "#{APPLICATION_NAME} version #{VERSION}"
93
+ exit 0
94
+ }
95
+ parser.parse!
96
+ }
97
+ unless ARGV.empty?
98
+ msg = opts.file_mode? ? "--job-file and job class argument is exclusive" : "bad argument: #{ARGV.first}"
99
+ usage_exit msg, parser.help
100
+ end
101
+ rescue OptionError => ex
102
+ raise if $DEBUG
103
+ usage_exit ex.message, parser.help
104
+ end
105
+
106
+ def list_variables(vars)
107
+ vars.each_variable do |var|
108
+ puts "#{var.name}=#{var.value.inspect}"
109
+ end
110
+ end
111
+
112
+ def list_declarations(decls)
113
+ decls.each do |decl|
114
+ if decl.have_default_value?
115
+ puts "#{decl.name}\t= #{decl.default_value.inspect}"
116
+ else
117
+ puts decl.name
118
+ end
119
+ end
120
+ end
121
+
122
+ def usage_exit(msg, usage)
123
+ print_error msg
124
+ $stderr.puts usage
125
+ exit 1
126
+ end
127
+
128
+ def error_exit(msg)
129
+ print_error msg
130
+ exit 1
131
+ end
132
+
133
+ def print_error(msg)
134
+ $stderr.puts "#{program_name}: error: #{msg}"
135
+ end
136
+
137
+ def program_name
138
+ File.basename($PROGRAM_NAME, '.*')
139
+ end
140
+ end
141
+
142
+ class GlobalOptions
143
+ def initialize(app)
144
+ @app = app
145
+ @job_file = nil
146
+ @environment = nil
147
+ @home = nil
148
+ @global_variables = Variables.new
149
+ @dry_run = false
150
+ @explain = false
151
+ @list_global_variables = false
152
+ @list_variables = false
153
+ @list_declarations = false
154
+ @parser = OptionParser.new
155
+ define_options @parser
156
+ end
157
+
158
+ attr_reader :parser
159
+
160
+ def help
161
+ @parser.help
162
+ end
163
+
164
+ def define_options(parser)
165
+ parser.banner = <<-EndBanner
166
+ Synopsis:
167
+ #{@app.program_name} [global_options] JOB_CLASS [job_options]
168
+ #{@app.program_name} [global_options] --job=JOB_FILE -- [job_options]
169
+ Global Options:
170
+ EndBanner
171
+ parser.on('-f', '--job=JOB_FILE', 'Give job parameters via job file (YAML).') {|path|
172
+ @job_file = path
173
+ }
174
+ parser.on('-e', '--environment=NAME', "Sets execution environment [default: #{Context::DEFAULT_ENV}]") {|env|
175
+ @environment = env
176
+ }
177
+ parser.on('-C', '--home=PATH', 'Sets application home directory.') {|path|
178
+ @home = Pathname(path)
179
+ }
180
+ parser.on('-n', '--dry-run', 'Shows job script without executing it.') {
181
+ @dry_run = true
182
+ }
183
+ parser.on('-E', '--explain', 'Applies EXPLAIN to the SQL.') {
184
+ @explain = true
185
+ }
186
+ parser.on('--list-job-class', 'Lists job class name and (internal) class path.') {
187
+ JobClass.list.each do |name|
188
+ puts name
189
+ end
190
+ exit 0
191
+ }
192
+ parser.on('--list-global-variables', 'Lists global variables.') {
193
+ @list_global_variables = true
194
+ }
195
+ parser.on('--list-variables', 'Lists all variables.') {
196
+ @list_variables = true
197
+ }
198
+ parser.on('--list-declarations', 'Lists script variable declarations.') {
199
+ @list_declarations = true
200
+ }
201
+ parser.on('-r', '--require=FEATURE', 'Requires ruby library.') {|feature|
202
+ require feature
203
+ }
204
+ parser.on('-v', '--variable=NAME=VALUE', 'Set global variable (is different from job-level -v !!).') {|name_value|
205
+ name, value = name_value.split('=', 2)
206
+ @global_variables[name] = value
207
+ }
208
+ parser.on('--help', 'Shows this message and quit.') {
209
+ puts parser.help
210
+ exit 0
211
+ }
212
+ parser.on('--version', 'Shows program version and quit.') {
213
+ puts "#{APPLICATION_NAME} version #{VERSION}"
214
+ exit 0
215
+ }
216
+ end
217
+
218
+ def on(*args, &block)
219
+ @parser.on(*args, &block)
220
+ end
221
+
222
+ def parse(argv)
223
+ @parser.order! argv
224
+ @rest_args = argv.dup
225
+ rescue OptionParser::ParseError => ex
226
+ raise OptionError, ex.message
227
+ end
228
+
229
+ attr_reader :environment
230
+ attr_reader :home
231
+ attr_reader :global_variables
232
+
233
+ attr_reader :job_file
234
+
235
+ def file_mode?
236
+ !!@job_file
237
+ end
238
+
239
+ def dry_run?
240
+ @dry_run
241
+ end
242
+
243
+ def explain?
244
+ @explain
245
+ end
246
+
247
+ def list_global_variables?
248
+ @list_global_variables
249
+ end
250
+
251
+ def list_variables?
252
+ @list_variables
253
+ end
254
+
255
+ def list_declarations?
256
+ @list_declarations
257
+ end
258
+ end
259
+
260
+ end
@@ -0,0 +1,52 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ module Bricolage
5
+
6
+ module CommandUtils
7
+ def command(*args, env: nil)
8
+ logger.info "command: #{args.join(' ')}"
9
+ sargs = args.map {|a| a.to_s }
10
+ sargs.unshift env if env
11
+ system(*sargs)
12
+ st = $?
13
+ logger.info "status: #{st.exitstatus} (#{st})"
14
+ st
15
+ end
16
+
17
+ def make_tmpfile(content, tmpdir: Dir.tmpdir)
18
+ path = new_tmpfile_path(tmpdir)
19
+ File.open(path, 'w') {|f|
20
+ f.write content
21
+ }
22
+ yield path
23
+ ensure
24
+ FileUtils.rm_f path
25
+ end
26
+
27
+ def new_tmpfile_path(tmpdir = Dir.tmpdir)
28
+ "#{tmpdir}/#{Time.now.to_i}_#{$$}_#{'%x' % Thread.current.object_id}_#{rand(2**16)}"
29
+ end
30
+
31
+ # CLUDGE: FIXME: bricolage-jobnet command writes stderr to the file, we can find error messages from there.
32
+ # Using a temporary file or Ruby SQL driver is **MUCH** better.
33
+ def retrieve_last_match_from_stderr(re, nth = 0)
34
+ return unless $stderr.stat.file?
35
+ $stderr.flush
36
+ f = $stderr.dup
37
+ matched = nil
38
+ begin
39
+ f.seek(0)
40
+ f.each do |line|
41
+ m = line.slice(re, nth)
42
+ matched = m if m
43
+ end
44
+ ensure
45
+ f.close
46
+ end
47
+ matched = matched.to_s.strip
48
+ matched.empty? ? nil : matched
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,126 @@
1
+ require 'bricolage/sqlstatement'
2
+ require 'bricolage/resource'
3
+ require 'bricolage/exception'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require 'erb'
7
+ require 'date'
8
+
9
+ module Bricolage
10
+
11
+ class ConfigLoader
12
+ def ConfigLoader.load_eruby_yaml(path)
13
+ new(nil).load_eruby_yaml(path)
14
+ end
15
+
16
+ def initialize(app_home)
17
+ @app_home = app_home
18
+ @base_dir = Pathname('.')
19
+ end
20
+
21
+ def load_eruby(path)
22
+ eruby(read_file(path), path)
23
+ end
24
+
25
+ def load_eruby_yaml(path)
26
+ parse_yaml(load_eruby(path), path)
27
+ end
28
+
29
+ def load_yaml(path)
30
+ parse_yaml(read_file(path), path)
31
+ end
32
+
33
+ def parse_yaml(text, path)
34
+ YAML.load(text)
35
+ rescue => err
36
+ raise ParameterError, "#{path}: config file syntax error: #{err.message}"
37
+ end
38
+
39
+ def eruby(text, path)
40
+ erb = ERB.new(text, nil, '%-')
41
+ erb.filename = path.to_s
42
+ push_base_dir(path) {
43
+ erb.result(binding())
44
+ }
45
+ end
46
+
47
+ def eval_file(path)
48
+ push_base_dir(path) {
49
+ instance_eval(File.read(path), path.to_s, 1)
50
+ }
51
+ end
52
+
53
+ def read_file(path)
54
+ File.read(path)
55
+ rescue SystemCallError => err
56
+ raise ParameterError, "could not read file: #{err.message}"
57
+ end
58
+
59
+ private
60
+
61
+ def app_home
62
+ @app_home or raise ParameterError, "app_home is not given in this file"
63
+ end
64
+
65
+ def base_dir
66
+ @base_dir
67
+ end
68
+
69
+ def push_base_dir(path)
70
+ saved, @base_dir = @base_dir, Pathname(path).parent
71
+ begin
72
+ yield
73
+ ensure
74
+ @base_dir = saved
75
+ end
76
+ end
77
+ end
78
+
79
+ module EmbeddedCodeAPI
80
+ private
81
+
82
+ def user_home
83
+ Pathname(ENV['HOME'])
84
+ end
85
+
86
+ def user_home_relative_path(rel)
87
+ user_home + rel
88
+ end
89
+
90
+ def app_home_relative_path(rel)
91
+ app_home + rel
92
+ end
93
+
94
+ def relative_path(rel)
95
+ base_dir + rel
96
+ end
97
+
98
+ def read_file_if_exist(path)
99
+ return nil unless File.exist?(path)
100
+ File.read(path)
101
+ end
102
+
103
+ def date(str)
104
+ Date.parse(str)
105
+ end
106
+
107
+ def ymd(date)
108
+ date.strftime('%Y-%m-%d')
109
+ end
110
+
111
+ def attribute_tables(attr)
112
+ all_tables.select {|table| table.attributes.include?(attr) }
113
+ end
114
+
115
+ def all_tables
116
+ Dir.glob("#{app_home}/*/*.ct").map {|path|
117
+ SQLStatement.new(FileResource.new(path))
118
+ }
119
+ end
120
+ end
121
+
122
+ class ConfigLoader
123
+ include EmbeddedCodeAPI
124
+ end
125
+
126
+ end
@@ -0,0 +1,108 @@
1
+ require 'bricolage/filesystem'
2
+ require 'bricolage/datasource'
3
+ require 'bricolage/configloader'
4
+ require 'bricolage/logger'
5
+ require 'forwardable'
6
+
7
+ module Bricolage
8
+
9
+ class Context
10
+ DEFAULT_ENV = 'development'
11
+
12
+ def Context.environment(opt_env)
13
+ opt_env || ENV['BRICOLAGE_ENV'] || DEFAULT_ENV
14
+ end
15
+
16
+ def Context.for_application(home_path, job_path = nil, environment: nil, global_variables: nil, logger: nil)
17
+ env = environment(environment)
18
+ fs = FileSystem.for_option_pathes(home_path, job_path, env)
19
+ load(fs, env, global_variables: global_variables, logger: logger)
20
+ end
21
+
22
+ def Context.load(fs, env, global_variables: nil, data_sources: nil, logger: nil)
23
+ new(fs, env, global_variables: global_variables, logger: logger).tap {|ctx|
24
+ ctx.load_configurations
25
+ }
26
+ end
27
+ private_class_method :load
28
+
29
+ def initialize(fs, env, global_variables: nil, data_sources: nil, logger: nil)
30
+ @logger = logger || Logger.default
31
+ @filesystem = fs
32
+ @environment = env
33
+ @opt_global_variables = global_variables || Variables.new
34
+ @data_sources = data_sources
35
+ end
36
+
37
+ def load_configurations
38
+ @filesystem.config_pathes('prelude.rb').each do |path|
39
+ EmbeddedCodeAPI.module_eval(File.read(path)) if path.exist?
40
+ end
41
+ @data_sources = DataSourceFactory.load(self, @logger)
42
+ end
43
+
44
+ attr_reader :environment
45
+ attr_reader :logger
46
+
47
+ def get_data_source(type, name)
48
+ @data_sources.get(type, name)
49
+ end
50
+
51
+ def subsystem(id)
52
+ self.class.new(@filesystem.subsystem(id), @environment,
53
+ global_variables: @opt_global_variables,
54
+ data_sources: @data_sources,
55
+ logger: @logger)
56
+ end
57
+
58
+ extend Forwardable
59
+ def_delegators '@filesystem',
60
+ :scoped?,
61
+ :home_path,
62
+ :root_relative_path,
63
+ :config_path,
64
+ :config_pathes,
65
+ :job_dir,
66
+ :job_file,
67
+ :parameter_file,
68
+ :parameter_file_loader
69
+
70
+ #
71
+ # Variables
72
+ #
73
+
74
+ def global_variables
75
+ Variables.union(
76
+ builtin_variables,
77
+ load_global_variables,
78
+ @opt_global_variables
79
+ )
80
+ end
81
+
82
+ def builtin_variables
83
+ Variables.define {|vars|
84
+ vars['bricolage_env'] = @environment
85
+ vars['bricolage_home'] = home_path.to_s
86
+ }
87
+ end
88
+
89
+ def load_global_variables
90
+ vars_list = config_pathes(GLOBAL_VARIABLE_FILE).map {|path|
91
+ path.exist? ? load_variables(path) : nil
92
+ }
93
+ Variables.union(*vars_list.compact)
94
+ end
95
+
96
+ GLOBAL_VARIABLE_FILE = 'variable.yml'
97
+
98
+ def load_variables(path)
99
+ Variables.define {|vars|
100
+ parameter_file_loader.load_eruby_yaml(path).each do |name, value|
101
+ vars[name] = value
102
+ end
103
+ }
104
+ end
105
+ private :load_variables
106
+ end
107
+
108
+ end
@@ -0,0 +1,144 @@
1
+ require 'bricolage/script'
2
+ require 'bricolage/configloader'
3
+ require 'bricolage/exception'
4
+
5
+ module Bricolage
6
+
7
+ class DataSourceFactory
8
+ def DataSourceFactory.load(context, logger)
9
+ loader = Loader.new(context, logger)
10
+ loader.load_passwords
11
+ loader.load
12
+ end
13
+
14
+ DEFAULT_CONFIG_FILE_NAME = 'database.yml'
15
+ DEFAULT_PASSWORD_FILE_NAME = 'password.yml'
16
+
17
+ class Loader < ConfigLoader
18
+ def initialize(context, logger)
19
+ super context.home_path
20
+ @context = context
21
+ @logger = logger
22
+ @passwords = nil
23
+ end
24
+
25
+ def load_passwords(basename = DEFAULT_PASSWORD_FILE_NAME)
26
+ @context.config_pathes(basename).each do |path|
27
+ if path.exist?
28
+ @passwords = load_yaml(path)
29
+ break
30
+ end
31
+ end
32
+ end
33
+
34
+ def load(basename = DEFAULT_CONFIG_FILE_NAME)
35
+ database_yml = @context.config_pathes(basename).detect {|path| path.exist? }
36
+ raise ParameterError, "database.yml does not exist" unless database_yml
37
+ @config_dir = database_yml.parent
38
+ DataSourceFactory.new(load_eruby_yaml(database_yml), @context, @logger)
39
+ end
40
+
41
+ def password(name)
42
+ (@passwords || {})[name] or raise ParameterError, "no such password entry: #{name}"
43
+ end
44
+ end
45
+
46
+ def initialize(configs, context, logger)
47
+ @configs = configs
48
+ @context = context
49
+ @logger = logger
50
+ end
51
+
52
+ BUILTIN_TYPES = %w(generic file)
53
+
54
+ # For job classes
55
+ def get(kind, name)
56
+ if BUILTIN_TYPES.include?(kind)
57
+ return DataSource.new_for_type(kind, kind, {}, @context, @logger)
58
+ end
59
+ entry_name = name || kind
60
+ conf = config(entry_name)
61
+ type = conf.delete(:type)
62
+ DataSource.new_for_type(type, entry_name, conf, @context, @logger)
63
+ end
64
+
65
+ # Ruby API
66
+ def [](name)
67
+ if BUILTIN_TYPES.include?(name)
68
+ return DataSource.new_for_type(name, name, {}, @context, @logger)
69
+ end
70
+ conf = config(name)
71
+ type = conf.delete(:type)
72
+ DataSource.new_for_type(type, name, conf, @context, @logger)
73
+ end
74
+
75
+ private
76
+
77
+ def config(key)
78
+ ent = @configs[key] or raise ParameterError, "no such data source entry: #{key}"
79
+ canonicalize(ent)
80
+ end
81
+
82
+ def canonicalize(config)
83
+ h = {}
84
+ config.each do |k, v|
85
+ h[k.intern] = v
86
+ end
87
+ h
88
+ end
89
+ end
90
+
91
+ class DataSource
92
+ def DataSource.new_for_type(type, name, config, context, logger)
93
+ ds = get_class(type).new(**config)
94
+ ds.__send__ :initialize_base, name, context, logger
95
+ ds
96
+ rescue ArgumentError => err
97
+ # FIXME: do not rely on error message
98
+ ent = err.message.slice(/unknown keyword: (\S+)/, 1) or raise
99
+ raise ParameterError, "unknown config entry in database.yml: #{name}.#{ent}"
100
+ end
101
+
102
+ CLASSES = {}
103
+
104
+ class << self
105
+ private
106
+
107
+ def declare_type(type)
108
+ CLASSES[type.to_s] = self
109
+ end
110
+ end
111
+
112
+ def DataSource.get_class(type)
113
+ unless CLASSES[type.to_s]
114
+ begin
115
+ require "bricolage/#{type}datasource"
116
+ rescue LoadError
117
+ raise ParameterError, "no such SQL client type: #{type}"
118
+ end
119
+ raise FatalError, "DataSource class does not exist: #{type}" unless CLASSES[type.to_s]
120
+ end
121
+ CLASSES[type.to_s]
122
+ end
123
+
124
+ def initialize_base(name, context, logger)
125
+ @name = name
126
+ @context = context
127
+ @logger = logger
128
+ end
129
+ private :initialize_base
130
+
131
+ attr_reader :name
132
+ attr_reader :context
133
+ attr_reader :logger
134
+
135
+ def open
136
+ yield nil
137
+ end
138
+
139
+ def open_for_batch(&block)
140
+ open(&block)
141
+ end
142
+ end
143
+
144
+ end