marty 2.5.2 → 2.5.4
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 +5 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +11 -589
- data/Gemfile +9 -9
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -3
- data/app/components/marty/api_auth_view.rb +3 -3
- data/app/components/marty/api_config_view.rb +8 -8
- data/app/components/marty/api_log_view.rb +16 -20
- data/app/components/marty/auth_app.rb +6 -6
- data/app/components/marty/base_rule_view.rb +27 -19
- data/app/components/marty/config_view.rb +12 -9
- data/app/components/marty/data_grid_view.rb +26 -26
- data/app/components/marty/delorean_rule_view.rb +0 -1
- data/app/components/marty/event_view.rb +27 -27
- data/app/components/marty/extras/layout.rb +26 -26
- data/app/components/marty/extras/misc.rb +2 -2
- data/app/components/marty/grid.rb +13 -13
- data/app/components/marty/grid_append_only.rb +0 -1
- data/app/components/marty/import_type_view.rb +13 -13
- data/app/components/marty/import_view.rb +17 -16
- data/app/components/marty/log_view.rb +16 -14
- data/app/components/marty/main_auth_app.rb +59 -59
- data/app/components/marty/main_auth_app/client/main_auth_app.js +3 -3
- data/app/components/marty/mcfly_grid_panel.rb +10 -10
- data/app/components/marty/new_posting_form.rb +11 -11
- data/app/components/marty/new_posting_window.rb +0 -1
- data/app/components/marty/posting_grid.rb +12 -13
- data/app/components/marty/promise_view.rb +6 -6
- data/app/components/marty/report_form.rb +50 -53
- data/app/components/marty/report_select.rb +27 -27
- data/app/components/marty/reporting.rb +4 -4
- data/app/components/marty/script_form.rb +40 -42
- data/app/components/marty/script_grid.rb +24 -24
- data/app/components/marty/script_tester.rb +40 -42
- data/app/components/marty/scripting.rb +25 -27
- data/app/components/marty/simple_app.rb +24 -9
- data/app/components/marty/tag_grid.rb +12 -13
- data/app/components/marty/user_view.rb +35 -35
- data/app/controllers/marty/application_controller.rb +3 -4
- data/app/controllers/marty/components_controller.rb +1 -1
- data/app/controllers/marty/delayed_job_controller.rb +1 -0
- data/app/controllers/marty/diagnostic/controller.rb +4 -6
- data/app/controllers/marty/job_controller.rb +6 -6
- data/app/controllers/marty/report_controller.rb +11 -11
- data/app/controllers/marty/rpc_controller.rb +15 -16
- data/app/helpers/marty/script_set.rb +4 -4
- data/app/models/marty/api_auth.rb +4 -5
- data/app/models/marty/api_config.rb +1 -1
- data/app/models/marty/base.rb +9 -8
- data/app/models/marty/base_rule.rb +18 -13
- data/app/models/marty/config.rb +4 -5
- data/app/models/marty/data_grid.rb +157 -181
- data/app/models/marty/delorean_rule.rb +63 -62
- data/app/models/marty/enum.rb +1 -1
- data/app/models/marty/event.rb +56 -59
- data/app/models/marty/helper.rb +38 -6
- data/app/models/marty/import_type.rb +6 -6
- data/app/models/marty/log.rb +3 -2
- data/app/models/marty/name_validator.rb +3 -2
- data/app/models/marty/pg_enum.rb +3 -4
- data/app/models/marty/posting.rb +20 -24
- data/app/models/marty/promise.rb +28 -30
- data/app/models/marty/script.rb +30 -28
- data/app/models/marty/tag.rb +8 -8
- data/app/models/marty/token.rb +2 -2
- data/app/models/marty/user.rb +24 -23
- data/app/models/marty/vw_promise.rb +10 -11
- data/config/routes.rb +2 -2
- data/delorean/blame_report.dl +268 -0
- data/{spec/dummy/delorean/fields.dl → delorean/marty_fields.dl} +8 -0
- data/delorean/table_report.dl +34 -0
- data/docker-compose.dummy.yml +2 -3
- data/lib/marty/aws/base.rb +8 -8
- data/lib/marty/aws/request.rb +4 -4
- data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -0
- data/lib/marty/content_handler.rb +25 -25
- data/lib/marty/data_change.rb +49 -71
- data/lib/marty/data_conversion.rb +20 -28
- data/lib/marty/data_exporter.rb +25 -28
- data/lib/marty/data_importer.rb +25 -27
- data/lib/marty/engine.rb +1 -2
- data/lib/marty/json_schema.rb +22 -24
- data/lib/marty/logger.rb +6 -9
- data/lib/marty/mcfly_model.rb +20 -24
- data/lib/marty/migrations.rb +37 -35
- data/lib/marty/monkey.rb +33 -33
- data/lib/marty/permissions.rb +18 -18
- data/lib/marty/promise_job.rb +17 -17
- data/lib/marty/promise_proxy.rb +6 -6
- data/lib/marty/relation.rb +6 -7
- data/lib/marty/rpc_call.rb +13 -12
- data/lib/marty/rule_script_set.rb +32 -28
- data/lib/marty/schema_helper.rb +37 -51
- data/lib/marty/util.rb +25 -24
- data/lib/marty/version.rb +1 -1
- data/lib/marty/xl.rb +121 -115
- data/make-dummy.mk +3 -0
- data/marty.gemspec +21 -21
- data/other/marty/api/base.rb +34 -35
- data/other/marty/diagnostic/aws/ec2_instance.rb +8 -8
- data/other/marty/diagnostic/base.rb +13 -14
- data/other/marty/diagnostic/collection.rb +2 -1
- data/other/marty/diagnostic/connections.rb +8 -6
- data/other/marty/diagnostic/database.rb +1 -0
- data/other/marty/diagnostic/delayed_job_version.rb +7 -9
- data/other/marty/diagnostic/delayed_job_worker_total_count.rb +1 -1
- data/other/marty/diagnostic/delayed_job_workers.rb +1 -1
- data/other/marty/diagnostic/environment_variables.rb +17 -15
- data/other/marty/diagnostic/fatal.rb +1 -1
- data/other/marty/diagnostic/node.rb +5 -9
- data/other/marty/diagnostic/nodes.rb +7 -5
- data/other/marty/diagnostic/packer.rb +7 -7
- data/other/marty/diagnostic/reporter.rb +24 -27
- data/other/marty/diagnostic/version.rb +3 -5
- data/script/rails +2 -1
- data/spec/controllers/application_controller_spec.rb +6 -6
- data/spec/controllers/delayed_job_controller_spec.rb +4 -4
- data/spec/controllers/diagnostic/controller_spec.rb +59 -60
- data/spec/controllers/job_controller_spec.rb +68 -69
- data/spec/controllers/rpc_controller_spec.rb +353 -359
- data/spec/controllers/rpc_import_spec.rb +15 -16
- data/spec/dummy/delorean/blame_report.dl +110 -15
- data/spec/dummy/delorean/data_report.dl +4 -4
- data/spec/dummy/delorean/marty_fields.dl +63 -0
- data/spec/dummy/delorean/table_report.dl +34 -0
- data/spec/features/auth_app_spec.rb +1 -2
- data/spec/features/data_import_spec.rb +2 -3
- data/spec/features/enum_spec.rb +42 -46
- data/spec/features/jobs_dashboard_spec.rb +14 -8
- data/spec/features/log_view_spec.rb +40 -43
- data/spec/features/reporting_spec.rb +15 -15
- data/spec/features/rule_spec.rb +195 -190
- data/spec/features/scripting_spec.rb +17 -20
- data/spec/features/scripting_test_spec.rb +32 -33
- data/spec/features/user_view_spec.rb +15 -17
- data/spec/job_helper.rb +11 -11
- data/spec/lib/data_blame_spec.rb +82 -0
- data/spec/lib/data_exporter_spec.rb +31 -32
- data/spec/lib/data_importer_spec.rb +382 -395
- data/spec/lib/delorean_query_spec.rb +117 -119
- data/spec/lib/json_schema_spec.rb +382 -392
- data/spec/lib/logger_spec.rb +23 -24
- data/spec/lib/mcfly_model_spec.rb +112 -109
- data/spec/lib/migrations_spec.rb +10 -10
- data/spec/lib/struct_compare_spec.rb +6 -6
- data/spec/lib/table_report_spec.rb +90 -0
- data/spec/lib/xl_spec.rb +63 -65
- data/spec/lib/xl_styles_spec.rb +16 -19
- data/spec/models/api_auth_spec.rb +30 -30
- data/spec/models/config_spec.rb +32 -32
- data/spec/models/data_grid_spec.rb +642 -655
- data/spec/models/event_spec.rb +96 -88
- data/spec/models/import_type_spec.rb +20 -20
- data/spec/models/posting_spec.rb +35 -35
- data/spec/models/promise_spec.rb +5 -5
- data/spec/models/rule_spec.rb +280 -269
- data/spec/models/script_spec.rb +27 -18
- data/spec/models/user_spec.rb +9 -9
- data/spec/other/diagnostic/base_spec.rb +20 -19
- data/spec/other/diagnostic/collection_spec.rb +6 -5
- data/spec/other/diagnostic/delayed_job_version_spec.rb +1 -1
- data/spec/other/diagnostic/delayed_job_workers_spec.rb +8 -8
- data/spec/other/diagnostic/reporter_spec.rb +31 -33
- data/spec/spec_helper.rb +5 -5
- data/spec/support/chromedriver.rb +3 -5
- data/spec/support/components/netzke_combobox.rb +1 -1
- data/spec/support/components/netzke_grid.rb +17 -17
- data/spec/support/custom_matchers.rb +2 -2
- data/spec/support/download_helper.rb +1 -1
- data/spec/support/helper.rb +1 -2
- data/spec/support/netzke.rb +31 -31
- data/spec/support/performance_helper.rb +8 -8
- data/spec/support/post_run_logger.rb +1 -2
- data/spec/support/setup.rb +1 -4
- data/spec/support/shared_connection.rb +2 -2
- data/spec/support/structure_compare.rb +21 -22
- data/spec/support/suite.rb +1 -2
- data/spec/support/users.rb +5 -6
- metadata +32 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class Marty::JobController < ActionController::Base
|
|
2
2
|
def download
|
|
3
|
-
job_id = params[
|
|
3
|
+
job_id = params['job_id']
|
|
4
4
|
|
|
5
5
|
promise = Marty::Promise.find_by_id(job_id)
|
|
6
6
|
|
|
@@ -12,12 +12,12 @@ class Marty::JobController < ActionController::Base
|
|
|
12
12
|
|
|
13
13
|
# somewhat hacky: if result has "result" key, it's used as the
|
|
14
14
|
# content.
|
|
15
|
-
data = data[
|
|
15
|
+
data = data['result'] || data
|
|
16
16
|
title = promise.title
|
|
17
17
|
else
|
|
18
|
-
format =
|
|
19
|
-
data = {error: "Job not found: #{job_id}"}
|
|
20
|
-
title =
|
|
18
|
+
format = 'json'
|
|
19
|
+
data = { error: "Job not found: #{job_id}" }
|
|
20
|
+
title = 'error'
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
res, type, disposition, filename =
|
|
@@ -27,6 +27,6 @@ class Marty::JobController < ActionController::Base
|
|
|
27
27
|
type: type,
|
|
28
28
|
filename: filename,
|
|
29
29
|
disposition: disposition,
|
|
30
|
-
|
|
30
|
+
)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -3,27 +3,27 @@ class Marty::ReportController < Marty::ApplicationController
|
|
|
3
3
|
format, req_disposition, title =
|
|
4
4
|
params[:format], params[:disposition], params[:reptitle]
|
|
5
5
|
|
|
6
|
-
raise
|
|
6
|
+
raise 'bad format' unless Marty::ContentHandler::GEN_FORMATS.member?(format)
|
|
7
7
|
|
|
8
8
|
data = Marty::ReportForm.run_eval(params)
|
|
9
9
|
|
|
10
10
|
# hacky: shouldn't have error parsing logic here
|
|
11
|
-
format =
|
|
11
|
+
format = 'json' if data.is_a?(Hash) && (data[:error] || data['error'])
|
|
12
12
|
|
|
13
13
|
# hack for testing -- txt -> csv
|
|
14
|
-
exp_format = format ==
|
|
14
|
+
exp_format = format == 'txt' ? 'csv' : format
|
|
15
15
|
|
|
16
16
|
res, type, disposition, filename =
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Marty::ContentHandler.
|
|
18
|
+
export(data, exp_format, title)
|
|
19
19
|
|
|
20
20
|
# hack for testing -- set content-type
|
|
21
|
-
type =
|
|
21
|
+
type = 'text/plain' if format == 'txt' && type =~ /csv/
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
send_data(res,
|
|
24
|
+
type: type,
|
|
25
|
+
filename: filename,
|
|
26
|
+
disposition: req_disposition || disposition,
|
|
27
|
+
)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
class Marty::RpcController < ActionController::Base
|
|
2
2
|
def evaluate
|
|
3
|
-
begin
|
|
4
3
|
# set default result and params in case of unexpected errors
|
|
5
4
|
# to ensure logging capabilities
|
|
6
5
|
result = nil
|
|
@@ -13,9 +12,9 @@ class Marty::RpcController < ActionController::Base
|
|
|
13
12
|
|
|
14
13
|
# resolve api config in order to determine api class and settings
|
|
15
14
|
api_config = Marty::ApiConfig.lookup(*massaged_params.values_at(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
:script,
|
|
16
|
+
:node,
|
|
17
|
+
:attr)
|
|
19
18
|
) || {}
|
|
20
19
|
|
|
21
20
|
# default to base class if no config is present
|
|
@@ -26,32 +25,32 @@ class Marty::RpcController < ActionController::Base
|
|
|
26
25
|
|
|
27
26
|
api_params = api.process_params(massaged_params)
|
|
28
27
|
auth = api.is_authorized?(api_params)
|
|
29
|
-
return result = {error:
|
|
28
|
+
return result = { error: 'Permission denied' } unless auth
|
|
30
29
|
|
|
31
30
|
start_time = Time.zone.now
|
|
32
31
|
api.before_evaluate(api_params)
|
|
33
32
|
result = api.evaluate(api_params, request, api_config)
|
|
34
33
|
api.after_evaluate(api_params, result)
|
|
35
|
-
|
|
34
|
+
rescue StandardError => e
|
|
36
35
|
# log unexpected failures in rpc controller and respond with
|
|
37
36
|
# generic server error
|
|
38
37
|
Marty::Logger.log('rpc_controller', 'failure', e.message)
|
|
39
|
-
result = {error: 'internal server error'}
|
|
40
|
-
|
|
38
|
+
result = { error: 'internal server error' }
|
|
39
|
+
ensure
|
|
41
40
|
# if logging is enabled, always log the result even on error
|
|
42
41
|
if api_config && api_config[:logged] && api
|
|
43
42
|
api.log(result,
|
|
44
|
-
api_params + {start_time: start_time, auth: auth},
|
|
43
|
+
api_params + { start_time: start_time, auth: auth },
|
|
45
44
|
request)
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
api.respond_to(self) do
|
|
49
|
-
result || {'error' => 'internal server error'}
|
|
48
|
+
result || { 'error' => 'internal server error' }
|
|
50
49
|
end
|
|
51
|
-
end
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
private
|
|
53
|
+
|
|
55
54
|
def process_active_params params
|
|
56
55
|
# must permit params before conversion to_h
|
|
57
56
|
# convert hash to json and parse to get expected hash (not indifferent)
|
|
@@ -77,7 +76,7 @@ class Marty::RpcController < ActionController::Base
|
|
|
77
76
|
# FIXME: small patch to allow for single attr array
|
|
78
77
|
attr = ActiveSupport::JSON.decode(attr) rescue attr
|
|
79
78
|
|
|
80
|
-
return {error:
|
|
79
|
+
return { error: 'Malformed attrs' } unless
|
|
81
80
|
attr.is_a?(String) || (attr.is_a?(Array) && attr.count == 1)
|
|
82
81
|
|
|
83
82
|
# if attr is a single attr array, remember to return as an array
|
|
@@ -86,7 +85,7 @@ class Marty::RpcController < ActionController::Base
|
|
|
86
85
|
ret_arr = true
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
return {error:
|
|
88
|
+
return { error: 'Malformed attrs' } unless attr =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
|
90
89
|
|
|
91
90
|
begin
|
|
92
91
|
case params
|
|
@@ -97,13 +96,13 @@ class Marty::RpcController < ActionController::Base
|
|
|
97
96
|
when ActionController::Parameters
|
|
98
97
|
params = process_active_params(params)
|
|
99
98
|
else
|
|
100
|
-
return {error:
|
|
99
|
+
return { error: 'Bad params' }
|
|
101
100
|
end
|
|
102
101
|
rescue JSON::ParserError => e
|
|
103
|
-
return {error:
|
|
102
|
+
return { error: 'Malformed params' }
|
|
104
103
|
end
|
|
105
104
|
|
|
106
|
-
return {error:
|
|
105
|
+
return { error: 'Malformed params' } unless params.is_a?(Hash)
|
|
107
106
|
|
|
108
107
|
# permit request params and convert to hash
|
|
109
108
|
process_active_params(request_params.except(:rpc)).symbolize_keys + {
|
|
@@ -10,7 +10,7 @@ class Marty::ScriptSet < Delorean::AbstractContainer
|
|
|
10
10
|
|
|
11
11
|
clear_cache
|
|
12
12
|
|
|
13
|
-
def initialize(tag=nil)
|
|
13
|
+
def initialize(tag = nil)
|
|
14
14
|
@tag = Marty::Tag.map_to_tag(tag)
|
|
15
15
|
super()
|
|
16
16
|
end
|
|
@@ -30,7 +30,7 @@ class Marty::ScriptSet < Delorean::AbstractContainer
|
|
|
30
30
|
# hacky/rare anyway. So, don't bother for now.
|
|
31
31
|
|
|
32
32
|
max_dt = Marty::Script.
|
|
33
|
-
order(
|
|
33
|
+
order('created_dt DESC').limit(1).pluck(:created_dt).first
|
|
34
34
|
|
|
35
35
|
@@dengines_dt ||= max_dt
|
|
36
36
|
|
|
@@ -41,7 +41,7 @@ class Marty::ScriptSet < Delorean::AbstractContainer
|
|
|
41
41
|
|
|
42
42
|
return engine if engine
|
|
43
43
|
|
|
44
|
-
script = Marty::Script.find_script(sname, tag) || raise(
|
|
44
|
+
script = Marty::Script.find_script(sname, tag) || raise('No such script')
|
|
45
45
|
|
|
46
46
|
@@dengines[sname] = parse_check(sname, script.body)
|
|
47
47
|
else
|
|
@@ -49,7 +49,7 @@ class Marty::ScriptSet < Delorean::AbstractContainer
|
|
|
49
49
|
|
|
50
50
|
return engine if engine
|
|
51
51
|
|
|
52
|
-
script = Marty::Script.find_script(sname, tag) || raise(
|
|
52
|
+
script = Marty::Script.find_script(sname, tag) || raise('No such script')
|
|
53
53
|
|
|
54
54
|
@@engines[[tag.id, sname]] = parse_check(sname, script.body)
|
|
55
55
|
end
|
|
@@ -7,10 +7,10 @@ class Marty::ApiAuth < Marty::Base
|
|
|
7
7
|
|
|
8
8
|
class ApiAuthValidator < ActiveModel::Validator
|
|
9
9
|
def validate(api)
|
|
10
|
-
api.errors.add(:base, "API Key length must be #{KEY_SIZE*2}") if
|
|
11
|
-
api.api_key && api.api_key.length != KEY_SIZE*2
|
|
10
|
+
api.errors.add(:base, "API Key length must be #{KEY_SIZE * 2}") if
|
|
11
|
+
api.api_key && api.api_key.length != KEY_SIZE * 2
|
|
12
12
|
|
|
13
|
-
api.errors.add(:base,
|
|
13
|
+
api.errors.add(:base, 'Script Name must reference a valid script') unless
|
|
14
14
|
Marty::Script.find_script(api.script_name, nil)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -21,10 +21,9 @@ class Marty::ApiAuth < Marty::Base
|
|
|
21
21
|
validates_uniqueness_of :app_name, scope: [:script_name,
|
|
22
22
|
:obsoleted_dt]
|
|
23
23
|
|
|
24
|
-
|
|
25
24
|
before_validation do
|
|
26
25
|
self.api_key = Marty::ApiAuth.generate_key if
|
|
27
|
-
|
|
26
|
+
api_key.blank?
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def self.generate_key
|
|
@@ -17,6 +17,6 @@ class Marty::ApiConfig < Marty::Base
|
|
|
17
17
|
|
|
18
18
|
def self.multi_lookup(script, node, attrs)
|
|
19
19
|
(attrs.nil? ? [nil] : attrs).
|
|
20
|
-
map { |attr| lookup(script, node, attr).try{|x| x.unshift(attr) }}
|
|
20
|
+
map { |attr| lookup(script, node, attr).try { |x| x.unshift(attr) } }
|
|
21
21
|
end
|
|
22
22
|
end
|
data/app/models/marty/base.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
class Marty::Base < ActiveRecord::Base
|
|
2
|
-
self.table_name_prefix =
|
|
2
|
+
self.table_name_prefix = 'marty_'
|
|
3
3
|
self.abstract_class = true
|
|
4
4
|
|
|
5
5
|
def self.mcfly_pt(pt)
|
|
6
|
-
tb =
|
|
7
|
-
|
|
6
|
+
tb = table_name
|
|
7
|
+
where("#{tb}.obsoleted_dt >= ? AND #{tb}.created_dt < ?", pt, pt)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
class << self
|
|
@@ -13,9 +13,9 @@ class Marty::Base < ActiveRecord::Base
|
|
|
13
13
|
|
|
14
14
|
def self.get_struct_attrs
|
|
15
15
|
self.struct_attrs ||=
|
|
16
|
-
|
|
17
|
-
(
|
|
18
|
-
|
|
16
|
+
attribute_names - Mcfly::COLUMNS.to_a -
|
|
17
|
+
(const_defined?('MCFLY_UNIQUENESS') &&
|
|
18
|
+
const_get('MCFLY_UNIQUENESS') || []).map(&:to_s)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def self.get_final_attrs
|
|
@@ -48,12 +48,13 @@ class Marty::Base < ActiveRecord::Base
|
|
|
48
48
|
|
|
49
49
|
def self.make_openstruct(inst)
|
|
50
50
|
return nil unless inst
|
|
51
|
+
|
|
51
52
|
fa = get_final_attrs
|
|
52
53
|
os = OpenStruct.new(inst.attributes.slice(*fa))
|
|
53
54
|
if self == Marty::DataGrid
|
|
54
55
|
def os.lookup_grid_distinct_entry(pt, params)
|
|
55
|
-
dgh =
|
|
56
|
-
|
|
56
|
+
dgh = to_h.stringify_keys.slice(
|
|
57
|
+
'id', 'group_id', 'created_dt', 'metadata', 'data_type')
|
|
57
58
|
Marty::DataGrid.lookup_grid_distinct_entry_h(pt, params, dgh)
|
|
58
59
|
end
|
|
59
60
|
end
|
|
@@ -9,6 +9,7 @@ class Marty::BaseRule < Marty::Base
|
|
|
9
9
|
def chkrange(v)
|
|
10
10
|
v.match(/\A(\[|\()([0-9\.-]*),([0-9\.-]*)(\]|\))\z/)
|
|
11
11
|
end
|
|
12
|
+
|
|
12
13
|
def gettypes(v)
|
|
13
14
|
types = []
|
|
14
15
|
types << :string if v.is_a?(String)
|
|
@@ -20,6 +21,7 @@ class Marty::BaseRule < Marty::Base
|
|
|
20
21
|
types << :boolean if [true, false, 'True', 'False'].include?(v)
|
|
21
22
|
types
|
|
22
23
|
end
|
|
24
|
+
|
|
23
25
|
def check(name, h)
|
|
24
26
|
multi, type, enum, values, req = h.values_at(:multi, :type, :enum, :values,
|
|
25
27
|
:required)
|
|
@@ -31,6 +33,7 @@ class Marty::BaseRule < Marty::Base
|
|
|
31
33
|
return errors[errtype] << "- Required field #{ns} is missing" if
|
|
32
34
|
v.blank? && req
|
|
33
35
|
return if v.blank?
|
|
36
|
+
|
|
34
37
|
gotmulti = v.is_a?(Array) ? 'multi' : 'single'
|
|
35
38
|
return errors[errtype] << "- Wrong arity for #{ns} (expected #{expmulti} "\
|
|
36
39
|
"got #{gotmulti})" if expmulti != gotmulti
|
|
@@ -40,22 +43,24 @@ class Marty::BaseRule < Marty::Base
|
|
|
40
43
|
gettypes(vv).member?(type)
|
|
41
44
|
end
|
|
42
45
|
return unless enum || values
|
|
46
|
+
|
|
43
47
|
vals = enum && enum::VALUES || values.to_set
|
|
44
48
|
bad = (vs - vals)
|
|
45
49
|
p = bad.count > 1 ? 's' : ''
|
|
46
50
|
return errors[errtype] <<
|
|
47
51
|
%Q(- Bad value#{p} '#{bad.to_a.join("', '")}' for #{ns}) if bad.present?
|
|
48
52
|
end
|
|
53
|
+
|
|
49
54
|
def validate
|
|
50
55
|
self.class.guard_info.each { |name, h| check(name, h) }
|
|
51
56
|
grids.each do |vn, gn|
|
|
52
57
|
return errors[:grids] << "- Bad grid name '#{gn}' for '#{vn}'" unless
|
|
53
58
|
gn.blank? || Marty::DataGrid.lookup('infinity', gn)
|
|
54
59
|
end
|
|
55
|
-
cg_err = computed_guards.delete(
|
|
60
|
+
cg_err = computed_guards.delete('~~ERROR~~')
|
|
56
61
|
errors[:computed] <<
|
|
57
62
|
"- Error in rule '#{name}' field 'computed_guards': #{cg_err.capitalize}" if cg_err
|
|
58
|
-
res_err = results.delete(
|
|
63
|
+
res_err = results.delete('~~ERROR~~')
|
|
59
64
|
errors[:computed] <<
|
|
60
65
|
"- Error in rule '#{name}' field 'results': #{res_err.capitalize}" if res_err
|
|
61
66
|
end
|
|
@@ -71,45 +76,45 @@ class Marty::BaseRule < Marty::Base
|
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
before_create do
|
|
74
|
-
self.class.guard_info.each do |k,v|
|
|
79
|
+
self.class.guard_info.each do |k, v|
|
|
75
80
|
next if !v.include?(:default) || self.simple_guards.include?(k)
|
|
81
|
+
|
|
76
82
|
self.simple_guards[k] = v[:default]
|
|
77
83
|
end
|
|
78
84
|
end
|
|
79
85
|
|
|
80
86
|
def self.get_subq(field, subfield, multi, type, vraw)
|
|
81
87
|
arrow = multi || ![:range, :string, :date, :datetime].include?(type) ?
|
|
82
|
-
|
|
83
|
-
op = multi || type == :range ?
|
|
88
|
+
'->' : '->>'
|
|
89
|
+
op = multi || type == :range ? '@>' : '='
|
|
84
90
|
value0 = [:string, :date, :datetime].include?(type) ?
|
|
85
91
|
ActiveRecord::Base.connection.quote(vraw) :
|
|
86
92
|
type == :range ? vraw.to_f :
|
|
87
93
|
"'#{vraw}'::jsonb"
|
|
88
94
|
value = multi ? %Q('["%s"]') % value0[1..-2] : value0
|
|
89
|
-
fieldcast = type == :range ?
|
|
95
|
+
fieldcast = type == :range ? '::numrange' : ''
|
|
90
96
|
"(#{field}#{arrow}'#{subfield}')#{fieldcast} #{op} #{value}"
|
|
91
97
|
end
|
|
92
98
|
|
|
93
99
|
def self.get_matches_(pt, attrs, params)
|
|
94
|
-
|
|
95
|
-
q = select("DISTINCT ON (name) *").where(attrs)
|
|
100
|
+
q = select('DISTINCT ON (name) *').where(attrs)
|
|
96
101
|
|
|
97
102
|
params.each do |k, vraw|
|
|
98
103
|
h = guard_info
|
|
99
104
|
use_k = (h[k] && k) ||
|
|
100
|
-
(h[k+
|
|
101
|
-
(h[k+
|
|
105
|
+
(h[k + '_array'] && k + '_array') ||
|
|
106
|
+
(h[k + '_range'] && k + '_range')
|
|
102
107
|
next unless use_k
|
|
108
|
+
|
|
103
109
|
multi, type = h[use_k].values_at(:multi, :type)
|
|
104
110
|
filts = [vraw].flatten.map do |v|
|
|
105
111
|
qstr = get_subq('simple_guards', use_k, multi, type, v)
|
|
106
|
-
end.join(
|
|
112
|
+
end.join(' OR ')
|
|
107
113
|
isn = "simple_guards->'#{use_k}' IS NULL OR"
|
|
108
114
|
|
|
109
115
|
q = q.where("(#{isn} #{filts})")
|
|
110
116
|
end
|
|
111
|
-
#print q.to_sql
|
|
117
|
+
# print q.to_sql
|
|
112
118
|
q.order(:name)
|
|
113
119
|
end
|
|
114
|
-
|
|
115
120
|
end
|
data/app/models/marty/config.rb
CHANGED
|
@@ -2,7 +2,7 @@ class Marty::Config < Marty::Base
|
|
|
2
2
|
class ConfigValidator < ActiveModel::Validator
|
|
3
3
|
def validate(entry)
|
|
4
4
|
v = entry.get_value
|
|
5
|
-
entry.errors[:base] <<
|
|
5
|
+
entry.errors[:base] << 'bad JSON value' if v.nil?
|
|
6
6
|
v
|
|
7
7
|
end
|
|
8
8
|
end
|
|
@@ -11,13 +11,12 @@ class Marty::Config < Marty::Base
|
|
|
11
11
|
validates_uniqueness_of :key
|
|
12
12
|
validates_with ConfigValidator
|
|
13
13
|
|
|
14
|
-
delorean_fn :lookup, sig: 1 do
|
|
15
|
-
|key|
|
|
14
|
+
delorean_fn :lookup, sig: 1 do |key|
|
|
16
15
|
self[key]
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def get_value
|
|
20
|
-
|
|
19
|
+
value[0]
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def set_value(v)
|
|
@@ -27,7 +26,7 @@ class Marty::Config < Marty::Base
|
|
|
27
26
|
def self.[]=(key, value)
|
|
28
27
|
entry = find_by_key(key)
|
|
29
28
|
if !entry
|
|
30
|
-
entry =
|
|
29
|
+
entry = new
|
|
31
30
|
entry.key = key
|
|
32
31
|
end
|
|
33
32
|
entry.set_value(value)
|
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
class Marty::DataGrid < Marty::Base
|
|
2
2
|
# If data_type is nil, assume float
|
|
3
|
-
DEFAULT_DATA_TYPE =
|
|
3
|
+
DEFAULT_DATA_TYPE = 'float'
|
|
4
4
|
|
|
5
5
|
INDEX_MAP = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
'numrange' => Marty::GridIndexNumrange,
|
|
7
|
+
'int4range' => Marty::GridIndexInt4range,
|
|
8
|
+
'integer' => Marty::GridIndexInteger,
|
|
9
|
+
'string' => Marty::GridIndexString,
|
|
10
|
+
'boolean' => Marty::GridIndexBoolean,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
ARRSEP = '|'
|
|
14
14
|
|
|
15
15
|
class DataGridValidator < ActiveModel::Validator
|
|
16
16
|
def validate(dg)
|
|
17
|
-
|
|
18
17
|
dg.errors.add(:base, "'#{dg.data_type}' not a defined type or class") unless
|
|
19
18
|
Marty::DataGrid.convert_data_type(dg.data_type)
|
|
20
19
|
|
|
21
|
-
dg.errors.add(:base,
|
|
22
|
-
dg.data.is_a?(Array) && dg.data.all? {|a| a.is_a? Array}
|
|
23
|
-
|
|
24
|
-
dg.errors.add(:base, "metadata must be an array of hashes") unless
|
|
25
|
-
dg.metadata.is_a?(Array) && dg.metadata.all? {|a| a.is_a? Hash}
|
|
20
|
+
dg.errors.add(:base, 'data must be array of arrays') unless
|
|
21
|
+
dg.data.is_a?(Array) && dg.data.all? { |a| a.is_a? Array }
|
|
26
22
|
|
|
27
|
-
dg.errors.add(:base,
|
|
28
|
-
dg.metadata.all? {|
|
|
23
|
+
dg.errors.add(:base, 'metadata must be an array of hashes') unless
|
|
24
|
+
dg.metadata.is_a?(Array) && dg.metadata.all? { |a| a.is_a? Hash }
|
|
29
25
|
|
|
30
|
-
dg.errors.add(:base,
|
|
31
|
-
dg.metadata.
|
|
26
|
+
dg.errors.add(:base, 'metadata must contain only h/v dirs') unless
|
|
27
|
+
dg.metadata.all? { |h| ['h', 'v'].member? h['dir'] }
|
|
32
28
|
|
|
33
|
-
dg.
|
|
34
|
-
|
|
|
29
|
+
dg.errors.add(:base, 'metadata item attrs must be unique') unless
|
|
30
|
+
dg.metadata.map { |h| h['attr'] }.uniq.length == dg.metadata.length
|
|
35
31
|
|
|
32
|
+
dg.metadata.each do |inf|
|
|
36
33
|
attr, type, keys, rs_keep =
|
|
37
|
-
inf[
|
|
34
|
+
inf['attr'], inf['type'], inf['keys'], inf['rs_keep']
|
|
38
35
|
|
|
39
36
|
unless rs_keep.nil? || rs_keep.empty?
|
|
40
37
|
m = /\A *(<|<=|>|>=)? *([a-z][a-z_0-9]+) *\z/.match(rs_keep)
|
|
@@ -44,7 +41,7 @@ class Marty::DataGrid < Marty::Base
|
|
|
44
41
|
end
|
|
45
42
|
end
|
|
46
43
|
|
|
47
|
-
dg.errors.add(:base,
|
|
44
|
+
dg.errors.add(:base, 'metadata elements must have attr/type/keys') unless
|
|
48
45
|
attr && type && keys
|
|
49
46
|
|
|
50
47
|
# enforce Delorean attr syntax (a bit Draconian)
|
|
@@ -54,24 +51,24 @@ class Marty::DataGrid < Marty::Base
|
|
|
54
51
|
dg.errors.add(:base, "unknown metadata type #{type}") unless
|
|
55
52
|
Marty::DataGrid.type_to_index(type)
|
|
56
53
|
|
|
57
|
-
dg.errors.add(:base,
|
|
58
|
-
keys.is_a?(Array) && keys.
|
|
54
|
+
dg.errors.add(:base, 'bad metadata keys') unless
|
|
55
|
+
keys.is_a?(Array) && !keys.empty?
|
|
59
56
|
end
|
|
60
57
|
|
|
61
58
|
# Check key uniqueness of vertical/horizontal key
|
|
62
59
|
# combinations. FIXME: ideally, we should also check for
|
|
63
60
|
# array/range key subsumption. Those will result in runtime
|
|
64
61
|
# errors anyway when multiple hits are produced.
|
|
65
|
-
v_keys = dg.dir_infos(
|
|
66
|
-
h_keys = dg.dir_infos(
|
|
62
|
+
v_keys = dg.dir_infos('v').map { |inf| inf['keys'] }
|
|
63
|
+
h_keys = dg.dir_infos('h').map { |inf| inf['keys'] }
|
|
67
64
|
|
|
68
65
|
v_zip_keys = v_keys.empty? ? [] : v_keys[0].zip(*v_keys[1..-1])
|
|
69
66
|
h_zip_keys = h_keys.empty? ? [] : h_keys[0].zip(*h_keys[1..-1])
|
|
70
67
|
|
|
71
|
-
dg.errors.add(:base,
|
|
68
|
+
dg.errors.add(:base, 'duplicate horiz. key combination') unless
|
|
72
69
|
h_zip_keys.uniq.length == h_zip_keys.length
|
|
73
70
|
|
|
74
|
-
dg.errors.add(:base,
|
|
71
|
+
dg.errors.add(:base, 'duplicate vertical key combination') unless
|
|
75
72
|
v_zip_keys.uniq.length == v_zip_keys.length
|
|
76
73
|
end
|
|
77
74
|
end
|
|
@@ -89,26 +86,23 @@ class Marty::DataGrid < Marty::Base
|
|
|
89
86
|
|
|
90
87
|
# FIXME: if the caller requests data as part of fields, there could
|
|
91
88
|
# be memory concerns with caching since some data_grids have massive data
|
|
92
|
-
cached_delorean_fn :lookup_h, sig: [2, 3] do
|
|
93
|
-
|pt, name, fields = nil|
|
|
89
|
+
cached_delorean_fn :lookup_h, sig: [2, 3] do |pt, name, fields = nil|
|
|
94
90
|
fields ||= %w(id group_id created_dt metadata data_type name)
|
|
95
91
|
dga = mcfly_pt(pt).where(name: name).pluck(*fields).first
|
|
96
92
|
dga && Hash[fields.zip(dga)]
|
|
97
93
|
end
|
|
98
94
|
|
|
99
95
|
# deprecated - remove 2018-Oct
|
|
100
|
-
cached_mcfly_lookup :lookup_id, sig: 2 do
|
|
101
|
-
|pt, group_id|
|
|
96
|
+
cached_mcfly_lookup :lookup_id, sig: 2 do |pt, group_id|
|
|
102
97
|
find_by_group_id group_id
|
|
103
98
|
end
|
|
104
99
|
|
|
105
|
-
cached_delorean_fn :exists, sig: 2 do
|
|
106
|
-
|pt, name|
|
|
100
|
+
cached_delorean_fn :exists, sig: 2 do |pt, name|
|
|
107
101
|
Marty::DataGrid.mcfly_pt(pt).where(name: name).exists?
|
|
108
102
|
end
|
|
109
103
|
|
|
110
104
|
def self.get_struct_attrs
|
|
111
|
-
self.struct_attrs ||= super + [
|
|
105
|
+
self.struct_attrs ||= super + ['id', 'group_id', 'created_dt', 'name']
|
|
112
106
|
end
|
|
113
107
|
|
|
114
108
|
def to_s
|
|
@@ -125,6 +119,7 @@ class Marty::DataGrid < Marty::Base
|
|
|
125
119
|
def self.register_rule_handler(handler)
|
|
126
120
|
(@@rule_handlers ||= []) << handler
|
|
127
121
|
end
|
|
122
|
+
|
|
128
123
|
def update_rules(old, new)
|
|
129
124
|
@@rule_handlers.each { |rh| rh.call(old, new) }
|
|
130
125
|
end
|
|
@@ -133,7 +128,7 @@ class Marty::DataGrid < Marty::Base
|
|
|
133
128
|
# transaction -- i.e. together with build_index. before_save would
|
|
134
129
|
# be OK, but then save inside it would cause an infinite loop.
|
|
135
130
|
def save!
|
|
136
|
-
if
|
|
131
|
+
if changed?
|
|
137
132
|
transaction do
|
|
138
133
|
nc, nw, n = [name_changed?, name_was, name]
|
|
139
134
|
res = super
|
|
@@ -147,14 +142,15 @@ class Marty::DataGrid < Marty::Base
|
|
|
147
142
|
|
|
148
143
|
# FIXME: hacky -- save is just save!
|
|
149
144
|
def save
|
|
150
|
-
|
|
145
|
+
save!
|
|
151
146
|
end
|
|
152
147
|
|
|
153
148
|
def self.type_to_index(type)
|
|
154
149
|
# map given header type to an index class -- uses string index
|
|
155
150
|
# for ruby classes.
|
|
156
151
|
return INDEX_MAP[type] if INDEX_MAP[type]
|
|
157
|
-
|
|
152
|
+
|
|
153
|
+
INDEX_MAP['string'] if (type.constantize rescue nil)
|
|
158
154
|
end
|
|
159
155
|
|
|
160
156
|
def self.convert_data_type(data_type)
|
|
@@ -172,29 +168,30 @@ class Marty::DataGrid < Marty::Base
|
|
|
172
168
|
@@dtcache = {}
|
|
173
169
|
end
|
|
174
170
|
|
|
175
|
-
PLV_DT_FMT =
|
|
171
|
+
PLV_DT_FMT = '%Y-%m-%d %H:%M:%S.%N6'
|
|
176
172
|
|
|
177
|
-
def self.plv_lookup_grid_distinct(h_passed, dgh, ret_grid_data=false,
|
|
178
|
-
distinct=true)
|
|
179
|
-
cd = dgh[
|
|
173
|
+
def self.plv_lookup_grid_distinct(h_passed, dgh, ret_grid_data = false,
|
|
174
|
+
distinct = true)
|
|
175
|
+
cd = dgh['created_dt']
|
|
180
176
|
@@dtcache ||= {}
|
|
181
177
|
@@dtcache[cd] ||= cd.strftime(PLV_DT_FMT)
|
|
182
178
|
row_info = {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
'id' => dgh['id'],
|
|
180
|
+
'group_id' => dgh['group_id'],
|
|
181
|
+
'created_dt' => @@dtcache[cd]
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
h = dgh[
|
|
189
|
-
attr = m[
|
|
184
|
+
h = dgh['metadata'].each_with_object({}) do |m, h|
|
|
185
|
+
attr = m['attr']
|
|
190
186
|
inc = h_passed.fetch(attr, :__nf__)
|
|
191
187
|
next if inc == :__nf__
|
|
188
|
+
|
|
192
189
|
val = (defined? inc.name) ? inc.name : inc
|
|
193
190
|
h[attr] = val.is_a?(String) ?
|
|
194
191
|
ActiveRecord::Base.connection.quote(val)[1..-2] : val
|
|
195
192
|
end
|
|
196
193
|
|
|
197
|
-
fn =
|
|
194
|
+
fn = 'lookup_grid_distinct'
|
|
198
195
|
hjson = "'#{h.to_json}'::JSONB"
|
|
199
196
|
rijson = "'#{row_info.to_json}'::JSONB"
|
|
200
197
|
params = "#{hjson}, #{rijson}, #{ret_grid_data}, #{distinct}"
|
|
@@ -202,66 +199,63 @@ class Marty::DataGrid < Marty::Base
|
|
|
202
199
|
raw = ActiveRecord::Base.connection.execute(sql)[0][fn]
|
|
203
200
|
res = JSON.parse(raw)
|
|
204
201
|
|
|
205
|
-
if res[
|
|
206
|
-
msg = res[
|
|
207
|
-
parms, sqls, ress, dg = res[
|
|
208
|
-
|
|
202
|
+
if res['error']
|
|
203
|
+
msg = res['error']
|
|
204
|
+
parms, sqls, ress, dg = res['error_extra'].values_at(
|
|
205
|
+
'params', 'sql', 'results', 'dg')
|
|
209
206
|
|
|
210
207
|
raise "DG #{name}: Error in PLV8 call: #{msg}\n"\
|
|
211
208
|
"params: #{parms}\n"\
|
|
212
209
|
"sqls: #{sqls}\n"\
|
|
213
210
|
"results: #{ress}\n"\
|
|
214
211
|
"dg: #{dg}\n"\
|
|
215
|
-
"ri: #{row_info}" if res[
|
|
212
|
+
"ri: #{row_info}" if res['error']
|
|
216
213
|
end
|
|
217
214
|
|
|
218
215
|
if ret_grid_data
|
|
219
|
-
dg = find(dgh[
|
|
216
|
+
dg = find(dgh['id'])
|
|
220
217
|
md, mmd = modify_grid(h_passed, dg.metadata, dg.data)
|
|
221
|
-
res[
|
|
222
|
-
res[
|
|
218
|
+
res['data'] = md
|
|
219
|
+
res['metadata'] = mmd
|
|
223
220
|
end
|
|
224
221
|
res
|
|
225
222
|
end
|
|
226
223
|
|
|
227
224
|
# deprecated - remove 2018-Oct
|
|
228
|
-
cached_delorean_fn :lookup_grid, sig: 4 do
|
|
229
|
-
|pt, dg, h, distinct|
|
|
225
|
+
cached_delorean_fn :lookup_grid, sig: 4 do |pt, dg, h, distinct|
|
|
230
226
|
dg_is_grid = Marty::DataGrid === dg
|
|
231
|
-
dg_is_os
|
|
227
|
+
dg_is_os = dg.is_a?(OpenStruct)
|
|
232
228
|
raise "bad DataGrid #{dg}" unless dg_is_grid || dg_is_os
|
|
233
229
|
raise "non-hash arg #{h}" unless Hash === h
|
|
230
|
+
|
|
234
231
|
dgh = dg_is_os ? dg.to_h.stringify_keys :
|
|
235
232
|
dg.attributes.slice('id', 'group_id', 'created_dt', 'metadata')
|
|
236
233
|
res = plv_lookup_grid_distinct(h, dgh, false, distinct)
|
|
237
|
-
res[
|
|
234
|
+
res['result']
|
|
238
235
|
end
|
|
239
236
|
|
|
240
|
-
cached_delorean_fn :lookup_grid_h, sig: 4 do
|
|
241
|
-
|pt, dgn, h, distinct|
|
|
242
|
-
|
|
237
|
+
cached_delorean_fn :lookup_grid_h, sig: 4 do |pt, dgn, h, distinct|
|
|
243
238
|
dgh = lookup_h(pt, dgn)
|
|
244
239
|
raise "#{dgn} grid not found" unless dgh
|
|
245
240
|
raise "non-hash arg #{h}" unless Hash === h
|
|
246
241
|
|
|
247
242
|
res = lookup_grid_distinct_entry_h(pt, h, dgh, nil, true, false, distinct)
|
|
248
|
-
res[
|
|
243
|
+
res['result']
|
|
249
244
|
end
|
|
250
245
|
|
|
251
246
|
# FIXME: using cached_delorean_fn just for the caching -- this is
|
|
252
247
|
# not expected to be called from Delorean.
|
|
253
|
-
cached_delorean_fn :find_class_instance, sig: 3 do
|
|
254
|
-
|pt, klass, v|
|
|
248
|
+
cached_delorean_fn :find_class_instance, sig: 3 do |pt, klass, v|
|
|
255
249
|
if Marty::PgEnum === klass
|
|
256
250
|
klass.find_by_name(v)
|
|
257
251
|
else
|
|
258
252
|
# FIXME: very hacky -- hard-coded name
|
|
259
|
-
Marty::DataConversion.find_row(klass, {
|
|
253
|
+
Marty::DataConversion.find_row(klass, { 'name' => v }, pt)
|
|
260
254
|
end
|
|
261
255
|
end
|
|
262
256
|
|
|
263
|
-
def self.lookup_grid_distinct_entry_h(pt, h, dgh, visited=nil, follow=true,
|
|
264
|
-
return_grid_data=false, distinct=true)
|
|
257
|
+
def self.lookup_grid_distinct_entry_h(pt, h, dgh, visited = nil, follow = true,
|
|
258
|
+
return_grid_data = false, distinct = true)
|
|
265
259
|
|
|
266
260
|
# Perform grid lookup, if result is another data_grid, and follow is true,
|
|
267
261
|
# then perform lookup on the resulting grid. Allows grids to be nested
|
|
@@ -275,15 +269,15 @@ class Marty::DataGrid < Marty::Base
|
|
|
275
269
|
# "metadata" => <grid's metadata (array of hashes)>
|
|
276
270
|
vhash = plv_lookup_grid_distinct(h, dgh, return_grid_data, distinct)
|
|
277
271
|
|
|
278
|
-
return vhash if vhash[
|
|
272
|
+
return vhash if vhash['result'].nil? || !dgh['data_type']
|
|
279
273
|
|
|
280
274
|
c_data_type = Marty::DataGrid.convert_data_type(dgh['data_type'])
|
|
281
275
|
|
|
282
276
|
return vhash if String === c_data_type
|
|
283
277
|
|
|
284
|
-
res = vhash[
|
|
278
|
+
res = vhash['result']
|
|
285
279
|
|
|
286
|
-
v =
|
|
280
|
+
v = case
|
|
287
281
|
when Marty::PgEnum === res
|
|
288
282
|
c_data_type.find_by_name(res)
|
|
289
283
|
when Marty::DataGrid == c_data_type
|
|
@@ -291,10 +285,10 @@ class Marty::DataGrid < Marty::Base
|
|
|
291
285
|
Marty::DataGrid.lookup_h(pt, res) :
|
|
292
286
|
Marty::DataGrid.lookup(pt, res)
|
|
293
287
|
else
|
|
294
|
-
Marty::DataConversion.find_row(c_data_type, {
|
|
288
|
+
Marty::DataConversion.find_row(c_data_type, { 'name' => res }, pt)
|
|
295
289
|
end
|
|
296
290
|
|
|
297
|
-
return vhash.merge(
|
|
291
|
+
return vhash.merge('result' => v) unless (Marty::DataGrid == c_data_type &&
|
|
298
292
|
follow)
|
|
299
293
|
|
|
300
294
|
visited ||= []
|
|
@@ -309,24 +303,22 @@ class Marty::DataGrid < Marty::Base
|
|
|
309
303
|
end
|
|
310
304
|
|
|
311
305
|
def dir_infos(dir)
|
|
312
|
-
metadata.select {|inf| inf[
|
|
306
|
+
metadata.select { |inf| inf['dir'] == dir }
|
|
313
307
|
end
|
|
314
308
|
|
|
315
309
|
def self.export_keys(inf)
|
|
316
310
|
# should unify this with Marty::DataConversion.convert
|
|
317
311
|
|
|
318
|
-
type = inf[
|
|
312
|
+
type = inf['type']
|
|
319
313
|
klass = type.constantize unless INDEX_MAP[type]
|
|
320
314
|
|
|
321
|
-
inf[
|
|
322
|
-
|v|
|
|
323
|
-
|
|
315
|
+
inf['keys'].map do |v|
|
|
324
316
|
case type
|
|
325
|
-
when
|
|
317
|
+
when 'numrange', 'int4range'
|
|
326
318
|
Marty::Util.pg_range_to_human(v)
|
|
327
|
-
when
|
|
319
|
+
when 'boolean'
|
|
328
320
|
v.to_s
|
|
329
|
-
when
|
|
321
|
+
when 'string', 'integer'
|
|
330
322
|
v.map(&:to_s).join(ARRSEP) if v
|
|
331
323
|
else
|
|
332
324
|
# assume it's an AR class
|
|
@@ -350,46 +342,45 @@ class Marty::DataGrid < Marty::Base
|
|
|
350
342
|
|
|
351
343
|
def export_array
|
|
352
344
|
# add data type metadata row if not default
|
|
353
|
-
dt_row = lenient ? [
|
|
345
|
+
dt_row = lenient ? ['lenient'] : []
|
|
354
346
|
dt_row << data_type unless [nil, DEFAULT_DATA_TYPE].member?(data_type)
|
|
355
347
|
|
|
356
348
|
meta_rows = dt_row.empty? ? [] : [[dt_row.join(' ')]]
|
|
357
349
|
|
|
358
|
-
meta_rows += metadata.map
|
|
359
|
-
[inf[
|
|
360
|
-
|
|
350
|
+
meta_rows += metadata.map do |inf|
|
|
351
|
+
[inf['attr'], inf['type'], inf['dir'], inf['rs_keep'] || '']
|
|
352
|
+
end
|
|
361
353
|
|
|
362
|
-
v_infos, h_infos = dir_infos(
|
|
354
|
+
v_infos, h_infos = dir_infos('v'), dir_infos('h')
|
|
363
355
|
|
|
364
|
-
h_key_rows = h_infos.map
|
|
365
|
-
[nil]*v_infos.count + self.class.export_keys(inf)
|
|
366
|
-
|
|
356
|
+
h_key_rows = h_infos.map do |inf|
|
|
357
|
+
[nil] * v_infos.count + self.class.export_keys(inf)
|
|
358
|
+
end
|
|
367
359
|
|
|
368
360
|
transposed_v_keys = v_infos.empty? ? [[]] :
|
|
369
|
-
v_infos.map {|inf| self.class.export_keys(inf)}.transpose
|
|
361
|
+
v_infos.map { |inf| self.class.export_keys(inf) }.transpose
|
|
370
362
|
|
|
371
|
-
data_rows = transposed_v_keys.each_with_index.map
|
|
372
|
-
keys + (
|
|
373
|
-
|
|
363
|
+
data_rows = transposed_v_keys.each_with_index.map do |keys, i|
|
|
364
|
+
keys + (data[i] || [])
|
|
365
|
+
end
|
|
374
366
|
|
|
375
367
|
[meta_rows, h_key_rows, data_rows]
|
|
376
368
|
end
|
|
377
369
|
|
|
378
370
|
def export
|
|
379
|
-
|
|
380
|
-
return
|
|
371
|
+
# return null string when called from Netzke on add_in_form
|
|
372
|
+
return '' if metadata.nil? && data.nil?
|
|
381
373
|
|
|
382
|
-
|
|
374
|
+
meta_rows, h_key_rows, data_rows = export_array
|
|
383
375
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
376
|
+
Marty::DataExporter.
|
|
377
|
+
to_csv(meta_rows + [[]] + h_key_rows + data_rows,
|
|
378
|
+
'col_sep' => "\t",
|
|
387
379
|
).
|
|
388
|
-
|
|
380
|
+
gsub(/\"\"/, '') # remove "" to beautify output
|
|
389
381
|
end
|
|
390
382
|
|
|
391
|
-
delorean_fn :export, sig: 1 do
|
|
392
|
-
|os|
|
|
383
|
+
delorean_fn :export, sig: 1 do |os|
|
|
393
384
|
dg = find(os.id)
|
|
394
385
|
dg.export
|
|
395
386
|
end
|
|
@@ -398,27 +389,28 @@ class Marty::DataGrid < Marty::Base
|
|
|
398
389
|
return unless v
|
|
399
390
|
|
|
400
391
|
case type
|
|
401
|
-
when
|
|
392
|
+
when 'numrange', 'int4range'
|
|
402
393
|
Marty::Util.human_to_pg_range(v)
|
|
403
|
-
when
|
|
394
|
+
when 'integer'
|
|
404
395
|
v.split(ARRSEP).map do |val|
|
|
405
396
|
Integer(val) rescue raise "invalid integer: #{val}"
|
|
406
397
|
end.uniq.sort
|
|
407
|
-
when
|
|
398
|
+
when 'float'
|
|
408
399
|
v.split(ARRSEP).map do |val|
|
|
409
400
|
Float(val) rescue raise "invalid float: #{val}"
|
|
410
401
|
end.uniq.sort
|
|
411
|
-
when
|
|
402
|
+
when 'string'
|
|
412
403
|
res = v.split(ARRSEP).uniq.sort
|
|
413
|
-
raise
|
|
414
|
-
res.any? {|x| x != x.strip}
|
|
415
|
-
raise
|
|
404
|
+
raise 'leading/trailing spaces in elements not allowed' if
|
|
405
|
+
res.any? { |x| x != x.strip }
|
|
406
|
+
raise '0-length string not allowed' if res.any?(&:empty?)
|
|
407
|
+
|
|
416
408
|
res
|
|
417
|
-
when
|
|
409
|
+
when 'boolean'
|
|
418
410
|
case v.downcase
|
|
419
|
-
when
|
|
411
|
+
when 'true', 't'
|
|
420
412
|
true
|
|
421
|
-
when
|
|
413
|
+
when 'false', 'f'
|
|
422
414
|
false
|
|
423
415
|
else
|
|
424
416
|
raise "bad boolean #{v}"
|
|
@@ -427,8 +419,7 @@ class Marty::DataGrid < Marty::Base
|
|
|
427
419
|
# AR class
|
|
428
420
|
# FIXME: won't work if the obj identifier (name) has ARRSEP
|
|
429
421
|
res = v.split(ARRSEP).uniq
|
|
430
|
-
res.each do
|
|
431
|
-
|k|
|
|
422
|
+
res.each do |k|
|
|
432
423
|
begin
|
|
433
424
|
# check to see if class instance actually exists
|
|
434
425
|
Marty::DataGrid.
|
|
@@ -442,17 +433,14 @@ class Marty::DataGrid < Marty::Base
|
|
|
442
433
|
end
|
|
443
434
|
|
|
444
435
|
def self.maybe_get_klass(type)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
rescue NameError
|
|
436
|
+
type.constantize unless INDEX_MAP[type] || type == 'float'
|
|
437
|
+
rescue NameError
|
|
448
438
|
raise "unknown header type/klass: #{type}"
|
|
449
|
-
end
|
|
450
439
|
end
|
|
451
440
|
|
|
452
441
|
def self.parse_keys(pt, keys, type)
|
|
453
442
|
klass = maybe_get_klass(type)
|
|
454
|
-
keys.map do
|
|
455
|
-
|v|
|
|
443
|
+
keys.map do |v|
|
|
456
444
|
parse_fvalue(pt, v, type, klass)
|
|
457
445
|
end
|
|
458
446
|
end
|
|
@@ -465,9 +453,9 @@ class Marty::DataGrid < Marty::Base
|
|
|
465
453
|
pt ||= 'infinity'
|
|
466
454
|
|
|
467
455
|
rows = CSV.new(grid_text, options).to_a
|
|
468
|
-
blank_index = rows.find_index {|x| x.all?(&:nil?)}
|
|
456
|
+
blank_index = rows.find_index { |x| x.all?(&:nil?) }
|
|
469
457
|
|
|
470
|
-
raise
|
|
458
|
+
raise 'must have a blank row separating metadata' unless
|
|
471
459
|
blank_index
|
|
472
460
|
|
|
473
461
|
raise "can't import grid with trailing blank column" if
|
|
@@ -483,61 +471,58 @@ class Marty::DataGrid < Marty::Base
|
|
|
483
471
|
dts = dt.split
|
|
484
472
|
raise "bad data type '#{dt}'" if dts.count > 2
|
|
485
473
|
|
|
486
|
-
lenient = dts.delete
|
|
474
|
+
lenient = dts.delete 'lenient'
|
|
487
475
|
data_type = dts.first
|
|
488
476
|
end
|
|
489
477
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
raise "metadata elements must include attr/type/dir" unless
|
|
478
|
+
rows_for_metadata = rows[(data_type || lenient ? 1 : 0)...blank_index]
|
|
479
|
+
metadata = rows_for_metadata.map do |attr, type, dir, rs_keep, key|
|
|
480
|
+
raise 'metadata elements must include attr/type/dir' unless
|
|
494
481
|
attr && type && dir
|
|
495
|
-
raise "bad dir #{dir}" unless [
|
|
482
|
+
raise "bad dir #{dir}" unless ['h', 'v'].member? dir
|
|
496
483
|
raise "unknown metadata type #{type}" unless
|
|
497
484
|
Marty::DataGrid.type_to_index(type)
|
|
498
485
|
|
|
499
486
|
res = {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
487
|
+
'attr' => attr,
|
|
488
|
+
'type' => type,
|
|
489
|
+
'dir' => dir,
|
|
490
|
+
'keys' => key && parse_keys(pt, [key], type),
|
|
504
491
|
}
|
|
505
|
-
res[
|
|
492
|
+
res['rs_keep'] = rs_keep if rs_keep
|
|
506
493
|
res
|
|
507
494
|
end
|
|
508
495
|
|
|
509
|
-
v_infos = metadata.select {|inf| inf[
|
|
510
|
-
h_infos = metadata.select {|inf| inf[
|
|
496
|
+
v_infos = metadata.select { |inf| inf['dir'] == 'v' }
|
|
497
|
+
h_infos = metadata.select { |inf| inf['dir'] == 'h' }
|
|
511
498
|
|
|
512
499
|
# keys+data start right after blank_index
|
|
513
|
-
data_index = blank_index+1
|
|
500
|
+
data_index = blank_index + 1
|
|
514
501
|
|
|
515
502
|
# process horizontal key rows
|
|
516
|
-
h_infos.each_with_index do
|
|
517
|
-
|
|
503
|
+
h_infos.each_with_index do |inf, i|
|
|
504
|
+
row = rows[data_index + i]
|
|
518
505
|
|
|
519
|
-
row
|
|
520
|
-
|
|
521
|
-
raise "horiz. key row #{data_index+i} must include nil starting cells" if
|
|
506
|
+
raise "horiz. key row #{data_index + i} must include nil starting cells" if
|
|
522
507
|
row[0, v_infos.count].any?
|
|
523
508
|
|
|
524
|
-
inf[
|
|
509
|
+
inf['keys'] = parse_keys(pt, row[v_infos.count, row.count], inf['type'])
|
|
525
510
|
end
|
|
526
511
|
|
|
527
|
-
raise
|
|
528
|
-
h_infos.map {|inf| inf[
|
|
512
|
+
raise 'horiz. info keys length mismatch!' unless
|
|
513
|
+
h_infos.map { |inf| inf['keys'].length }.uniq.count <= 1
|
|
529
514
|
|
|
530
|
-
data_rows = rows[data_index+h_infos.count, rows.count]
|
|
515
|
+
data_rows = rows[data_index + h_infos.count, rows.count]
|
|
531
516
|
|
|
532
517
|
# process vertical key columns
|
|
533
|
-
v_key_cols = data_rows.map {|r| r[0, v_infos.count]}.transpose
|
|
518
|
+
v_key_cols = data_rows.map { |r| r[0, v_infos.count] }.transpose
|
|
534
519
|
|
|
535
520
|
v_infos.each_with_index do |inf, i|
|
|
536
|
-
inf[
|
|
521
|
+
inf['keys'] = parse_keys(pt, v_key_cols[i], inf['type'])
|
|
537
522
|
end
|
|
538
523
|
|
|
539
|
-
raise
|
|
540
|
-
v_infos.map {|inf| inf[
|
|
524
|
+
raise 'vert. info keys length mismatch!' unless
|
|
525
|
+
v_infos.map { |inf| inf['keys'].length }.uniq.count <= 1
|
|
541
526
|
|
|
542
527
|
c_data_type = Marty::DataGrid.convert_data_type(data_type)
|
|
543
528
|
|
|
@@ -548,18 +533,14 @@ class Marty::DataGrid < Marty::Base
|
|
|
548
533
|
if String === c_data_type
|
|
549
534
|
tsym = c_data_type.to_sym
|
|
550
535
|
|
|
551
|
-
data = data_rows.map do
|
|
552
|
-
|
|
553
|
-
r[v_infos.count, r.count].map do
|
|
554
|
-
|v|
|
|
536
|
+
data = data_rows.map do |r|
|
|
537
|
+
r[v_infos.count, r.count].map do |v|
|
|
555
538
|
Marty::DataConversion.convert(v, tsym) if v
|
|
556
539
|
end
|
|
557
540
|
end
|
|
558
541
|
else
|
|
559
|
-
data = data_rows.map do
|
|
560
|
-
|
|
561
|
-
r[v_infos.count, r.count].map do
|
|
562
|
-
|v|
|
|
542
|
+
data = data_rows.map do |r|
|
|
543
|
+
r[v_infos.count, r.count].map do |v|
|
|
563
544
|
next v if !v || Marty::DataGrid.
|
|
564
545
|
find_class_instance(pt, c_data_type, v)
|
|
565
546
|
|
|
@@ -571,9 +552,9 @@ class Marty::DataGrid < Marty::Base
|
|
|
571
552
|
[metadata, data, data_type, lenient]
|
|
572
553
|
end
|
|
573
554
|
|
|
574
|
-
def self.create_from_import(name, import_text, created_dt=nil)
|
|
555
|
+
def self.create_from_import(name, import_text, created_dt = nil)
|
|
575
556
|
metadata, data, data_type, lenient = parse(created_dt, import_text, {})
|
|
576
|
-
dg =
|
|
557
|
+
dg = new
|
|
577
558
|
dg.name = name
|
|
578
559
|
dg.data = data
|
|
579
560
|
dg.data_type = data_type
|
|
@@ -584,15 +565,16 @@ class Marty::DataGrid < Marty::Base
|
|
|
584
565
|
dg
|
|
585
566
|
end
|
|
586
567
|
|
|
587
|
-
def update_from_import(name, import_text, created_dt=nil)
|
|
588
|
-
|
|
589
|
-
|
|
568
|
+
def update_from_import(name, import_text, created_dt = nil)
|
|
569
|
+
new_metadata, data, data_type, lenient =
|
|
570
|
+
self.class.parse(created_dt, import_text, {})
|
|
590
571
|
|
|
591
572
|
self.name = name
|
|
592
573
|
self.data = data
|
|
593
574
|
self.data_type = data_type
|
|
594
575
|
self.lenient = !!lenient
|
|
595
|
-
|
|
576
|
+
# Otherwise changed will depend on order in hashes
|
|
577
|
+
self.metadata = new_metadata unless metadata == new_metadata
|
|
596
578
|
self.created_dt = created_dt if created_dt
|
|
597
579
|
save!
|
|
598
580
|
end
|
|
@@ -600,17 +582,13 @@ class Marty::DataGrid < Marty::Base
|
|
|
600
582
|
# FIXME: should be private
|
|
601
583
|
def build_index
|
|
602
584
|
# create indices for the metadata
|
|
603
|
-
metadata.each do
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
attr, type, keys = inf["attr"], inf["type"], inf["keys"]
|
|
585
|
+
metadata.each do |inf|
|
|
586
|
+
attr, type, keys = inf['attr'], inf['type'], inf['keys']
|
|
607
587
|
|
|
608
588
|
# find index class
|
|
609
589
|
idx_class = Marty::DataGrid.type_to_index(type)
|
|
610
590
|
|
|
611
|
-
keys.each_with_index do
|
|
612
|
-
|k, index|
|
|
613
|
-
|
|
591
|
+
keys.each_with_index do |k, index|
|
|
614
592
|
gi = idx_class.new
|
|
615
593
|
gi.attr = attr
|
|
616
594
|
gi.key = k
|
|
@@ -623,16 +601,16 @@ class Marty::DataGrid < Marty::Base
|
|
|
623
601
|
end
|
|
624
602
|
|
|
625
603
|
def self.modify_grid(params, metadata, data)
|
|
626
|
-
removes = [
|
|
604
|
+
removes = ['h', 'v'].each_with_object({}) { |dir, hash| hash[dir] = Set.new }
|
|
627
605
|
|
|
628
606
|
metadata_copy, data_copy = metadata.deep_dup, data.deep_dup
|
|
629
607
|
|
|
630
608
|
metadata_copy.each do |meta|
|
|
631
609
|
dir, keys, type, rs_keep = meta.values_at(
|
|
632
|
-
|
|
610
|
+
'dir', 'keys', 'type', 'rs_keep')
|
|
633
611
|
next unless rs_keep
|
|
634
612
|
|
|
635
|
-
if type ==
|
|
613
|
+
if type == 'numrange' || type == 'int4range'
|
|
636
614
|
modop, modvalparm = parse_bounds(rs_keep)
|
|
637
615
|
modval = params[modvalparm]
|
|
638
616
|
if modval
|
|
@@ -652,23 +630,23 @@ class Marty::DataGrid < Marty::Base
|
|
|
652
630
|
|
|
653
631
|
removes.reject! { |dir, set| set.empty? }
|
|
654
632
|
|
|
655
|
-
removes.each do
|
|
656
|
-
|dir
|
|
657
|
-
|
|
658
|
-
meta["keys"] = remove_indices(meta["keys"], removes[dir])
|
|
633
|
+
removes.each do |dir, set|
|
|
634
|
+
metadata_copy.select { |m| m['dir'] == dir }.each do |meta|
|
|
635
|
+
meta['keys'] = remove_indices(meta['keys'], removes[dir])
|
|
659
636
|
end
|
|
660
637
|
end
|
|
661
638
|
|
|
662
|
-
data_copy = remove_indices(data_copy, removes[
|
|
639
|
+
data_copy = remove_indices(data_copy, removes['v']) if removes['v']
|
|
663
640
|
|
|
664
641
|
data_copy.each_index do |index|
|
|
665
|
-
data_copy[index] = remove_indices(data_copy[index], removes[
|
|
666
|
-
end if removes[
|
|
642
|
+
data_copy[index] = remove_indices(data_copy[index], removes['h'])
|
|
643
|
+
end if removes['h']
|
|
667
644
|
|
|
668
645
|
[data_copy, metadata_copy]
|
|
669
646
|
end
|
|
670
647
|
|
|
671
648
|
private
|
|
649
|
+
|
|
672
650
|
def self.remove_indices(orig_array, inds)
|
|
673
651
|
orig_array.each_with_object([]).with_index do |(item, new_array), index|
|
|
674
652
|
new_array.push(item) unless inds.include?(index)
|
|
@@ -731,7 +709,6 @@ class Marty::DataGrid < Marty::Base
|
|
|
731
709
|
prune_a.push(index)
|
|
732
710
|
end
|
|
733
711
|
end
|
|
734
|
-
|
|
735
712
|
end
|
|
736
713
|
[prune_a, rewrite_a]
|
|
737
714
|
end
|
|
@@ -741,7 +718,6 @@ class Marty::DataGrid < Marty::Base
|
|
|
741
718
|
prune_a, rewrite_a, value = [], [], Array(val)
|
|
742
719
|
|
|
743
720
|
keys.each_with_index do |key, index|
|
|
744
|
-
|
|
745
721
|
# rewrite any nil (wildcard) keys in the dimension
|
|
746
722
|
# to be our 'to-keep' val(s)
|
|
747
723
|
if key.nil?
|