marty 1.0.44 → 1.0.46

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,6 +34,11 @@ class Marty::RpcController < ActionController::Base
34
34
  end
35
35
  end
36
36
 
37
+ def massage_message(msg)
38
+ m = %r|'#/([^']+)' of type ([^ ]+) matched the disallowed schema|.match(msg)
39
+ return msg unless m
40
+ "disallowed parameter '#{m[1]}' of type #{m[2]} was received"
41
+ end
37
42
  def _get_errors(errs)
38
43
  if errs.is_a?(Array)
39
44
  errs.map { |err| _get_errors(err) }
@@ -41,10 +46,11 @@ class Marty::RpcController < ActionController::Base
41
46
  if !errs.include?(:failed_attribute)
42
47
  errs.map { |k, v| _get_errors(v) }
43
48
  else
44
- fa, message, errors = errs.values_at(:failed_attribute,
45
- :message, :errors)
46
- (['AllOf','AnyOf','Not'].include?(fa) ? [] : [message]) +
47
- _get_errors(errors || {})
49
+ fa, fragment, message, errors = errs.values_at(:failed_attribute,
50
+ :fragment,
51
+ :message, :errors)
52
+ ((['AllOf','AnyOf','Not'].include?(fa) && fragment =='#/') ?
53
+ [] : [massage_message(message)]) + _get_errors(errors || {})
48
54
  end
49
55
  end
50
56
  end
@@ -1,5 +1,4 @@
1
1
  class Marty::DataGrid < Marty::Base
2
-
3
2
  # If data_type is nil, assume float
4
3
  DEFAULT_DATA_TYPE = "float"
5
4
 
@@ -146,102 +145,52 @@ class Marty::DataGrid < Marty::Base
146
145
  data_type.constantize rescue nil
147
146
  end
148
147
 
149
- def query_grid_dir(h, infos)
150
- return [0] if infos.empty?
151
-
152
- sqla = infos.map do |inf|
153
- type, attr = inf["type"], inf["attr"]
154
-
155
- next unless h.has_key?(attr)
156
-
157
- v = h[attr]
158
-
159
- ix_class = INDEX_MAP[type] || INDEX_MAP["string"]
160
-
161
- q = "key IS NULL"
162
-
163
- unless v.nil?
164
- q = case type
165
- when "boolean"
166
- "key = ?"
167
- when "numrange", "int4range"
168
- "key @> ?"
169
- else # "string", "integer", AR klass
170
- "key @> ARRAY[?]"
171
- end + " OR #{q}"
172
-
173
- # FIXME: very hacky -- need to cast numrange/intrange values or
174
- # we get errors from PG.
175
- v = case type
176
- when "string"
177
- v.to_s
178
- when "numrange"
179
- v.to_f
180
- when "int4range", "integer"
181
- v.to_i
182
- when "boolean"
183
- v
184
- else # AR class
185
- # FIXME: really hacky to hard-code "name". Used to
186
- # perform to_s which could lead ot strange failures when
187
- # model had no to_s defined.
188
- begin
189
- String === v ? v : v.name
190
- rescue NoMethodError
191
- raise "could not get name for #{v}"
192
- end
193
- end
194
- end
195
-
196
- # FIXME: could potentially order results by key NULLS LAST.
197
- # This would prefer more specific rather than wild card
198
- # solutions. However, would need to figure out how to preserve
199
- # ordering on subsequent INTERSECT operations.
200
- ix_class.
201
- select(:index).
202
- distinct.
203
- where(data_grid_id: group_id,
204
- created_dt: created_dt,
205
- attr: inf["attr"],
206
- ).
207
- where(q, v).to_sql
208
- end.compact
209
-
210
- sql = sqla.join(" INTERSECT ")
211
-
212
- self.class.connection.execute(sql).to_a.map { |hh| hh["index"].to_i }
148
+ def self.clear_dtcache
149
+ @@dtcache = {}
213
150
  end
214
151
 
215
- def lookup_grid_distinct(pt, h, return_grid_data=false, distinct=true)
216
- isets = ["h", "v"].each_with_object({}) do |dir, ih|
217
- infos = dir_infos(dir)
218
-
219
- ih[dir] = query_grid_dir(h, infos)
152
+ PLV_DT_FMT = "%Y-%m-%d %H:%M:%S.%N6"
220
153
 
221
- unless ih[dir] or return_grid_data
222
- attrs = infos.map { |inf| inf["attr"] }
223
-
224
- raise "#{dir} attrs not provided: %s" % attrs.join(',')
225
- end
226
-
227
- raise "Grid #{name}, (#{ih[dir].count}) #{dir} matches > 1." if
228
- distinct && ih[dir] && ih[dir].count > 1
154
+ def plv_lookup_grid_distinct(h_passed, ret_grid_data=false, distinct=true)
155
+ row_info = {"id" => id,
156
+ "group_id" => group_id,
157
+ "created_dt" =>
158
+ (@@dtcache ||= {})[created_dt] ||= created_dt.strftime(PLV_DT_FMT)
159
+ }
160
+ h = metadata.each_with_object({}) do |m, h|
161
+ attr = m["attr"]
162
+ inc = h_passed.fetch(attr, :__nf__)
163
+ next if inc == :__nf__
164
+ h[attr] = (defined? inc.name) ? inc.name : inc
229
165
  end
230
166
 
231
- # deterministic result: pick min index when there's a choice
232
- vi, hi = isets["v"].min, isets["h"].min if isets["v"] && isets["h"]
233
-
234
- raise "DataGrid lookup failed #{name}" unless (vi && hi) or lenient or
235
- return_grid_data
236
-
237
- modified_data, modified_metadata = modify_grid(h) if return_grid_data
167
+ fn = "lookup_grid_distinct"
168
+ hjson = "'#{h.to_json}'::JSONB"
169
+ rijson = "'#{row_info.to_json}'::JSONB"
170
+ params = "#{hjson}, #{rijson}, #{ret_grid_data}, #{distinct}"
171
+ sql = "SELECT #{fn}(#{params})"
172
+ raw = ActiveRecord::Base.connection.execute(sql)[0][fn]
173
+ res = JSON.parse(raw)
174
+
175
+ if res["error"]
176
+ msg = res["error"]
177
+ parms, sqls, ress, dg = res["error_extra"].values_at(
178
+ "params", "sql",
179
+ "results", "dg")
180
+ raise "DG #{name}: Error in PLV8 call: #{msg}\n"\
181
+ "params: #{parms}\n"\
182
+ "sqls: #{sqls}\n"\
183
+ "results: #{ress}\n"\
184
+ "dg: #{dg}\n"\
185
+ "ri: #{row_info}" if res["error"]
186
+ end
238
187
 
239
- return {
240
- "result" => (data[vi][hi] if vi && hi),
241
- "name" => name,
242
- "data" => (modified_data if return_grid_data),
243
- "metadata" => (modified_metadata if return_grid_data)
244
- }
188
+ if ret_grid_data
189
+ md, mmd = modify_grid(h_passed)
190
+ res["data"] = md
191
+ res["metadata"] = mmd
192
+ end
193
+ res
245
194
  end
246
195
 
247
196
  # FIXME: added for Apollo -- not sure where this belongs given that
@@ -252,8 +201,7 @@ class Marty::DataGrid < Marty::Base
252
201
  raise "bad DataGrid #{dg}" unless Marty::DataGrid === dg
253
202
  raise "non-hash arg #{h}" unless Hash === h
254
203
 
255
- res = dg.lookup_grid_distinct(pt, h, false, distinct)
256
-
204
+ res = dg.plv_lookup_grid_distinct(h, false, distinct)
257
205
  res["result"]
258
206
  end
259
207
 
@@ -283,7 +231,7 @@ class Marty::DataGrid < Marty::Base
283
231
  # "data" => <grid's data array>
284
232
  # "metadata" => <grid's metadata (array of hashes)>
285
233
 
286
- vhash = lookup_grid_distinct(pt, h, return_grid_data)
234
+ vhash = plv_lookup_grid_distinct(h, return_grid_data)
287
235
 
288
236
  return vhash if vhash["result"].nil? || !data_type
289
237
 
@@ -291,7 +239,11 @@ class Marty::DataGrid < Marty::Base
291
239
 
292
240
  return vhash if String === c_data_type
293
241
 
294
- v = Marty::DataGrid.find_class_instance(pt, c_data_type, vhash["result"])
242
+ res = vhash["result"]
243
+
244
+ v = Marty::PgEnum === res ?
245
+ c_data_type.find_by_name(res) :
246
+ Marty::DataConversion.find_row(c_data_type, {"name" => res}, pt)
295
247
 
296
248
  return vhash.merge({"result" => v}) unless (Marty::DataGrid === v && follow)
297
249
 
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%=Rails.application.class.parent_name%> Diagnostic</title>
5
+ <style type="text/css">
6
+ body { margin: auto;
7
+ margin-bottom: 50px;
8
+ border: 0;
9
+ background-color: #fff;
10
+ color: #333333;
11
+ font-family: Arial, Helvetica, Verdana, sans-serif;
12
+ font-weight: normal;
13
+ font-size: 9pt; }
14
+ table { border: none;
15
+ border-collapse: collapse;
16
+ display: inline-block;
17
+ margin: 0px 5px 0px 5px;
18
+ text-align: left;
19
+ }
20
+ th { padding: 9px;
21
+ border: none;
22
+ background-color: #d6d6d6 }
23
+ td { padding: 9px;
24
+ border: none;
25
+ }
26
+ th.error { background-color: #ff5555;
27
+ color: #ffffff;
28
+ }
29
+ tr.passed { background-color: #d0e9c6 }
30
+ tr.failed { background-color: #ff5555;
31
+ color: #ffffff
32
+ }
33
+ td.desc { font-size: 10pt; }
34
+ h1 { display: block;
35
+ margin: 0px auto 40px auto;
36
+ padding: 8px;
37
+ background-color: #1aaa55;
38
+ font-size: 18pt;
39
+ color: #ffffff;
40
+ line-height: 1.5em; }
41
+ h2 {text-align: center;}
42
+ h3.error {color: red;}
43
+ h3 {text-align: center;}
44
+ td.overflow { max-width: 350px;
45
+ overflow: auto; }
46
+ .wrapper {
47
+ overflow-x: auto;
48
+ white-space: nowrap;
49
+ display: block;}
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div style="text-align:center">
54
+ <h1><%=Rails.application.class.parent_name%> Diagnostic &#x26E8</h1>
55
+ <%== @result %>
56
+ </div>
57
+ </body>
58
+ </html>
data/config/routes.rb CHANGED
@@ -2,6 +2,5 @@ Marty::Engine.routes.draw do
2
2
  match via: [:get, :post], "rpc/:action(.:format)" => "rpc", as: :rpc
3
3
  get "job/:action" => "job", as: :job
4
4
  match via: [:get, :post], "report(.:format)" => "report#index", as: :report
5
- get 'diag/(:action)', controller: 'diagnostic'
6
- get 'diagnostic/(:action)', controller: 'diagnostic'
5
+ get 'diag', to: 'diagnostic#op'
7
6
  end
@@ -0,0 +1,16 @@
1
+ // PARAM: err JSONB
2
+ // RETURN: JSONB
3
+ var locre = /at.*[ (]([a-z][a-z0-9_]*[:][0-9]+)[:][0-9]+/i;
4
+ var stack = err.stack;
5
+ var res = '';
6
+ if (stack) {
7
+ var lines = stack.split('\\n');
8
+ for (i=0, len=lines.length; i<len; ++i) {
9
+ m = locre.exec(lines[i]);
10
+ if (m) {
11
+ res += m[1];
12
+ }
13
+ }
14
+ return { "error": `${res} ${err.message}` };
15
+ }
16
+ else return { "error": err };
@@ -0,0 +1,64 @@
1
+ // PARAM: h JSONB
2
+ // PARAM: row_info JSONB
3
+ // PARAM: return_grid_data boolean default false
4
+ // PARAM: dis boolean default false
5
+ // RETURN: JSONB
6
+ var sqls = []
7
+ var ress = []
8
+ try {
9
+ var query_dir = plv8.find_function('query_grid_dir');
10
+ var errinfo = plv8.find_function('errinfo');
11
+ var ih = {};
12
+ var sql = 'SELECT metadata, lenient, name, group_id, data FROM marty_data_grids WHERE id = $1'
13
+ var dg = plv8.execute(sql, [row_info['id']])[0];
14
+ var res;
15
+ ['h','v'].forEach(function(dir) {
16
+ var infos = dg["metadata"].filter(function(md) { return md["dir"] == dir; });
17
+ if (infos.length == 0)
18
+ {
19
+ ih[dir] = [0]
20
+ return
21
+ }
22
+ a = query_dir(h, infos, row_info);
23
+ sqls.push(a);
24
+ ih[dir] = []
25
+ if (a) {
26
+ res = plv8.execute(a[0], a[1]);
27
+ ress.push(res);
28
+ for (var j = 0; j < res.length; j++)
29
+ {
30
+ ih[dir].push(res[j]["index"])
31
+ }
32
+ }
33
+ if (dis && ih[dir].length > 1) {
34
+ throw Error("matches > 1");
35
+ }
36
+ });
37
+ if ((ih["v"].length == 0 || ih["h"].length == 0) &&
38
+ !dg['lenient'] && !return_grid_data) {
39
+ throw Error("Data Grid lookup failed");
40
+ }
41
+
42
+ vi = ih["v"].length > 0 ? Math.min.apply(9999, ih["v"]) : null
43
+ hi = ih["h"].length > 0 ? Math.min.apply(9999, ih["h"]) : null
44
+
45
+ var result = null;
46
+ if (vi!==null && hi!==null) {
47
+ result = dg["data"][vi][hi];
48
+ }
49
+ return { "result" : result,
50
+ "name" : dg["name"],
51
+ "data" : return_grid_data ? dg["data"] : null,
52
+ "metadata" : return_grid_data ? dg["metadata"] : null
53
+ };
54
+ } catch (err) {
55
+ ei = errinfo(err);
56
+
57
+ ei["error_extra"] = {}
58
+ ei["error_extra"]["sql"] = sqls;
59
+ ei["error_extra"]["results"] = ress;
60
+ ei["error_extra"]["params"] = h;
61
+ ei["error_extra"]["dg"] = dg;
62
+
63
+ return ei;
64
+ }
@@ -0,0 +1,61 @@
1
+ // PARAM: h JSONB
2
+ // PARAM: infos JSONB[]
3
+ // PARAM: row_info JSONB
4
+ // RETURN: JSONB
5
+ var getfilter = function(type, idx) {
6
+ switch(type) {
7
+ case "boolean":
8
+ return "key = $" + idx + " OR ";
9
+ case "numrange":
10
+ return "key @> $" + idx + "::numeric OR ";
11
+ case "int4range":
12
+ return "key @> $" + idx + "::integer OR ";
13
+ case "integer":
14
+ return "key @> ARRAY[$" + idx + "::integer] OR ";
15
+ default:
16
+ return "key @> ARRAY[$" + idx + "::text] OR ";
17
+ }
18
+ }
19
+
20
+ var temp = [];
21
+ var args = [];
22
+
23
+ var sql;
24
+ for (var sqlidx=1, i = 0; i < infos.length; i++) {
25
+ var type = infos[i]["type"];
26
+ var attr = infos[i]["attr"];
27
+ var v = h[attr];
28
+ if (!h.hasOwnProperty(attr)) {
29
+ //throw Error(`missing attr ${attr}`)
30
+ continue;
31
+ }
32
+ switch (type) {
33
+ case 'boolean':
34
+ case "numrange":
35
+ case "int4range":
36
+ case "integer":
37
+ tab = `marty_grid_index_${type}s`;
38
+ break;
39
+ default:
40
+ tab = 'marty_grid_index_strings';
41
+ };
42
+
43
+ sql = `SELECT DISTINCT index from ${tab} ` +
44
+ "WHERE data_grid_id = $" + sqlidx++ +
45
+ " AND created_dt = $" + sqlidx++ +
46
+ " AND attr = $" + sqlidx++ + ' ';
47
+
48
+ args.push(row_info["group_id"]);
49
+ args.push(row_info["created_dt"]);
50
+ args.push(attr);
51
+
52
+ if (v!==null) {
53
+ filt = getfilter(type, sqlidx++);
54
+ args.push(v);
55
+ } else filt = ''
56
+ sql += ' AND (' + filt + "key is NULL) ";
57
+
58
+ temp.push(sql);
59
+ }
60
+ if (temp ==[]) return null;
61
+ return [temp.join(" INTERSECT "), args];
@@ -0,0 +1,12 @@
1
+ class CreateDgPlv8V1Fns < ActiveRecord::Migration
2
+ def change
3
+ connection.execute <<-SQL
4
+ -- required to utilize plv8 extension
5
+ CREATE EXTENSION IF NOT EXISTS plv8;
6
+ SQL
7
+ marty_path = Gem.loaded_specs["marty"].full_gem_path
8
+ Dir.glob("#{marty_path}/db/js/*_v1.js") do |f|
9
+ connection.execute(Marty::Migrations.get_plv8_migration(f))
10
+ end
11
+ end
12
+ end
@@ -303,4 +303,24 @@ OUT
303
303
  "cols = #{cols}, act_cols = #{act_cols}")
304
304
  end.map(&:to_sym)
305
305
  end
306
+
307
+ def self.get_plv8_migration(file)
308
+ fnname = %r(/([^/]+)_v[0-9]+\.js\z).match(file)[1]
309
+ lines=File.readlines(file)
310
+ parts = lines.map do |line|
311
+ next [:param, $1] if %r(\A// PARAM[:] (.*)$).match(line)
312
+ next [:ret, $1] if %r(\A// RETURN[:] (.*)$).match(line)
313
+ [:body, line]
314
+ end.group_by(&:first)
315
+ args = parts[:param].map{ |(_,l)| l}.join(",\n")
316
+ ret = parts[:ret][0][1]
317
+ body = parts[:body].map{ |(_,l)| l}.join
318
+ <<EOT
319
+ CREATE OR REPLACE FUNCTION #{fnname} (
320
+ #{args}
321
+ ) RETURNS #{ret} AS $$
322
+ #{body}
323
+ $$ LANGUAGE plv8;
324
+ EOT
325
+ end
306
326
  end