fastapi 0.1.25 → 0.1.26
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 +4 -4
- data/lib/fastapi.rb +388 -774
- data/lib/fastapi/active_record_extension.rb +5 -7
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5478a368c91cfadee25770397b95a7b24865166f
|
4
|
+
data.tar.gz: 6362f9b6f08b3f0111ae69e3ac5695c4bf96cc8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da97e14f324c2772288c78626102adb6ce2fb9b0a4db07d678a5b3dbd24fd80a77bfb9d5e573322c81646868481e0d03e84d0d23ccb38535d5d1e75f83dfd01f
|
7
|
+
data.tar.gz: f885f7f223dfea56a927167c38acd8cea4b45e1b2166671f1cce6eeab8b28daaa60e98053bec391f917b073619f0ade693ec6d0233fc0a955405c66a8b5d060d
|
data/lib/fastapi.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
1
|
require 'oj'
|
2
|
-
require 'fastapi/active_record_extension
|
2
|
+
require 'fastapi/active_record_extension'
|
3
3
|
|
4
4
|
class FastAPI
|
5
5
|
|
6
|
-
@@result_types = {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
'is_null',
|
23
|
-
'not_null',
|
24
|
-
]
|
6
|
+
@@result_types = { single: 0, multiple: 1 }
|
7
|
+
|
8
|
+
@@api_comparator_list = %w(
|
9
|
+
is
|
10
|
+
not
|
11
|
+
gt
|
12
|
+
gte
|
13
|
+
lt
|
14
|
+
lte
|
15
|
+
in
|
16
|
+
not_in
|
17
|
+
contains
|
18
|
+
icontains
|
19
|
+
is_null
|
20
|
+
not_null
|
21
|
+
)
|
25
22
|
|
26
23
|
def initialize(model)
|
27
24
|
@model = model
|
@@ -40,11 +37,9 @@ class FastAPI
|
|
40
37
|
# @param fields [Array] an array containing fields to whitelist for the SQL query. Can also pass in fields as arguments.
|
41
38
|
# @return [FastAPI] the current instance
|
42
39
|
def whitelist(fields = [])
|
43
|
-
|
44
|
-
@whitelist_fields.concat fields
|
40
|
+
@whitelist_fields.concat(fields)
|
45
41
|
|
46
42
|
self
|
47
|
-
|
48
43
|
end
|
49
44
|
|
50
45
|
# Create and execute an optimized SQL query based on specified filters
|
@@ -53,27 +48,13 @@ class FastAPI
|
|
53
48
|
# @param meta [Hash] a hash containing custom metadata
|
54
49
|
# @return [FastAPI] the current instance
|
55
50
|
def filter(filters = {}, meta = {}, safe = false)
|
56
|
-
|
57
51
|
result = fastapi_query(filters, safe)
|
58
52
|
|
59
|
-
metadata
|
60
|
-
|
61
|
-
meta.each do |key, value|
|
62
|
-
metadata[key] = value
|
63
|
-
end
|
64
|
-
|
65
|
-
metadata[:total] = result[:total]
|
66
|
-
metadata[:offset] = result[:offset]
|
67
|
-
metadata[:count] = result[:count]
|
68
|
-
metadata[:error] = result[:error]
|
69
|
-
|
70
|
-
@metadata = metadata
|
71
|
-
@data = result[:data]
|
72
|
-
|
53
|
+
@metadata = meta.merge(result.slice(:total, :offset, :count, :error))
|
54
|
+
@data = result[:data]
|
73
55
|
@result_type = @@result_types[:multiple]
|
74
56
|
|
75
57
|
self
|
76
|
-
|
77
58
|
end
|
78
59
|
|
79
60
|
# Create and execute an optimized SQL query based on specified filters.
|
@@ -83,11 +64,7 @@ class FastAPI
|
|
83
64
|
# @param meta [Hash] a hash containing custom metadata
|
84
65
|
# @return [FastAPI] the current instance
|
85
66
|
def safe_filter(filters = {}, meta = {})
|
86
|
-
|
87
67
|
filter(filters, meta, true)
|
88
|
-
|
89
|
-
self
|
90
|
-
|
91
68
|
end
|
92
69
|
|
93
70
|
# Create and execute an optimized SQL query based on specified object id.
|
@@ -97,15 +74,13 @@ class FastAPI
|
|
97
74
|
# @param meta [Hash] a hash containing custom metadata
|
98
75
|
# @return [FastAPI] the current instance
|
99
76
|
def fetch(id, meta = {})
|
77
|
+
filter({ id: id }, meta)
|
100
78
|
|
101
|
-
|
102
|
-
|
103
|
-
if @metadata[:total] == 0
|
104
|
-
@metadata[:error] = {message: @model.to_s + ' id does not exist'}
|
79
|
+
if @metadata[:total].zero?
|
80
|
+
@metadata[:error] = { message: "#{@model} id does not exist" }
|
105
81
|
end
|
106
82
|
|
107
83
|
self
|
108
|
-
|
109
84
|
end
|
110
85
|
|
111
86
|
# Returns the data from the most recently executed `filter` or `fetch` call.
|
@@ -140,10 +115,7 @@ class FastAPI
|
|
140
115
|
#
|
141
116
|
# @return [Hash] available data and metadata
|
142
117
|
def to_hash
|
143
|
-
{
|
144
|
-
meta: @metadata,
|
145
|
-
data: @data
|
146
|
-
}
|
118
|
+
{ meta: @metadata, data: @data }
|
147
119
|
end
|
148
120
|
|
149
121
|
# Intended to return the final API response
|
@@ -157,24 +129,11 @@ class FastAPI
|
|
157
129
|
#
|
158
130
|
# @return [String] JSON data and metadata
|
159
131
|
def spoof(data = [], meta = {})
|
132
|
+
meta[:total] ||= data.count
|
133
|
+
meta[:count] ||= data.count
|
134
|
+
meta[:offset] ||= 0
|
160
135
|
|
161
|
-
|
162
|
-
meta[:total] = data.count
|
163
|
-
end
|
164
|
-
|
165
|
-
if not meta.has_key? :offset
|
166
|
-
meta[:offset] = 0
|
167
|
-
end
|
168
|
-
|
169
|
-
if not meta.has_key? :count
|
170
|
-
meta[:count] = data.count
|
171
|
-
end
|
172
|
-
|
173
|
-
Oj.dump({
|
174
|
-
meta: meta,
|
175
|
-
data: data
|
176
|
-
}, mode: :compat)
|
177
|
-
|
136
|
+
Oj.dump({ meta: meta, data: data }, mode: :compat)
|
178
137
|
end
|
179
138
|
|
180
139
|
# Returns a JSONified string representing a rejected API response with invalid fields parameters
|
@@ -192,7 +151,7 @@ class FastAPI
|
|
192
151
|
fields: fields
|
193
152
|
}
|
194
153
|
},
|
195
|
-
data: []
|
154
|
+
data: []
|
196
155
|
}, mode: :compat)
|
197
156
|
end
|
198
157
|
|
@@ -210,878 +169,533 @@ class FastAPI
|
|
210
169
|
message: message.to_s
|
211
170
|
}
|
212
171
|
},
|
213
|
-
data: []
|
172
|
+
data: []
|
214
173
|
}, mode: :compat)
|
215
174
|
end
|
216
175
|
|
217
176
|
private
|
218
177
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
not ActiveRecord::Base.connection.instance_of? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
223
|
-
raise 'Fast API only supports PostgreSQL at this time'
|
224
|
-
end
|
225
|
-
|
226
|
-
offset = 0
|
227
|
-
count = 500
|
228
|
-
order = nil
|
229
|
-
|
230
|
-
if filters.has_key? :__offset
|
231
|
-
offset = filters[:__offset].to_i
|
232
|
-
filters.delete(:__offset)
|
233
|
-
end
|
234
|
-
|
235
|
-
if filters.has_key? :__count
|
236
|
-
count = [1, [500, filters[:__count].to_i].min].max
|
237
|
-
filters.delete(:__count)
|
238
|
-
end
|
239
|
-
|
240
|
-
begin
|
241
|
-
prepared_data = api_generate_sql(filters, offset, count, safe)
|
242
|
-
rescue Exception => error
|
243
|
-
return {
|
244
|
-
data: [],
|
245
|
-
total: 0,
|
246
|
-
count: 0,
|
247
|
-
offset: offset,
|
248
|
-
error: {message: error.message}
|
249
|
-
}
|
250
|
-
end
|
178
|
+
def clamp(value, min, max)
|
179
|
+
[min, value, max].sort[1]
|
180
|
+
end
|
251
181
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
model_lookup[key] = {
|
256
|
-
model: model,
|
257
|
-
fields: model.fastapi_fields_sub,
|
258
|
-
types: model.fastapi_fields_sub.map { |field| (columns_hash.has_key? field.to_s) ? columns_hash[field.to_s].type : nil },
|
259
|
-
}
|
260
|
-
end
|
182
|
+
def error(offset, message)
|
183
|
+
{ data: [], total: 0, count: 0, offset: offset, error: { message: message } }
|
184
|
+
end
|
261
185
|
|
262
|
-
|
263
|
-
|
264
|
-
begin
|
265
|
-
count_result = ActiveRecord::Base.connection.execute(prepared_data[:count_query])
|
266
|
-
result = ActiveRecord::Base.connection.execute(prepared_data[:query])
|
267
|
-
rescue
|
268
|
-
return {
|
269
|
-
data: [],
|
270
|
-
total: 0,
|
271
|
-
count: 0,
|
272
|
-
offset: offset,
|
273
|
-
error: {message: 'Query failed'}
|
274
|
-
}
|
275
|
-
end
|
186
|
+
def fastapi_query(filters = {}, safe = false)
|
276
187
|
|
277
|
-
|
188
|
+
unless ActiveRecord::ConnectionAdapters.constants.include?(:PostgreSQLAdapter) &&
|
189
|
+
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
190
|
+
fail 'FastAPI only supports PostgreSQL at this time.'
|
191
|
+
end
|
278
192
|
|
279
|
-
|
193
|
+
offset = filters.delete(:__offset).try(:to_i) || 0
|
194
|
+
cnt = filters.delete(:__count).try(:to_i) || 500
|
195
|
+
count = clamp(cnt, 1, 500)
|
280
196
|
|
281
|
-
|
282
|
-
|
197
|
+
begin
|
198
|
+
prepared_data = api_generate_sql(filters, offset, count, safe)
|
199
|
+
rescue StandardError => exception
|
200
|
+
return error(offset, exception.message)
|
201
|
+
end
|
283
202
|
|
284
|
-
|
203
|
+
model_lookup = prepared_data[:models].each_with_object({}) do |(key, model), lookup|
|
204
|
+
columns = model.columns_hash
|
205
|
+
lookup[key] = {
|
206
|
+
model: model,
|
207
|
+
fields: model.fastapi_fields_sub,
|
208
|
+
types: model.fastapi_fields_sub.map { |field| columns[field.to_s].try(:type) }
|
209
|
+
}
|
210
|
+
end
|
285
211
|
|
286
|
-
|
287
|
-
|
288
|
-
|
212
|
+
begin
|
213
|
+
count_result = ActiveRecord::Base.connection.execute(prepared_data[:count_query])
|
214
|
+
result = ActiveRecord::Base.connection.execute(prepared_data[:query])
|
215
|
+
rescue
|
216
|
+
error(offset, 'Query failed')
|
217
|
+
end
|
289
218
|
|
290
|
-
|
291
|
-
split_index = field.rindex('__')
|
219
|
+
total_size = count_result.values.size > 0 ? count_result.values[0][0].to_i : 0
|
292
220
|
|
293
|
-
|
221
|
+
fields = result.fields
|
222
|
+
rows = result.values
|
294
223
|
|
295
|
-
|
296
|
-
|
297
|
-
|
224
|
+
dataset = rows.each_with_object([]) do |row, data|
|
225
|
+
datum = row.each_with_object({}).with_index do |(val, current), index|
|
226
|
+
field = fields[index]
|
227
|
+
split_index = field.rindex('__')
|
298
228
|
|
299
|
-
|
300
|
-
val,
|
301
|
-
model_lookup[field_sym][:fields],
|
302
|
-
model_lookup[field_sym][:types]
|
303
|
-
)
|
229
|
+
if field[0..7] == '__many__'
|
304
230
|
|
305
|
-
|
231
|
+
field = field[8..-1]
|
232
|
+
field_sym = field.to_sym
|
233
|
+
model = model_lookup[field_sym]
|
306
234
|
|
307
|
-
|
308
|
-
field = field[split_index + 2..-1]
|
309
|
-
model = model_lookup[obj_name][:model]
|
235
|
+
current[field_sym] = parse_many(val, model[:fields], model[:types])
|
310
236
|
|
311
|
-
|
312
|
-
currow[obj_name] = {}
|
313
|
-
end
|
237
|
+
elsif split_index
|
314
238
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
(model.columns_hash[field].respond_to?('array') and model.columns_hash[field].array)
|
319
|
-
)
|
239
|
+
obj_name = field[0..split_index - 1].to_sym
|
240
|
+
field = field[split_index + 2..-1]
|
241
|
+
model = model_lookup[obj_name][:model]
|
320
242
|
|
321
|
-
|
243
|
+
current[obj_name] ||= {}
|
322
244
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
(@model.columns_hash[field].respond_to?('array') and @model.columns_hash[field].array)
|
327
|
-
)
|
245
|
+
current[obj_name][field.to_sym] = api_convert_type(val,
|
246
|
+
model.columns_hash[field].type,
|
247
|
+
(model.columns_hash[field].respond_to?('array') && model.columns_hash[field].array))
|
328
248
|
|
329
|
-
|
249
|
+
elsif @model.columns_hash[field]
|
330
250
|
|
251
|
+
current[field.to_sym] = api_convert_type(val,
|
252
|
+
@model.columns_hash[field].type,
|
253
|
+
(@model.columns_hash[field].respond_to?('array') && @model.columns_hash[field].array))
|
331
254
|
end
|
332
|
-
|
333
|
-
dataset[index] = currow
|
334
|
-
|
335
255
|
end
|
336
|
-
|
337
|
-
my_end = Time.now()
|
338
|
-
|
339
|
-
# puts dataset.size.to_s + '-length array parsed in ' + (my_end - start).to_s
|
340
|
-
|
341
|
-
{
|
342
|
-
data: dataset,
|
343
|
-
total: total_size,
|
344
|
-
count: dataset.size,
|
345
|
-
offset: offset,
|
346
|
-
error: nil
|
347
|
-
}
|
348
|
-
|
256
|
+
data << datum
|
349
257
|
end
|
258
|
+
{ data: dataset, total: total_size, count: dataset.size, offset: offset, error: nil }
|
259
|
+
end
|
350
260
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
unless str.is_a? String
|
356
|
-
return []
|
261
|
+
def parse_many(str, fields, types)
|
262
|
+
Oj.load(str).map do |row|
|
263
|
+
row.values.each_with_object({}).with_index do |(value, values), index|
|
264
|
+
values[fields[index]] = api_convert_type(value, types[index])
|
357
265
|
end
|
266
|
+
end
|
267
|
+
end
|
358
268
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
values = []
|
363
|
-
|
364
|
-
i = str.index('{')
|
365
|
-
|
366
|
-
return values unless i
|
269
|
+
def api_comparison(comparator, value, field, type, is_array)
|
270
|
+
field_string = is_array ? "ANY(#{field})" : field
|
367
271
|
|
368
|
-
|
272
|
+
if comparator == 'is'
|
369
273
|
|
370
|
-
|
274
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' = ' + field_string
|
371
275
|
|
372
|
-
|
276
|
+
elsif comparator == 'not'
|
373
277
|
|
374
|
-
|
278
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' <> ' + field_string
|
375
279
|
|
376
|
-
|
280
|
+
elsif comparator == 'gt'
|
377
281
|
|
378
|
-
|
282
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' < ' + field_string
|
379
283
|
|
380
|
-
|
381
|
-
nextIndex = str.index('"', i)
|
284
|
+
elsif comparator == 'gte'
|
382
285
|
|
383
|
-
|
286
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' <= ' + field_string
|
384
287
|
|
385
|
-
|
386
|
-
while str[nextIndex - j] == '\\'
|
387
|
-
j += 1
|
388
|
-
end
|
288
|
+
elsif comparator == 'lt'
|
389
289
|
|
390
|
-
|
391
|
-
break
|
392
|
-
end
|
290
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' > ' + field_string
|
393
291
|
|
394
|
-
|
292
|
+
elsif comparator == 'lte'
|
395
293
|
|
396
|
-
|
294
|
+
ActiveRecord::Base.connection.quote(value.to_s) + ' >= ' + field_string
|
397
295
|
|
398
|
-
|
296
|
+
elsif comparator == 'in' || comparator == 'not_in'
|
399
297
|
|
400
|
-
|
298
|
+
unless value.is_a?(Array)
|
401
299
|
|
300
|
+
if value.is_a?(Range)
|
301
|
+
value = value.to_a
|
402
302
|
else
|
403
|
-
|
404
|
-
if c == ','
|
405
|
-
|
406
|
-
values.push nil
|
407
|
-
i += 1
|
408
|
-
next
|
409
|
-
|
410
|
-
end
|
411
|
-
|
412
|
-
parensIndex = str.index('}', i)
|
413
|
-
nextIndex = str.index(',', i)
|
414
|
-
|
415
|
-
if nextIndex.nil? or nextIndex > parensIndex
|
416
|
-
|
417
|
-
values.push str[i...parensIndex]
|
418
|
-
break
|
419
|
-
|
420
|
-
end
|
421
|
-
|
422
|
-
values.push str[i...nextIndex]
|
423
|
-
|
424
|
-
i = nextIndex + 1
|
425
|
-
|
303
|
+
value = [value.to_s]
|
426
304
|
end
|
427
|
-
|
428
305
|
end
|
429
306
|
|
430
|
-
|
431
|
-
|
432
|
-
end
|
433
|
-
|
434
|
-
def parse_many(str, fields = [], types = [])
|
435
|
-
|
436
|
-
unless str.is_a? String
|
437
|
-
return []
|
438
|
-
end
|
439
|
-
|
440
|
-
rows = []
|
441
|
-
cur_row = {}
|
442
|
-
entry_index = 0
|
443
|
-
|
444
|
-
i = 0
|
445
|
-
len = str.length
|
446
|
-
|
447
|
-
i = str.index('(')
|
448
|
-
|
449
|
-
if not i
|
450
|
-
return rows
|
451
|
-
end
|
452
|
-
|
453
|
-
i = i + 1
|
454
|
-
|
455
|
-
while i < len
|
456
|
-
|
457
|
-
c = str[i]
|
458
|
-
|
459
|
-
if c == ')'
|
460
|
-
|
461
|
-
rows << cur_row
|
462
|
-
cur_row = {}
|
463
|
-
entry_index = 0
|
464
|
-
i = i + 3
|
465
|
-
|
466
|
-
elsif c == '"'
|
467
|
-
|
468
|
-
i = i + 1
|
469
|
-
nextIndex = str.index('"', i)
|
470
|
-
|
471
|
-
while str[nextIndex - 1] == '\\'
|
472
|
-
|
473
|
-
j = 1
|
474
|
-
while str[nextIndex - j] == '\\'
|
475
|
-
j = j + 1
|
476
|
-
end
|
477
|
-
|
478
|
-
if j & 1 == 1
|
479
|
-
break
|
480
|
-
end
|
481
|
-
|
482
|
-
nextIndex = str.index('"', nextIndex + 1)
|
483
|
-
|
484
|
-
end
|
485
|
-
|
486
|
-
cur_row[fields[entry_index]] = api_convert_type(str[i...nextIndex], types[entry_index])
|
307
|
+
if is_array
|
487
308
|
|
488
|
-
|
309
|
+
type_convert = {
|
310
|
+
boolean: '::boolean',
|
311
|
+
integer: '::integer',
|
312
|
+
float: '::float',
|
313
|
+
string: '::varchar'
|
314
|
+
}[type]
|
489
315
|
|
490
|
-
|
491
|
-
|
492
|
-
elsif c == ','
|
493
|
-
|
494
|
-
i = i + 1
|
495
|
-
cur_row[fields[entry_index]] = nil
|
496
|
-
entry_index = entry_index + 1
|
316
|
+
type_convert = '::text' if type.nil?
|
497
317
|
|
318
|
+
if comparator == 'in'
|
319
|
+
'ARRAY[' + (value.map { |val| ActiveRecord::Base.connection.quote(val.to_s) }).join(',') + ']' + type_convert + '[] && ' + field
|
498
320
|
else
|
321
|
+
'NOT ARRAY[' + (value.map { |val| ActiveRecord::Base.connection.quote(val.to_s) }).join(',') + ']' + type_convert + '[] && ' + field
|
322
|
+
end
|
323
|
+
else
|
499
324
|
|
500
|
-
|
501
|
-
|
325
|
+
if comparator == 'in'
|
326
|
+
field + ' IN(' + (value.map { |val| ActiveRecord::Base.connection.quote(val.to_s) }).join(',') + ')'
|
327
|
+
else
|
328
|
+
field + ' NOT IN(' + (value.map { |val| ActiveRecord::Base.connection.quote(val.to_s) }).join(',') + ')'
|
329
|
+
end
|
330
|
+
end
|
502
331
|
|
503
|
-
|
504
|
-
nextIndex = parensIndex
|
505
|
-
end
|
332
|
+
elsif comparator == 'contains'
|
506
333
|
|
507
|
-
|
508
|
-
cur_row[fields[entry_index]] = nil
|
509
|
-
else
|
510
|
-
cur_row[fields[entry_index]] = api_convert_type(str[i...nextIndex], types[entry_index])
|
511
|
-
end
|
334
|
+
field_string + ' LIKE \'%\' || ' + ActiveRecord::Base.connection.quote(value.to_s) + ' || \'%\''
|
512
335
|
|
513
|
-
|
336
|
+
elsif comparator == 'icontains'
|
514
337
|
|
515
|
-
|
516
|
-
rows << cur_row
|
517
|
-
cur_row = {}
|
518
|
-
entry_index = 0
|
519
|
-
i = nextIndex + 3
|
520
|
-
else
|
521
|
-
i = nextIndex + 1
|
522
|
-
end
|
338
|
+
field_string + ' ILIKE \'%\' || ' + ActiveRecord::Base.connection.quote(value.to_s) + ' || \'%\''
|
523
339
|
|
524
|
-
|
340
|
+
elsif comparator == 'is_null'
|
525
341
|
|
526
|
-
|
342
|
+
"#{field_string} IS NULL"
|
527
343
|
|
528
|
-
|
344
|
+
elsif comparator == 'not_null'
|
529
345
|
|
346
|
+
"#{field_string} IS NOT NULL"
|
530
347
|
end
|
348
|
+
end
|
531
349
|
|
350
|
+
def api_convert_type(val, type, is_array = false)
|
351
|
+
if val && is_array
|
352
|
+
Oj.load(val).map { |inner_value| api_convert_value(inner_value, type) }
|
353
|
+
else
|
354
|
+
api_convert_value(val, type)
|
355
|
+
end
|
356
|
+
end
|
532
357
|
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
358
|
+
def api_convert_value(val, type)
|
359
|
+
if val
|
360
|
+
case type
|
361
|
+
when :integer
|
362
|
+
val.to_i
|
363
|
+
when :float
|
364
|
+
val.to_f
|
365
|
+
when :boolean
|
366
|
+
{ 't' => true, 'f' => false }[val]
|
537
367
|
else
|
538
|
-
|
368
|
+
val
|
539
369
|
end
|
370
|
+
end
|
371
|
+
end
|
540
372
|
|
541
|
-
|
542
|
-
|
543
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' = ' + field_string
|
544
|
-
|
545
|
-
elsif comparator == 'not'
|
546
|
-
|
547
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' <> ' + field_string
|
548
|
-
|
549
|
-
elsif comparator == 'gt'
|
550
|
-
|
551
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' < ' + field_string
|
552
|
-
|
553
|
-
elsif comparator == 'gte'
|
554
|
-
|
555
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' <= ' + field_string
|
556
|
-
|
557
|
-
elsif comparator == 'lt'
|
558
|
-
|
559
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' > ' + field_string
|
560
|
-
|
561
|
-
elsif comparator == 'lte'
|
562
|
-
|
563
|
-
ActiveRecord::Base.connection.quote(value.to_s) + ' >= ' + field_string
|
564
|
-
|
565
|
-
elsif comparator == 'in' or comparator == 'not_in'
|
566
|
-
|
567
|
-
if not value.is_a? Array
|
568
|
-
|
569
|
-
if value.is_a? Range
|
570
|
-
value = value.to_a
|
571
|
-
else
|
572
|
-
value = [value.to_s]
|
573
|
-
end
|
574
|
-
|
575
|
-
end
|
576
|
-
|
577
|
-
if is_array
|
373
|
+
def parse_filters(filters, safe = false, model = nil)
|
578
374
|
|
579
|
-
|
580
|
-
|
581
|
-
integer: '::integer',
|
582
|
-
float: '::float'
|
583
|
-
}[type]
|
375
|
+
self_obj = model ? model : @model
|
376
|
+
self_string_table = model ? "__#{model.to_s.tableize}" : @model.to_s.tableize
|
584
377
|
|
585
|
-
|
378
|
+
filters = filters.clone.symbolize_keys
|
379
|
+
# if we're at the top level...
|
380
|
+
if model.nil?
|
586
381
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
end
|
592
|
-
|
593
|
-
else
|
382
|
+
if safe
|
383
|
+
filters.each do |key, value|
|
384
|
+
found_index = key.to_s.rindex('__')
|
385
|
+
key_root = found_index ? key.to_s[0..found_index].to_sym : key
|
594
386
|
|
595
|
-
if
|
596
|
-
|
597
|
-
else
|
598
|
-
field + ' NOT IN(' + (value.map { |val| ActiveRecord::Base.connection.quote(val.to_s) }).join(',') + ')'
|
387
|
+
if [:__order, :__offset, :__count].exclude?(key) && self_obj.fastapi_filters_whitelist.exclude?(key_root)
|
388
|
+
fail %(Filter "#{key}" not supported.)
|
599
389
|
end
|
600
|
-
|
601
390
|
end
|
602
|
-
|
603
|
-
elsif comparator == 'contains'
|
604
|
-
|
605
|
-
field_string + ' LIKE \'%\' || ' + ActiveRecord::Base.connection.quote(value.to_s) + ' || \'%\''
|
606
|
-
|
607
|
-
elsif comparator == 'icontains'
|
608
|
-
|
609
|
-
field_string + ' ILIKE \'%\' || ' + ActiveRecord::Base.connection.quote(value.to_s) + ' || \'%\''
|
610
|
-
|
611
|
-
elsif comparator == 'is_null'
|
612
|
-
|
613
|
-
'NULL = ' + field_string
|
614
|
-
|
615
|
-
elsif comparator == 'not_null'
|
616
|
-
|
617
|
-
'NOT NULL = ' + field_string
|
618
|
-
|
619
391
|
end
|
620
392
|
|
393
|
+
filters = @model.fastapi_filters.clone.merge(filters)
|
621
394
|
end
|
622
395
|
|
623
|
-
|
396
|
+
params = filters.has_key?(:__params) ? [*filters.delete(:__params)] : []
|
397
|
+
filters[:__order] ||= [:created_at, :DESC]
|
624
398
|
|
625
|
-
|
399
|
+
filters.each do |key, value|
|
626
400
|
|
627
|
-
|
401
|
+
next if [:__order, :__offset, :__count, :__params].include?(key)
|
628
402
|
|
629
|
-
|
403
|
+
found_index = key.to_s.rindex('__')
|
404
|
+
key_root = found_index.nil? ? key : key.to_s[0...found_index].to_sym
|
630
405
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
elsif type == :float
|
637
|
-
val = val.to_f
|
638
|
-
elsif type == :boolean
|
639
|
-
val = {
|
640
|
-
't' => true,
|
641
|
-
'f' => false
|
642
|
-
}[val]
|
406
|
+
if !self_obj.column_names.include?(key_root.to_s)
|
407
|
+
if !model.nil? || !(@model.reflect_on_all_associations(:has_many).map(&:name).include?(key_root) ||
|
408
|
+
@model.reflect_on_all_associations(:belongs_to).map(&:name).include?(key_root) ||
|
409
|
+
@model.reflect_on_all_associations(:has_one).map(&:name).include?(key_root))
|
410
|
+
fail %(Filter "#{key}" not supported)
|
643
411
|
end
|
644
412
|
end
|
645
|
-
|
646
|
-
val
|
647
|
-
|
648
413
|
end
|
649
414
|
|
650
|
-
def parse_filters(filters, safe = false, model = nil)
|
651
415
|
|
652
|
-
|
653
|
-
|
416
|
+
filter_array = []
|
417
|
+
filter_has_many = {}
|
418
|
+
filter_belongs_to = {}
|
654
419
|
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
if safe
|
660
|
-
filters.each do |key, value|
|
661
|
-
found_index = key.to_s.rindex('__')
|
662
|
-
key_root = found_index.nil? ? key : key.to_s[0...found_index].to_sym
|
663
|
-
if not [:__order, :__offset, :__count].include? key and not self_obj.fastapi_filters_whitelist.include? key_root
|
664
|
-
raise 'Filter "' + key.to_s + '" not supported'
|
665
|
-
end
|
666
|
-
end
|
667
|
-
end
|
668
|
-
|
669
|
-
all_filters = @model.fastapi_filters.clone()
|
670
|
-
|
671
|
-
filters.each do |field, value|
|
672
|
-
all_filters[field.to_sym] = value
|
673
|
-
end
|
674
|
-
|
675
|
-
filters = all_filters
|
676
|
-
|
677
|
-
end
|
678
|
-
|
679
|
-
if not filters.has_key? :__order
|
680
|
-
filters[:__order] = [:created_at, :DESC]
|
681
|
-
end
|
420
|
+
order = nil
|
421
|
+
order_has_many = {}
|
422
|
+
order_belongs_to = {}
|
682
423
|
|
683
|
-
|
424
|
+
# get the order first
|
684
425
|
|
685
|
-
|
686
|
-
params = filters[:__params]
|
687
|
-
filters.delete :__params
|
688
|
-
end
|
426
|
+
if filters.has_key?(:__order)
|
689
427
|
|
690
|
-
|
691
|
-
params = [params]
|
692
|
-
end
|
428
|
+
value = filters.delete(:__order)
|
693
429
|
|
694
|
-
|
430
|
+
order = value.clone()
|
695
431
|
|
696
|
-
|
697
|
-
|
432
|
+
if order.is_a?(String)
|
433
|
+
order = order.split(',')
|
434
|
+
if order.size < 2
|
435
|
+
order << 'ASC'
|
698
436
|
end
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
if not self_obj.column_names.include? key_root.to_s
|
704
|
-
if not model.nil? or not (
|
705
|
-
@model.reflect_on_all_associations(:has_many).map(&:name).include? key_root or
|
706
|
-
@model.reflect_on_all_associations(:belongs_to).map(&:name).include? key_root or
|
707
|
-
@model.reflect_on_all_associations(:has_one).map(&:name).include? key_root
|
708
|
-
)
|
709
|
-
raise 'Filter "' + key.to_s + '" not supported'
|
710
|
-
end
|
437
|
+
elsif order.is_a?(Array)
|
438
|
+
order = order.map { |v| v.to_s }
|
439
|
+
while order.size < 2
|
440
|
+
order << ''
|
711
441
|
end
|
712
|
-
|
442
|
+
else
|
443
|
+
order = ['', '']
|
713
444
|
end
|
714
445
|
|
446
|
+
order[1] = 'ASC' if ['ASC', 'DESC'].exclude?(order[1])
|
715
447
|
|
716
|
-
|
717
|
-
filter_has_many = {}
|
718
|
-
filter_belongs_to = {}
|
719
|
-
|
720
|
-
order = nil
|
721
|
-
order_has_many = {}
|
722
|
-
order_belongs_to = {}
|
723
|
-
|
724
|
-
# get the order first
|
448
|
+
if model.nil? && @model.fastapi_custom_order.has_key?(order[0].to_sym)
|
725
449
|
|
726
|
-
|
450
|
+
order[0] = @model.fastapi_custom_order[order[0].to_sym].gsub('self.', self_string_table + '.')
|
727
451
|
|
728
|
-
|
729
|
-
|
730
|
-
order = value.clone()
|
731
|
-
|
732
|
-
if order.is_a? String
|
733
|
-
order = order.split(',')
|
734
|
-
if order.size < 2
|
735
|
-
order << 'ASC'
|
736
|
-
end
|
737
|
-
elsif order.is_a? Array
|
738
|
-
order = order.map { |v| v.to_s }
|
739
|
-
while order.size < 2
|
740
|
-
order << ''
|
741
|
-
end
|
452
|
+
if params.is_a?(Array)
|
453
|
+
order[0].gsub!(/\$params\[([\w-]+)\]/) { ActiveRecord::Base.connection.quote(params[Regexp.last_match[1].to_i].to_s) }
|
742
454
|
else
|
743
|
-
order
|
455
|
+
order[0].gsub!(/\$params\[([\w-]+)\]/) { ActiveRecord::Base.connection.quote(params[Regexp.last_match[1]].to_s) }
|
744
456
|
end
|
745
457
|
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
if model.nil? and @model.fastapi_custom_order.has_key? order[0].to_sym
|
751
|
-
|
752
|
-
order[0] = @model.fastapi_custom_order[order[0].to_sym].gsub('self.', self_string_table + '.')
|
753
|
-
|
754
|
-
if params.is_a? Array
|
755
|
-
|
756
|
-
order[0] = order[0].gsub(/\$params\[([\w\d_-]+)\]/) { ActiveRecord::Base.connection.quote(params[Regexp.last_match[1].to_i].to_s) }
|
757
|
-
|
758
|
-
else
|
759
|
-
|
760
|
-
order[0] = order[0].gsub(/\$params\[([\w\d_-]+)\]/) { ActiveRecord::Base.connection.quote(params[Regexp.last_match[1]].to_s) }
|
761
|
-
|
762
|
-
end
|
763
|
-
|
764
|
-
order[0] = '(' + order[0] + ')'
|
765
|
-
order = order.join(' ')
|
458
|
+
order[0] = "(#{order[0]})"
|
459
|
+
order = order.join(' ')
|
460
|
+
else
|
766
461
|
|
462
|
+
if self_obj.column_names.exclude?(order[0])
|
463
|
+
order = nil
|
767
464
|
else
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
order = nil
|
772
|
-
|
773
|
-
else
|
774
|
-
|
775
|
-
order[0] = self_string_table + '.' + order[0]
|
776
|
-
order = order.join(' ')
|
777
|
-
|
778
|
-
end
|
779
|
-
|
465
|
+
order[0] = "#{self_string_table}.#{order[0]}"
|
466
|
+
order = order.join(' ')
|
780
467
|
end
|
781
|
-
|
782
|
-
filters.delete :__order
|
783
|
-
|
784
468
|
end
|
469
|
+
end
|
785
470
|
|
786
|
-
|
787
|
-
|
788
|
-
filters.each do |key, value|
|
789
|
-
|
790
|
-
field = key.to_s
|
791
|
-
|
792
|
-
if field.rindex('__').nil?
|
793
|
-
|
794
|
-
comparator = 'is'
|
471
|
+
if filters.size > 0
|
795
472
|
|
796
|
-
|
473
|
+
filters.each do |key, data|
|
474
|
+
field = key.to_s
|
797
475
|
|
798
|
-
|
799
|
-
|
476
|
+
if field.rindex('__').nil?
|
477
|
+
comparator = 'is'
|
478
|
+
else
|
800
479
|
|
801
|
-
|
802
|
-
|
803
|
-
end
|
480
|
+
comparator = field[(field.rindex('__') + 2)..-1]
|
481
|
+
field = field[0...field.rindex('__')]
|
804
482
|
|
483
|
+
if @@api_comparator_list.exclude?(comparator)
|
484
|
+
next # skip dis bro
|
805
485
|
end
|
486
|
+
end
|
806
487
|
|
807
|
-
|
488
|
+
if model.nil? && self_obj.reflect_on_all_associations(:has_many).map(&:name).include?(key)
|
808
489
|
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
order_has_many[key] = filter_result[:main_order]
|
490
|
+
filter_result = parse_filters(data, safe, field.singularize.classify.constantize)
|
491
|
+
filter_has_many[key] = filter_result[:main]
|
492
|
+
order_has_many[key] = filter_result[:main_order]
|
813
493
|
|
814
|
-
|
815
|
-
|
494
|
+
elsif model.nil? && (self_obj.reflect_on_all_associations(:belongs_to).map(&:name).include?(key) ||
|
495
|
+
self_obj.reflect_on_all_associations(:has_one).map(&:name).include?(key))
|
816
496
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
order_belongs_to[key] = filter_result[:main_order]
|
497
|
+
filter_result = parse_filters(data, safe, field.singularize.classify.constantize)
|
498
|
+
filter_belongs_to[key] = filter_result[:main]
|
499
|
+
order_belongs_to[key] = filter_result[:main_order]
|
821
500
|
|
822
|
-
|
501
|
+
elsif self_obj.column_names.include?(field)
|
823
502
|
|
824
|
-
|
825
|
-
|
826
|
-
|
503
|
+
base_field = "#{self_string_table}.#{field}"
|
504
|
+
field_string = base_field
|
505
|
+
is_array = false
|
827
506
|
|
828
|
-
|
507
|
+
if self_obj.columns_hash[field].respond_to?('array') && self_obj.columns_hash[field].array == true
|
508
|
+
field_string = "ANY(#{field_string})"
|
509
|
+
is_array = true
|
510
|
+
end
|
829
511
|
|
830
|
-
|
831
|
-
is_array = true
|
512
|
+
if self_obj.columns_hash[field].type == :boolean
|
832
513
|
|
514
|
+
# if data is not a boolean
|
515
|
+
if !!data != data
|
516
|
+
data = ['f', 'false'].include?(data) ? false : true
|
833
517
|
end
|
834
518
|
|
835
|
-
if
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
't' => true,
|
841
|
-
'f' => false,
|
842
|
-
'true' => true,
|
843
|
-
'false' => false
|
844
|
-
}
|
845
|
-
|
846
|
-
value = value.to_s.downcase
|
847
|
-
|
848
|
-
if bool_lookup.has_key? value
|
849
|
-
value = bool_lookup[value]
|
850
|
-
else
|
851
|
-
value = true
|
852
|
-
end
|
853
|
-
|
854
|
-
end
|
855
|
-
|
856
|
-
if !!value == value
|
857
|
-
|
858
|
-
if comparator == 'is'
|
859
|
-
filter_array << value.to_s.upcase + ' = ' + field_string
|
860
|
-
elsif comparator == 'not'
|
861
|
-
filter_array << 'NOT ' + value.to_s.upcase + ' = ' + field_string
|
862
|
-
end
|
863
|
-
|
864
|
-
end
|
865
|
-
|
866
|
-
elsif value == nil and comparator != 'is_null' and comparator != 'not_null'
|
867
|
-
|
868
|
-
if comparator == 'is'
|
869
|
-
filter_array << 'NULL = ' + field_string
|
870
|
-
elsif comparator == 'not'
|
871
|
-
filter_array << 'NOT NULL = ' + field_string
|
872
|
-
end
|
873
|
-
|
874
|
-
elsif value.is_a? Range and comparator == 'is'
|
875
|
-
|
876
|
-
filter_array << ActiveRecord::Base.connection.quote(value.first.to_s) + ' <= ' + field_string
|
877
|
-
filter_array << ActiveRecord::Base.connection.quote(value.last.to_s) + ' >= ' + field_string
|
878
|
-
|
879
|
-
else
|
880
|
-
|
881
|
-
filter_array << api_comparison(comparator, value, base_field, self_obj.columns_hash[field].type, is_array)
|
519
|
+
if comparator == 'is'
|
520
|
+
filter_array << "#{data.to_s.upcase} = #{field_string}"
|
521
|
+
elsif comparator == 'not'
|
522
|
+
filter_array << "NOT #{data.to_s.upcase} = #{field_string}"
|
523
|
+
end
|
882
524
|
|
525
|
+
elsif data == nil && comparator != 'is_null' && comparator != 'not_null'
|
526
|
+
if comparator == 'is'
|
527
|
+
filter_array << "#{field_string} IS NULL"
|
528
|
+
elsif comparator == 'not'
|
529
|
+
filter_array << "#{field_string} IS NOT NULL"
|
883
530
|
end
|
884
531
|
|
532
|
+
elsif data.is_a?(Range) && comparator == 'is'
|
533
|
+
filter_array << "#{ActiveRecord::Base.connection.quote(data.first.to_s)} <= #{field_string}"
|
534
|
+
filter_array << "#{ActiveRecord::Base.connection.quote(data.last.to_s)} >= #{field_string}"
|
535
|
+
else
|
536
|
+
filter_array << api_comparison(comparator, data, base_field, self_obj.columns_hash[field].type, is_array)
|
885
537
|
end
|
886
|
-
|
887
538
|
end
|
888
|
-
|
889
539
|
end
|
540
|
+
end
|
890
541
|
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
542
|
+
{
|
543
|
+
main: filter_array,
|
544
|
+
main_order: order,
|
545
|
+
has_many: filter_has_many,
|
546
|
+
has_many_order: order_has_many,
|
547
|
+
belongs_to: filter_belongs_to,
|
548
|
+
belongs_to_order: order_belongs_to
|
549
|
+
}
|
550
|
+
end
|
899
551
|
|
900
|
-
|
552
|
+
def api_generate_sql(filters, offset, count, safe = false)
|
901
553
|
|
902
|
-
|
554
|
+
filters = parse_filters(filters, safe)
|
903
555
|
|
904
|
-
|
556
|
+
belongs = []
|
557
|
+
has_many = []
|
905
558
|
|
906
|
-
|
907
|
-
belongs = []
|
908
|
-
has_many = []
|
559
|
+
model_lookup = {}
|
909
560
|
|
910
|
-
|
561
|
+
filter_fields = []
|
562
|
+
filter_fields.concat(@model.fastapi_fields)
|
563
|
+
filter_fields.concat(@whitelist_fields)
|
911
564
|
|
912
|
-
|
913
|
-
|
914
|
-
|
565
|
+
fields = filter_fields.each_with_object([]) do |field, field_list|
|
566
|
+
if @model.reflect_on_all_associations(:belongs_to).map(&:name).include?(field)
|
567
|
+
class_name = @model.reflect_on_association(field).options[:class_name]
|
915
568
|
|
916
|
-
|
569
|
+
if class_name
|
570
|
+
model = class_name.constantize
|
571
|
+
else
|
572
|
+
model = field.to_s.classify.constantize
|
573
|
+
end
|
917
574
|
|
918
|
-
|
919
|
-
|
575
|
+
model_lookup[field] = model
|
576
|
+
belongs << { model: model, alias: field, type: :belongs_to }
|
920
577
|
|
921
|
-
|
578
|
+
elsif @model.reflect_on_all_associations(:has_one).map(&:name).include?(field)
|
922
579
|
|
923
|
-
|
924
|
-
model = field.to_s.classify.constantize
|
925
|
-
else
|
926
|
-
model = class_name.constantize
|
927
|
-
end
|
580
|
+
class_name = @model.reflect_on_association(field).options[:class_name]
|
928
581
|
|
929
|
-
|
930
|
-
|
582
|
+
if class_name
|
583
|
+
model = class_name.constantize
|
584
|
+
else
|
585
|
+
model = field.to_s.classify.constantize
|
586
|
+
end
|
931
587
|
|
932
|
-
|
588
|
+
model_lookup[field] = model
|
933
589
|
|
934
|
-
|
935
|
-
model_lookup[field] = model
|
936
|
-
has_many << model
|
590
|
+
belongs << { model: model, alias: field, type: :has_one }
|
937
591
|
|
938
|
-
|
592
|
+
elsif @model.reflect_on_all_associations(:has_many).map(&:name).include?(field)
|
939
593
|
|
940
|
-
|
594
|
+
model = field.to_s.singularize.classify.constantize
|
595
|
+
model_lookup[field] = model
|
596
|
+
has_many << model
|
941
597
|
|
942
|
-
|
598
|
+
elsif @model.column_names.include?(field.to_s)
|
943
599
|
|
600
|
+
field_list << field
|
944
601
|
end
|
602
|
+
end
|
945
603
|
|
946
|
-
|
947
|
-
|
604
|
+
self_string = @model.to_s.tableize.singularize
|
605
|
+
self_string_table = @model.to_s.tableize
|
948
606
|
|
949
|
-
|
950
|
-
|
607
|
+
# Base fields
|
608
|
+
field_list = fields.each_with_object([]) do |field, list|
|
609
|
+
if @model.columns_hash[field.to_s].array
|
610
|
+
list << "ARRAY_TO_JSON(#{self_string_table}.#{field}) AS #{field}"
|
611
|
+
else
|
612
|
+
list << "#{self_string_table}.#{field} AS #{field}"
|
613
|
+
end
|
614
|
+
end
|
951
615
|
|
952
|
-
|
616
|
+
# Belongs fields (1 to 1)
|
617
|
+
joins = belongs.each_with_object([]) do |model_data, join_list|
|
953
618
|
|
954
|
-
|
955
|
-
|
619
|
+
model_string_table = model_data[:model].to_s.tableize
|
620
|
+
model_string_table_alias = model_data[:alias].to_s.pluralize
|
956
621
|
|
957
|
-
|
958
|
-
|
959
|
-
self_string_table,
|
960
|
-
'.',
|
961
|
-
field_string,
|
962
|
-
' as ',
|
963
|
-
field_string
|
964
|
-
].join('')
|
622
|
+
model_string_field = model_data[:alias].to_s
|
623
|
+
singular_self_table = self_string_table.singularize
|
965
624
|
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
model_string_table = model_data[:model].to_s.tableize
|
972
|
-
model_string_table_alias = model_data[:alias].to_s.pluralize
|
973
|
-
|
974
|
-
model_string_field = model_data[:alias].to_s
|
975
|
-
|
976
|
-
# fields
|
977
|
-
model_data[:model].fastapi_fields_sub.each do |field|
|
978
|
-
field_string = field.to_s
|
979
|
-
field_list << [
|
980
|
-
model_string_table_alias,
|
981
|
-
'.',
|
982
|
-
field_string,
|
983
|
-
' as ',
|
984
|
-
model_string_field,
|
985
|
-
'__',
|
986
|
-
field_string
|
987
|
-
].join('')
|
625
|
+
model_data[:model].fastapi_fields_sub.each do |field|
|
626
|
+
if model_data[:model].columns_hash[field.to_s].array
|
627
|
+
field_list << "ARRAY_TO_JSON(#{model_string_table_alias}.#{field}) AS #{model_string_field}__#{field}"
|
628
|
+
else
|
629
|
+
field_list << "#{model_string_table_alias}.#{field} AS #{model_string_field}__#{field}"
|
988
630
|
end
|
631
|
+
end
|
989
632
|
|
633
|
+
# fields
|
634
|
+
if model_data[:type] == :belongs_to
|
990
635
|
# joins
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
'ON',
|
997
|
-
model_string_table_alias + '.id',
|
998
|
-
'=',
|
999
|
-
self_string_table + '.' + model_string_field + '_id'
|
1000
|
-
].join(' ')
|
1001
|
-
|
636
|
+
join_list << "LEFT JOIN #{model_string_table} AS #{model_string_table_alias} " \
|
637
|
+
"ON #{model_string_table_alias}.id = #{self_string_table}.#{model_string_field}_id"
|
638
|
+
elsif model_data[:type] == :has_one
|
639
|
+
join_list << "LEFT JOIN #{model_string_table} AS #{model_string_table_alias} " \
|
640
|
+
"ON #{model_string_table_alias}.#{singular_self_table}_id = #{self_string_table}.id"
|
1002
641
|
end
|
642
|
+
end
|
1003
643
|
|
1004
|
-
|
1005
|
-
|
644
|
+
# Many fields (1 to many)
|
645
|
+
has_many.each do |model|
|
1006
646
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
model_symbol = model_string_table.to_sym
|
647
|
+
model_string_table = model.to_s.tableize
|
648
|
+
model_symbol = model_string_table.to_sym
|
1010
649
|
|
1011
|
-
|
650
|
+
model_fields = model.fastapi_fields_sub.each_with_object([]) do |field, m_fields|
|
651
|
+
m_fields << "__#{model_string_table}.#{field}"
|
652
|
+
end
|
1012
653
|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
# field_string
|
1019
|
-
].join(' ')
|
654
|
+
if filters[:has_many].has_key?(model_symbol)
|
655
|
+
if filters[:has_many][model_symbol].count > 0
|
656
|
+
has_many_filters = "AND #{filters[:has_many][model_symbol].join(' AND ')}"
|
657
|
+
else
|
658
|
+
has_many_filters = nil
|
1020
659
|
end
|
1021
660
|
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
if filters[:has_many][model_symbol].count > 0
|
1027
|
-
has_many_filters = 'AND ' + filters[:has_many][model_symbol].join(' AND ')
|
1028
|
-
end
|
1029
|
-
|
1030
|
-
if not filters[:has_many_order][model_symbol].nil?
|
1031
|
-
has_many_order = 'ORDER BY ' + filters[:has_many_order][model_symbol]
|
1032
|
-
end
|
1033
|
-
|
661
|
+
if filters[:has_many_order][model_symbol]
|
662
|
+
has_many_order = "ORDER BY #{filters[:has_many_order][model_symbol]}"
|
663
|
+
else
|
664
|
+
has_many_filters = nil
|
1034
665
|
end
|
1035
|
-
|
1036
|
-
field_list << [
|
1037
|
-
'ARRAY_TO_STRING(ARRAY(',
|
1038
|
-
'SELECT',
|
1039
|
-
'ROW(',
|
1040
|
-
model_fields.join(', '),
|
1041
|
-
')',
|
1042
|
-
'FROM',
|
1043
|
-
model_string_table,
|
1044
|
-
'as',
|
1045
|
-
'__' + model_string_table,
|
1046
|
-
'WHERE',
|
1047
|
-
'__' + model_string_table + '.' + self_string + '_id IS NOT NULL',
|
1048
|
-
'AND __' + model_string_table + '.' + self_string + '_id',
|
1049
|
-
'=',
|
1050
|
-
self_string_table + '.id',
|
1051
|
-
has_many_filters,
|
1052
|
-
has_many_order,
|
1053
|
-
'), \',\')',
|
1054
|
-
'as',
|
1055
|
-
'__many__' + model_string_table
|
1056
|
-
].join(' ')
|
1057
|
-
|
1058
666
|
end
|
1059
667
|
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
order_string,
|
1072
|
-
'LIMIT',
|
1073
|
-
count.to_s,
|
1074
|
-
'OFFSET',
|
1075
|
-
offset.to_s,
|
1076
|
-
].join(' '),
|
1077
|
-
count_query: [
|
1078
|
-
'SELECT COUNT(id) FROM',
|
1079
|
-
self_string_table,
|
1080
|
-
filter_string
|
1081
|
-
].join(' '),
|
1082
|
-
models: model_lookup
|
1083
|
-
}
|
1084
|
-
|
668
|
+
field_list << [
|
669
|
+
"ARRAY_TO_JSON(ARRAY(SELECT ROW(#{model_fields.join(', ')})",
|
670
|
+
"FROM #{model_string_table}",
|
671
|
+
"AS __#{model_string_table}",
|
672
|
+
"WHERE __#{model_string_table}.#{self_string}_id IS NOT NULL",
|
673
|
+
"AND __#{model_string_table}.#{self_string}_id",
|
674
|
+
"= #{self_string_table}.id",
|
675
|
+
has_many_filters,
|
676
|
+
has_many_order,
|
677
|
+
")) AS __many__#{model_string_table}"
|
678
|
+
].compact.join(' ')
|
1085
679
|
end
|
1086
680
|
|
681
|
+
filter_string = filters[:main].size > 0 ? "WHERE #{filters[:main].join(' AND ')}" : nil
|
682
|
+
order_string = filters[:main_order] ? "ORDER BY #{filters[:main_order]}" : nil
|
683
|
+
|
684
|
+
{
|
685
|
+
query: [
|
686
|
+
"SELECT #{field_list.join(', ')}",
|
687
|
+
"FROM #{self_string_table}",
|
688
|
+
joins.join(' '),
|
689
|
+
filter_string,
|
690
|
+
order_string,
|
691
|
+
"LIMIT #{count}",
|
692
|
+
"OFFSET #{offset}"
|
693
|
+
].compact.join(' '),
|
694
|
+
count_query: [
|
695
|
+
"SELECT COUNT(id) FROM #{self_string_table}",
|
696
|
+
filter_string
|
697
|
+
].compact.join(' '),
|
698
|
+
models: model_lookup
|
699
|
+
}
|
700
|
+
end
|
1087
701
|
end
|