marty 1.0.44 → 1.0.46

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