inat-get 0.8.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +16 -0
  4. data/Rakefile +4 -0
  5. data/bin/inat-get +59 -0
  6. data/docs/logo.png +0 -0
  7. data/inat-get.gemspec +33 -0
  8. data/lib/extra/enum.rb +184 -0
  9. data/lib/extra/period.rb +252 -0
  10. data/lib/extra/uuid.rb +90 -0
  11. data/lib/inat/app/application.rb +50 -0
  12. data/lib/inat/app/config/messagelevel.rb +22 -0
  13. data/lib/inat/app/config/shiftage.rb +24 -0
  14. data/lib/inat/app/config/updatemode.rb +20 -0
  15. data/lib/inat/app/config.rb +296 -0
  16. data/lib/inat/app/globals.rb +80 -0
  17. data/lib/inat/app/info.rb +21 -0
  18. data/lib/inat/app/logging.rb +35 -0
  19. data/lib/inat/app/preamble.rb +27 -0
  20. data/lib/inat/app/status.rb +74 -0
  21. data/lib/inat/app/task/context.rb +47 -0
  22. data/lib/inat/app/task/dsl.rb +24 -0
  23. data/lib/inat/app/task.rb +75 -0
  24. data/lib/inat/data/api.rb +218 -0
  25. data/lib/inat/data/cache.rb +9 -0
  26. data/lib/inat/data/db.rb +87 -0
  27. data/lib/inat/data/ddl.rb +18 -0
  28. data/lib/inat/data/entity/comment.rb +29 -0
  29. data/lib/inat/data/entity/flag.rb +22 -0
  30. data/lib/inat/data/entity/identification.rb +45 -0
  31. data/lib/inat/data/entity/observation.rb +172 -0
  32. data/lib/inat/data/entity/observationphoto.rb +25 -0
  33. data/lib/inat/data/entity/observationsound.rb +26 -0
  34. data/lib/inat/data/entity/photo.rb +31 -0
  35. data/lib/inat/data/entity/place.rb +57 -0
  36. data/lib/inat/data/entity/project.rb +94 -0
  37. data/lib/inat/data/entity/projectadmin.rb +21 -0
  38. data/lib/inat/data/entity/projectobservationrule.rb +50 -0
  39. data/lib/inat/data/entity/request.rb +58 -0
  40. data/lib/inat/data/entity/sound.rb +27 -0
  41. data/lib/inat/data/entity/taxon.rb +94 -0
  42. data/lib/inat/data/entity/user.rb +67 -0
  43. data/lib/inat/data/entity/vote.rb +22 -0
  44. data/lib/inat/data/entity.rb +291 -0
  45. data/lib/inat/data/enums/conservationstatus.rb +30 -0
  46. data/lib/inat/data/enums/geoprivacy.rb +14 -0
  47. data/lib/inat/data/enums/iconictaxa.rb +23 -0
  48. data/lib/inat/data/enums/identificationcategory.rb +13 -0
  49. data/lib/inat/data/enums/licensecode.rb +16 -0
  50. data/lib/inat/data/enums/projectadminrole.rb +11 -0
  51. data/lib/inat/data/enums/projecttype.rb +37 -0
  52. data/lib/inat/data/enums/qualitygrade.rb +12 -0
  53. data/lib/inat/data/enums/rank.rb +60 -0
  54. data/lib/inat/data/model.rb +551 -0
  55. data/lib/inat/data/query.rb +1145 -0
  56. data/lib/inat/data/sets/dataset.rb +104 -0
  57. data/lib/inat/data/sets/list.rb +190 -0
  58. data/lib/inat/data/sets/listers.rb +15 -0
  59. data/lib/inat/data/sets/wrappers.rb +137 -0
  60. data/lib/inat/data/types/extras.rb +88 -0
  61. data/lib/inat/data/types/location.rb +89 -0
  62. data/lib/inat/data/types/std.rb +293 -0
  63. data/lib/inat/report/table.rb +135 -0
  64. data/lib/inat/utils/deep.rb +30 -0
  65. metadata +137 -0
@@ -0,0 +1,1145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'date'
5
+
6
+ require 'extra/period'
7
+
8
+ require_relative '../app/globals'
9
+ require_relative '../app/status'
10
+ require_relative 'db'
11
+ require_relative 'entity/request'
12
+ require_relative 'enums/qualitygrade'
13
+ require_relative 'types/std'
14
+ require_relative 'types/location'
15
+ require_relative 'types/extras'
16
+
17
+ class Query
18
+
19
+ include LogDSL
20
+
21
+ private def parse_accuracy value
22
+ case value
23
+ when true, false
24
+ @api_params[:acc] = value
25
+ @db_where << [ "o.positional_accuracy IS#{ value && ' NOT' || '' } NULL", [] ]
26
+ when Integer
27
+ @api_params[:acc_above] = value - 1
28
+ @api_params[:acc_below] = value + 1
29
+ @db_where << [ "o.positional_accuracy = ?", [ value.to_i ] ]
30
+ when Range
31
+ min = value.begin
32
+ max = value.end
33
+ if min != nil
34
+ @api_params[:acc_above] = min.to_i - 1
35
+ @db_where << [ "o.positional_accuracy >= ?", [ min ] ]
36
+ end
37
+ if max != nil
38
+ @api_params[:acc_below] = max.to_i + 1
39
+ @db_where << [ "o.positional_accuracy <#{ value.exclude_end? && '' || '=' } ?", [ max ] ]
40
+ end
41
+ else
42
+ raise TypeError, "Invalid 'accuracy' type: #{ value.inspect }!", caller[1..]
43
+ end
44
+ end
45
+
46
+ private def parse_acc_above value
47
+ case value
48
+ when Integer
49
+ @api_params[:acc_above] = value
50
+ @db_where << [ "o.positional_accuracy > ?", [ value ] ]
51
+ else
52
+ raise TypeError, "Invalid 'acc_above' type: #{ value.inspect }!", caller[1..]
53
+ end
54
+ end
55
+
56
+ private def parse_acc_below value
57
+ case value
58
+ when Integer
59
+ @api_params[:acc_below] = value
60
+ @db_where << [ "o.positional_accuracy < ?", [ value ] ]
61
+ else
62
+ raise TypeError, "Invalid 'acc_below' type: #{ value.inspect }!", caller[1..]
63
+ end
64
+ end
65
+
66
+ private def parse_acc_below_or_unknown value
67
+ case value
68
+ when Integer
69
+ @api_params[:acc_below_or_unknown] = value
70
+ @db_where << [ "o.positional_accuracy < ? OR o.positional_accuracy IS NULL", [ value ] ]
71
+ else
72
+ raise TypeError, "Invalid 'acc_below_or_unknown' type: #{ value.inspect }!", caller[1..]
73
+ end
74
+ end
75
+
76
+ private def parse_captive value
77
+ case value
78
+ when true, false
79
+ @api_params[:captive] = value
80
+ @db_where << [ "o.captive = ?", [ value.to_db ] ]
81
+ else
82
+ raise TypeError, "Invalid 'captive' type: #{ value.inspect }!", caller[1..]
83
+ end
84
+ end
85
+
86
+ private def parse_endemic value
87
+ case value
88
+ when true, false
89
+ @api_params[:endemic] = value
90
+ @db_where << [ "o.endemic = ?", [ value.to_db ] ]
91
+ else
92
+ raise TypeError, "Invalid 'endemic' type: #{ value.inspect }!", caller[1..]
93
+ end
94
+ end
95
+
96
+ private def parse_introduced value
97
+ case value
98
+ when true, false
99
+ @api_params[:introduced] = value
100
+ @db_where << [ "o.introduced = ?", [ value.to_db ] ]
101
+ else
102
+ raise TypeError, "Invalid 'introduced' type: #{ value.inspect }!", caller[1..]
103
+ end
104
+ end
105
+
106
+ private def parse_native value
107
+ case value
108
+ when true, false
109
+ @api_params[:native] = value
110
+ @db_where << [ "o.native = ?", [ value.to_db ] ]
111
+ else
112
+ raise TypeError, "Invalid 'native' type: #{ value.inspect }!", caller[1..]
113
+ end
114
+ end
115
+
116
+ private def parse_mappable value
117
+ case value
118
+ when true, false
119
+ @api_params[:mappable] = value
120
+ @db_where << [ "o.mappable = ?", [ value.to_db ] ]
121
+ else
122
+ raise TypeError, "Invalid 'mappable' type: #{ value.inspect }!", caller[1..]
123
+ end
124
+ end
125
+
126
+ private def parse_location value
127
+ case value
128
+ when true, false
129
+ @api_params[:geo] = value
130
+ @db_where << [ "o.location_latitude IS#{ value && ' NOT' || '' } NULL", [] ]
131
+ when Radius
132
+ @api_params[:lat] = value.latitude
133
+ @api_params[:lng] = value.longitude
134
+ @api_params[:radius] = value.radius
135
+ @db_where << [ "o.location_latitude IS NOT NULL AND o.location_longitude IS NOT NULL AND (6371 * acos(sin(o.location_latitude*pi()/180)*sin(?*pi()/180) + cos(o.location_latitude*pi()/180)*cos(?*pi()/180))*cos((o.location_longitude - ?)*pi()/180) <= ?)", [ value.latitude, value.latitude, value.longitude, value.radius ] ]
136
+ # @r_match << lambda { |o| o.location != nil && o.location.latitude != nil && o.location.longitude != nil &&
137
+ # 6371 * Math.acos(Math.sin(o.location.latitude*Math::PI/180)*Math.sin(value.latitude*Math::PI/180) +
138
+ # Math.cos(o.location.latitude*Math::PI/180)*Math.cos(value.latitude*Math::PI/180)*Math.cos((o.location.longitude - value.longitude)*Math::PI/180)) <= value.radius }
139
+ when Sector
140
+ @api_params[:nelat] = value.north
141
+ @api_params[:nelng] = value.east
142
+ @api_params[:swlat] = value.south
143
+ @api_params[:swlng] = value.west
144
+ @db_where << [ "o.location_latitude IS NOT NULL AND o.location_longitude IS NOT NULL AND o.location_latitude >= ? AND o.location_latitude <= ? AND o.location_longitude >= ? AND o.location_longitude <= ?",
145
+ [ value.south, value.north, value.west, value.east ] ]
146
+ # @r_match << lambda { |o| o.location != nil && o.location.latitude != nil && o.location.longitude != nil &&
147
+ # o.location.latitude >= value.south && o.location.latitude <= value.north &&
148
+ # o.location.longitude >= value.west && o.location.longitude <= value.east }
149
+ else
150
+ raise TypeError, "Invalid 'location' type: #{ value.inspect }!"
151
+ end
152
+ end
153
+
154
+ private def parse_photos value
155
+ case value
156
+ when true, false
157
+ @api_params[:photos] = value
158
+ @db_where << [ "#{ value && '' || 'NOT ' }EXISTS (SELECT * FROM observation_photos WHERE observation_id = o.id)", [] ]
159
+ # @r_match << lambda { |o| (o.observation_photos == nil || o.observation_photos.empty?) != value }
160
+ else
161
+ raise TypeError, "Invalid 'photos' type: #{ value.inspect }!", caller[1..]
162
+ end
163
+ end
164
+
165
+ private def parse_sounds value
166
+ case value
167
+ when true, false
168
+ @api_params[:sounds] = value
169
+ @db_where << [ "#{ value && '' || 'NOT ' }EXISTS (SELECT * FROM observation_sounds WHERE observation_id = o.id)", [] ]
170
+ # @r_match << lambda { |o| (o.observation_sounds == nil || o.observation_sounds.empty?) != value }
171
+ else
172
+ raise TypeError, "Invalid 'sounds' type: #{ value.inspect }!", caller[1..]
173
+ end
174
+ end
175
+
176
+ private def parse_popular value
177
+ case value
178
+ when true, false
179
+ @api_params[:popular] = value
180
+ @db_where << [ "#{ value && '' || 'NOT ' }EXISTS (SELECT * FROM observation_faves WHERE observation_id = o.id)", [] ]
181
+ # @r_match << lambda { |o| (o.faves == nil || o.faves.empty?) != value }
182
+ # TODO: выяснить, должны ли попадать еще и откомментированные
183
+ else
184
+ raise TypeError, "Invalid 'popular' type: #{ value.inspect }!", caller[1..]
185
+ end
186
+ end
187
+
188
+ private def parse_taxon_is_active value
189
+ case value
190
+ when true, false
191
+ @api_params[:taxon_is_active] = value
192
+ @db_where << [ "o.taxon_is_active = ?", [ value.to_db ] ]
193
+ # @r_match << lambda { |o| o.taxon_is_active == value }
194
+ else
195
+ raise TypeError, "Invalid 'taxon_is_active' type: #{ value.inspect }!", caller[1..]
196
+ end
197
+ end
198
+
199
+ private def parse_threatened value
200
+ case value
201
+ when true, false
202
+ @api_params[:threatened] = value
203
+ @db_where << [ "o.threatened = ?", [ value.to_db ] ]
204
+ # @r_match << lambda { |o| o.threatened == value }
205
+ else
206
+ raise TypeError, "Invalid 'threatened' type: #{ value.inspect }!", caller[1..]
207
+ end
208
+ end
209
+
210
+ private def parse_verifiable value
211
+ case value
212
+ when true, false
213
+ @api_params[:verifiable] = value
214
+ @db_where << [ "o.quality_grade#{ value && '' || ' NOT' } IN ('research', 'needs_id')", [] ]
215
+ # @r_match << lambda { |o| [ QualityGrade::RESEARCH, QualityGrade::NEEDS_ID ].include?(o.quality_grade) == value }
216
+ else
217
+ raise TypeError, "Invalid 'verifiable' type: #{ value.inspect }!", caller[1..]
218
+ end
219
+ end
220
+
221
+ private def parse_licensed value
222
+ case value
223
+ when true, false
224
+ @api_params[:licensed] = value
225
+ @db_where << [ "o.license_code IS#{ value && ' NOT' || '' } NULL", [] ]
226
+ # @r_match << lambda { |o| (o.license_code != nil) == value }
227
+ else
228
+ raise TypeError, "Invalid 'licensed' type: #{ value.inspect }!", caller[1..]
229
+ end
230
+ end
231
+
232
+ private def parse_photo_licensed value
233
+ case value
234
+ when true, false
235
+ @api_params[:photo_licensed] = value
236
+ @db_where << [ "o.photo_licensed == ?", [ value.to_db ] ]
237
+ # @r_match << lambda { |o| o.photo_licensed == value }
238
+ else
239
+ raise TypeError, "Invalid 'photo_licensed' type: #{ value.inspect }!", caller[1..]
240
+ end
241
+ end
242
+
243
+ private def parse_id value
244
+ case value
245
+ when Integer
246
+ @api_params[:id] = value
247
+ @db_where << [ "o.id == ?", [ value.to_db ] ]
248
+ # @r_match << lambda { |o| o.id == value }
249
+ when Array
250
+ raise TypeError, "Invalid 'id' type: #{ value.inspect }!", caller[1..] if value.any? { |v| !(Integer === v) }
251
+ @api_params[:id] = value
252
+ @db_where << [ "o.id IN (#{ (['?'] * value.size).join(',') })", value ]
253
+ # @r_match << lambda { |o| value.include?(o.id) }
254
+ else
255
+ raise TypeError, "Invalid 'id' type: #{ value.inspect }!", caller[1..]
256
+ end
257
+ end
258
+
259
+ private def parse_not_id value
260
+ case value
261
+ when Integer
262
+ @api_params[:not_id] = value
263
+ @db_where << [ "o.id != ?", [ value.to_db ] ]
264
+ # @r_match << lambda { |o| o.id != value }
265
+ when Array
266
+ raise TypeError, "Invalid 'not_id' type: #{ value.inspect }!", caller[1..] if value.any? { |v| !(Integer === v) }
267
+ @api_params[:not_id] = value
268
+ @db_where << [ "o.id NOT IN (#{ (['?'] * value.size).join(',') })", value ]
269
+ # @r_match << lambda { |o| !value.include?(o.id) }
270
+ else
271
+ raise TypeError, "Invalid 'not_id' type: #{ value.inspect }!", caller[1..]
272
+ end
273
+ end
274
+
275
+ private def parse_license value
276
+ if Array === value
277
+ value = value.map { |v| LicenseCode::parse(value) }
278
+ else
279
+ value = LicenseCode::parse(value)
280
+ end
281
+ case value
282
+ when LicenseCode
283
+ @api_params[:license] = value
284
+ @db_where << [ "o.license_code = ?", [ value.to_db ] ]
285
+ # @r_match << lambda { |o| o.license_code == value }
286
+ when Array
287
+ @api_params[:license] = value
288
+ @db_where << [ "o.license_code IN (#{ (['?'] * value.size).join(',') })", value.map(&:to_db) ]
289
+ # @r_match << lambda { |o| value.include?(o.license_code) }
290
+ else
291
+ raise TypeError, "Invalid 'license' type: #{ value.inspect }!", caller[1..]
292
+ end
293
+ end
294
+
295
+ private def parse_photo_license value
296
+ if Array === value
297
+ value = value.map { |v| LicenseCode::parse(value) }
298
+ else
299
+ value = LicenseCode::parse(value)
300
+ end
301
+ case value
302
+ when LicenseCode
303
+ @api_params[:photo_license] = value
304
+ debug "DB selector is not implemented..."
305
+ # Здесь отсутствие выбора для БД непринципиально — фильтрация затем происходит на уровне приложения
306
+ @r_match << lambda { |o| o.photos != nil && o.photos.any? { |p| p.license_code == value } }
307
+ when Array
308
+ @api_params[:photo_license] = value
309
+ debug "DB selector is not implemented..."
310
+ @r_match << lambda { |o| o.photos != nil && o.photos.any? { |p| value.include?(p.license_code) } }
311
+ else
312
+ raise TypeError, "Invalid 'photo_license' type: #{ value.inspect }!", caller[1..]
313
+ end
314
+ end
315
+
316
+ private def parse_sound_license value
317
+ if Array === value
318
+ value = value.map { |v| LicenseCode::parse(value) }
319
+ else
320
+ value = LicenseCode::parse(value)
321
+ end
322
+ case value
323
+ when LicenseCode
324
+ @api_params[:sound_license] = value
325
+ debug "DB selector is not implemented..."
326
+ # Здесь отсутствие выбора для БД непринципиально — фильтрация затем происходит на уровне приложения
327
+ @r_match << lambda { |o| o.sounds != nil && o.sounds.any? { |s| s.license_code == value } }
328
+ when Array
329
+ @api_params[:sound_license] = value
330
+ debug "DB selector is not implemented..."
331
+ @r_match << lambda { |o| o.sounds != nil && o.sounds.any? { |s| value.include?(s.license_code) } }
332
+ else
333
+ raise TypeError, "Invalid 'sound_license' type: #{ value.inspect }!", caller[1..]
334
+ end
335
+ end
336
+
337
+ private def parse_place_id value
338
+ case value
339
+ when Integer
340
+ @api_params[:place_id] = value
341
+ @db_where << [ "EXISTS (SELECT * FROM observation_places WHERE observation_id = o.id AND place_id = ?)", [ value.to_db ] ]
342
+ # @r_match << lambda { |o| o.places != nil && o.places.any? { |p| p.id == value } }
343
+ when Array
344
+ @api_params[:place_id] = value
345
+ @db_where << [ "EXISTS (SELECT * FROM observation_places WHERE observation_id = o.id AND place_id IN (#{ (['?'] * value.size).join(',') }))", value ]
346
+ # debug "DB selector for 'place_id' arrays is not implemented..."
347
+ # # Здесь тоже полагаемся на постфильтрацию приложением
348
+ # @r_match << lambda { |o| o.places != nil && o.places.any? { |p| value.include?(p.id) } }
349
+ else
350
+ raise TypeError, "Invalid 'place_id' type: #{ value.inspect }!", caller[1..]
351
+ end
352
+ end
353
+
354
+ private def parse_project_id value
355
+ case value
356
+ when Integer
357
+ @api_params[:project_id] = value
358
+ @db_where << [ "EXISTS(SELECT * FROM project_observations WHERE observation_id = o.id AND project_id = ?) OR EXISTS(SELECT * FROM observation_projects WHERE observation_id = o.id AND project_id = ?)", [ value, value ] ]
359
+ # debug "DB selector for 'project_id' is not implemented..."
360
+ # # Здесь используем только постфильтрацию из-за необходимости обрабатывать slug
361
+ # @r_match << lambda { |o| o.projects != nil && o.projects.any? { |p| p.id == value } ||
362
+ # o.cached_projects != nil && o.cached_projects.any? { |p| p.id == value } }
363
+ when Array
364
+ value = value.map do |v|
365
+ case v
366
+ when Integer, Symbol
367
+ v
368
+ when String
369
+ v.intern
370
+ else
371
+ raise TypeError, "Invalid 'project_id' type: #{ value.inspect }!", caller[1..]
372
+ end
373
+ end
374
+ @api_params[:project_id] = value
375
+ @db_where << [ "EXISTS(SELECT * FROM project_observations WHERE observation_id = o.id AND project_id IN (#{ (['?'] * value.size).join(',') })) OR EXISTS(SELECT * FROM observation_projects WHERE observation_id = o.id AND project_id IN (#{ (['?'] * value.size).join(',') }))", value + value ]
376
+ # debug "DB selector for 'project_id' is not implemented..."
377
+ # @r_match << lambda { |o| o.projects != nil && o.projects.any? { |p| value.include?(p.id) } ||
378
+ # o.cached_projects != nil && o.cached_projects.any? { |p| value.include?(p.id) } }
379
+ else
380
+ raise TypeError, "Invalid 'project_id' type: #{ value.inspect }!", caller[1..]
381
+ end
382
+ end
383
+
384
+ private def parse_place value
385
+ id_value = case value
386
+ when Place
387
+ value.id
388
+ when Array
389
+ value.map do |v|
390
+ case v
391
+ when Place
392
+ v.id
393
+ else
394
+ raise TypeError, "Invalid 'place' type: #{ value.inspect }!", caller[1..]
395
+ end
396
+ end
397
+ else
398
+ raise TypeError, "Invalid 'place' type: #{ value.inspect }!", caller[1..]
399
+ end
400
+ parse_place_id id_value
401
+ end
402
+
403
+ private def parse_project value
404
+ id_value = case value
405
+ when Project
406
+ value.id
407
+ when Array
408
+ value.map do |v|
409
+ case v
410
+ when Project
411
+ v.id
412
+ else
413
+ raise TypeError, "Invalid 'project' type: #{ value.inspect }!", caller[1..]
414
+ end
415
+ end
416
+ else
417
+ raise TypeError, "Invalid 'project' type: #{ value.inspect }!", caller[1..]
418
+ end
419
+ parse_project_id id_value
420
+ end
421
+
422
+ private def parse_rank value
423
+ # TODO: переработать и убрать data из хранилища
424
+ rank = case value
425
+ when Rank
426
+ value
427
+ when Symbol
428
+ Rank[value]
429
+ when String
430
+ Rank::parse(value)
431
+ when Integer
432
+ Rank.select { |r| r.data == value }
433
+ when Array
434
+ value.map do |v|
435
+ case v
436
+ when Rank
437
+ v
438
+ when Symbol
439
+ Rank[v]
440
+ when String
441
+ Rank::parse(v)
442
+ when Integer
443
+ Rank.select { |r| r.data == value }
444
+ else
445
+ raise TypeError, "Invalid 'rank' type: #{ value.inspect }!", caller[1..]
446
+ end
447
+ end.flatten
448
+ when Range
449
+ min = value.begin
450
+ max = value.end
451
+ r_min = case min
452
+ when nil
453
+ nil
454
+ when Rank
455
+ min
456
+ when Symbol
457
+ Rank[min]
458
+ when String
459
+ Rank::parse(min)
460
+ when Integer
461
+ Rank.find { |r| r.data <= min }
462
+ # TODO: Подумать на предмет максимальной корректности. Но скорее всего не нужно.
463
+ else
464
+ raise TypeError, "Invalid 'rank' type: #{ value.inspect }!", caller[1..]
465
+ end
466
+ r_max = case max
467
+ when nil
468
+ nil
469
+ when Rank
470
+ max
471
+ when Symbol
472
+ Rank[max]
473
+ when String
474
+ Rank::parse(max)
475
+ when Integer
476
+ Rank.find { |r| r.data <= max }
477
+ else
478
+ raise TypeError, "Invalid 'rank' type: #{ value.inspect }!", caller[1..]
479
+ end
480
+ Range::new r_min, r_max, value.exclude_end?
481
+ else
482
+ raise TypeError, "Invalid 'rank' type: #{ value.inspect }!", caller[1..]
483
+ end
484
+ case rank
485
+ when Rank
486
+ @api_params[:rank] = rank
487
+ @db_where << [ "o.rank_name = ?", [ rank.name ] ]
488
+ # @r_match << lambda { |o| o.rank == rank }
489
+ when Array
490
+ @api_params[:rank] = rank
491
+ @db_where << [ "o.rank_name IN (#{(['?'] * rank.size).join(',')})", rank.map(&:name) ]
492
+ # @r_match << lambda { |o| rank.include?(o.rank) }
493
+ when Range
494
+ min = rank.begin
495
+ max = rank.end
496
+ if min
497
+ @api_params[:hrank] = min
498
+ @db_where << [ "o.rank_data <= ?", [ min.data ] ]
499
+ # @r_match << lambda { |o| o.rank >= min }
500
+ end
501
+ if max
502
+ @api_params[:lrank] = max
503
+ @db_where << [ "o.rank_data >= ?", [ max.data ] ]
504
+ # @r_match << lambda { |o| o.rank <= max }
505
+ end
506
+ else
507
+ raise TypeError, "Invalid rank value: #{ rank.inspect }!"
508
+ end
509
+ end
510
+
511
+ private def parse_hrank value
512
+ parse_rank(value..)
513
+ end
514
+
515
+ private def parse_lrank value
516
+ parse_rank(..value)
517
+ end
518
+
519
+ private def parse_taxon_id value
520
+ case value
521
+ when Integer
522
+ @api_params[:taxon_id] = value
523
+ @db_where << [ "EXISTS (SELECT * FROM observation_ident_taxa WHERE observation_id = o.id AND taxon_id = ?)", [ value ] ]
524
+ # @r_match << lambda { |o| o.ident_taxon_ids.include?(value) }
525
+ when Array
526
+ @api_params[:taxon_id] = value
527
+ @db_where << [ "EXISTS (SELECT * FROM observation_ident_taxa WHERE observation_id = o.id AND taxon_id IN (#{(['?'] * value.size).join(',')}))", value ]
528
+ # @r_match << lambda { |o| value.any? { |v| o.ident_taxon_ids.include?(v) } }
529
+ else
530
+ raise TypeError, "Invalid 'taxon_id' type: #{ value.inspect }!", caller[1..]
531
+ end
532
+ end
533
+
534
+ private def parse_taxon value
535
+ case value
536
+ when Taxon
537
+ parse_taxon_id value.id
538
+ when Array
539
+ ids = value.map do |v|
540
+ case v
541
+ when Taxon
542
+ v.id
543
+ else
544
+ raise TypeError, "Invalid 'taxon' type: #{ value.inspect }!", caller[1..]
545
+ end
546
+ end
547
+ parse_taxon_id ids
548
+ else
549
+ raise TypeError, "Invalid 'taxon' type: #{ value.inspect }!", caller[1..]
550
+ end
551
+ end
552
+
553
+ private def parse_without_taxon_id value
554
+ case value
555
+ when Integer
556
+ @api_params[:taxon_id] = value
557
+ @db_where << [ "NOT EXISTS (SELECT * FROM observation_ident_taxa WHERE observation_id = o.id AND taxon_id = ?)", [ value ] ]
558
+ # @r_match << lambda { |o| o.ident_taxon_ids.include?(value) }
559
+ when Array
560
+ @api_params[:taxon_id] = value
561
+ @db_where << [ "NOT EXISTS (SELECT * FROM observation_ident_taxa WHERE observation_id = o.id AND taxon_id IN (#{(['?'] * value.size).join(',')}))", value ]
562
+ # @r_match << lambda { |o| value.any? { |v| o.ident_taxon_ids.include?(v) } }
563
+ else
564
+ raise TypeError, "Invalid 'taxon_id' type: #{ value.inspect }!", caller[1..]
565
+ end
566
+ end
567
+
568
+ private def parse_without_taxon value
569
+ case value
570
+ when Taxon
571
+ parse_taxon_id value.id
572
+ when Array
573
+ ids = value.map do |v|
574
+ case v
575
+ when Taxon
576
+ v.id
577
+ else
578
+ raise TypeError, "Invalid 'without_taxon' type: #{ value.inspect }!", caller[1..]
579
+ end
580
+ end
581
+ parse_without_taxon_id ids
582
+ else
583
+ raise TypeError, "Invalid 'without_taxon' type: #{ value.inspect }!", caller[1..]
584
+ end
585
+ end
586
+
587
+ private def parse_taxon_name value
588
+ case value
589
+ when String
590
+ @api_params[:taxon_name] = value
591
+ debug "DB selector for 'taxon_name' is not implemented..."
592
+ small = value.downcase
593
+ @r_match << lambda { |o| o.taxon.name.downcase == small || o.taxon.preferred_common_name&.downcase == small || o.taxon.english_common_name&.downcase == small }
594
+ when Array
595
+ @api_params[:taxon_name] = value
596
+ debug "DB selector for 'taxon_name' is not implemented..."
597
+ small = value.map(&:downcase)
598
+ @r_match << lambda { |o| small.any? { |n| o.taxon.name.downcase == n || o.taxon.preferred_common_name&.downcase == n || o.taxon.english_common_name&.downcase == n } }
599
+ else
600
+ raise TypeError, "Invalid 'taxon_name' type: #{ value.inspect }!", caller[1..]
601
+ end
602
+ end
603
+
604
+ private def parse_user_login value
605
+ case value
606
+ when Integer
607
+ @api_params[:user_id] = value
608
+ @db_where << [ "o.user_id == ?", [ value ] ]
609
+ # @r_match << lambda { |o| o.user_id == value }
610
+ when String, Symbol
611
+ @api_params[:user_login] = value
612
+ @db_where << [ "EXISTS (SELECT * FROM users WHERE id = o.user_id AND login = ?)", [ value.to_db ] ]
613
+ # @r_match << lambda { |o| o.user.login == value.to_s }
614
+ when Array
615
+ @api_params[:user_id] = value
616
+ debug "DB selector for 'user_id/user_login' arrays is not implemented..."
617
+ @r_match << lambda { |o| value.any? { |v| o.user.id == v || o.user.login == v.to_s } }
618
+ else
619
+ raise TypeError, "Invalid 'user_id/user_login' type: #{ value.inspect }!", caller[1..]
620
+ end
621
+ end
622
+
623
+ private def parse_user value
624
+ case value
625
+ when User
626
+ parse_user_login value.id
627
+ else
628
+ raise TypeError, "Invalid 'user' type: #{ value.inspect }!", caller[1..]
629
+ end
630
+ end
631
+
632
+ private def parse_ident_user_id value
633
+ case value
634
+ when Integer
635
+ @api_params[:ident_user_id] = value
636
+ @db_where << [ "EXISTS (SELECT * FROM identifications WHERE observation_id = o.id AND user_id = ?)", [ value ] ]
637
+ # @r_match << lambda { |o| o.identification.any? { |i| i.user_id == value } }
638
+ else
639
+ raise TypeError, "Invalid 'ident_user_id' type: #{ value.inspect }!", caller[1..]
640
+ end
641
+ end
642
+
643
+ private def parse_ident_user value
644
+ case value
645
+ when User
646
+ parse_ident_user_id value.id
647
+ else
648
+ raise TypeError, "Invalid 'ident_user' type: #{ value.inspect }!", caller[1..]
649
+ end
650
+ end
651
+
652
+ private def parse_day value
653
+ case value
654
+ when Integer, String
655
+ value = value.to_i
656
+ @api_params[:day] = value
657
+ @db_where << [ "o.day = ?", [ value ] ]
658
+ # @r_match << lambda { |o| o.day == value }
659
+ when Array
660
+ value = value.map(&:to_i)
661
+ @api_params[:day] = value
662
+ @db_where << [ "o.day IN (#{(['?'] * value.size).join(',')})", value ]
663
+ # @r_match << lambda { |o| value.include?(o.day) }
664
+ else
665
+ raise TypeError, "Invalid 'day' type: #{ value.inspect }!", caller[1..]
666
+ end
667
+ end
668
+
669
+ private def parse_month value
670
+ case value
671
+ when Integer, String
672
+ value = value.to_i
673
+ @api_params[:month] = value
674
+ @db_where << [ "o.month = ?", [ value ] ]
675
+ # @r_match << lambda { |o| o.month == value }
676
+ when Array
677
+ value = value.map(&:to_i)
678
+ @api_params[:month] = value
679
+ @db_where << [ "o.month IN (#{(['?'] * value.size).join(',')})", value ]
680
+ # @r_match << lambda { |o| value.include?(o.month) }
681
+ else
682
+ raise TypeError, "Invalid 'month' type: #{ value.inspect }!", caller[1..]
683
+ end
684
+ end
685
+
686
+ private def parse_year value
687
+ case value
688
+ when Integer, String
689
+ value = value.to_i
690
+ @api_params[:year] = value
691
+ @db_where << [ "o.year = ?", [ value ] ]
692
+ # @r_match << lambda { |o| o.year == value }
693
+ when Array
694
+ value = value.map(&:to_i)
695
+ @api_params[:year] = value
696
+ @db_where << [ "o.year IN (#{(['?'] * value.size).join(',')})", value ]
697
+ # @r_match << lambda { |o| value.include?(o.year) }
698
+ else
699
+ raise TypeError, "Invalid 'year' type: #{ value.inspect }!", caller[1..]
700
+ end
701
+ end
702
+
703
+ private def parse_d1 value
704
+ case value
705
+ when Date
706
+ @api_params[:d1] = value
707
+ @db_where << [ "o.observed_on >= ?", [ value.to_db ] ]
708
+ # @r_match << lambda { |o| o.observed_on >= value }
709
+ else
710
+ raise TypeError, "Invalid 'd1' type: #{ value.inspect }!", caller[1..]
711
+ end
712
+ end
713
+
714
+ private def parse_d2 value
715
+ case value
716
+ when Date
717
+ @api_params[:d2] = value
718
+ @db_where << [ "o.observed_on <= ?", [ value.to_db ] ]
719
+ # @r_match << lambda { |o| o.observed_on <= value }
720
+ else
721
+ raise TypeError, "Invalid 'd2' type: #{ value.inspect }!", caller[1..]
722
+ end
723
+ end
724
+
725
+ private def parse_observed_on value
726
+ case value
727
+ when Date
728
+ @api_params[:observed_on] = value
729
+ @db_where << [ "o.observed_on = ?", [ value.to_db ] ]
730
+ # @r_match << lambda { |o| o.observed_on == value }
731
+ when Range
732
+ min = value.begin
733
+ max = value.end
734
+ parse_d1 min if min
735
+ parse_d2 max if max
736
+ else
737
+ raise TypeError, "Invalid 'observed_on/date' type: #{ value.inspect }!", caller[1..]
738
+ end
739
+ end
740
+
741
+ private def parse_created_d1 value
742
+ case value
743
+ when Time
744
+ @api_params[:created_d1] = value
745
+ @db_where << [ "o.created_at >= ?", [ value.to_db ] ]
746
+ # @r_match << lambda { |o| o.created_at >= value }
747
+ when Date
748
+ @api_params[:created_d1] = value
749
+ @db_where << [ "o.created_at >= ?", [ value.to_db ] ]
750
+ # @r_match << lambda { |o| o.created_at >= value.to_time }
751
+ else
752
+ raise TypeError, "Invalid 'created_d1' type: #{ value.inspect }!", caller[1..]
753
+ end
754
+ end
755
+
756
+ private def parse_created_d2 value
757
+ case value
758
+ when Time
759
+ @api_params[:created_d2] = value
760
+ @db_where << [ "o.created_at <= ?", [ value.to_db ] ]
761
+ # @r_match << lambda { |o| o.created_at <= value }
762
+ when Date
763
+ @api_params[:created_d2] = value
764
+ @db_where << [ "o.created_at <= ?", [ value.to_db ] ]
765
+ # @r_match << lambda { |o| o.created_at <= value.to_time }
766
+ else
767
+ raise TypeError, "Invalid 'created_d2' type: #{ value.inspect }!", caller[1..]
768
+ end
769
+ end
770
+
771
+ private def parse_created_on value
772
+ case value
773
+ when Date, Time
774
+ value = value.to_date
775
+ @api_params[:created_on] = value
776
+ @db_where << [ "o.created_at >= ? AND o.created_at < ?", value.to_db, value.to_db + 24 * 60 * 60 ]
777
+ # @r_match << lambda { |o| o.created_at.to_date == value }
778
+ when Range
779
+ min = value.begin
780
+ max = value.end
781
+ parse_created_d1 min if min
782
+ parse_created_d2 max if max
783
+ else
784
+ raise TypeError, "Invalid 'created_on' type: #{ value.inspect }!", caller[1..]
785
+ end
786
+ end
787
+
788
+ private def parse_geoprivacy value
789
+ case value
790
+ when GeoPrivacy
791
+ @api_params[:geoprivacy] = value
792
+ @db_where << [ "o.geoprivacy = ?", [ value.to_db ] ]
793
+ # @r_match << lambda { |o| o.geoprivacy == value }
794
+ when Array
795
+ @api_params[:geoprivacy] = value
796
+ @db_where << [ "o.geoprivacy IN (#{ (['?'] * value.size).join(',') })", value.map(&:to_db) ]
797
+ # @r_match << lambda { |o| value.include?(o.geoprivacy) }
798
+ else
799
+ raise TypeError, "Invalid 'geoprivacy' type: #{ value.inspect }!", caller[1..]
800
+ end
801
+ end
802
+
803
+ private def parse_taxon_geoprivacy value
804
+ case value
805
+ when GeoPrivacy
806
+ @api_params[:taxon_geoprivacy] = value
807
+ @db_where << [ "o.taxon_geoprivacy = ?", [ value.to_db ] ]
808
+ # @r_match << lambda { |o| o.taxon_geoprivacy == value }
809
+ when Array
810
+ @api_params[:taxon_geoprivacy] = value
811
+ @db_where << [ "o.taxon_geoprivacy IN (#{ (['?'] * value.size).join(',') })", value.map(&:to_db) ]
812
+ # @r_match << lambda { |o| value.include?(o.taxon_geoprivacy) }
813
+ else
814
+ raise TypeError, "Invalid 'taxon_geoprivacy' type: #{ value.inspect }!", caller[1..]
815
+ end
816
+ end
817
+
818
+ private def iconic_taxa value
819
+ case value
820
+ when IconicTaxa
821
+ @api_params[:iconic_taxa] = value
822
+ warning "DB selector for 'iconic_taxa' is not implemented..."
823
+ # Тут по идее надо по всем идентификациям брать...
824
+ @r_match << lambda { |o| o.taxon.iconic_taxon_name == value }
825
+ when Array
826
+ @api_params[:iconic_taxa] = value
827
+ warning "DB selector for 'iconic_taxa' is not implemented..."
828
+ @r_match << lambda { |o| value.include?(o.taxon.iconic_taxon_name) }
829
+ else
830
+ raise TypeError, "Invalid 'iconic_taxa' type: #{ value.inspect }!", caller[1..]
831
+ end
832
+ end
833
+
834
+ private def parse_quality_grade value
835
+ case value
836
+ when QualityGrade
837
+ @api_params[:quality_grade] = value
838
+ @db_where << [ "o.quality_grade = ?", [ value.to_db ] ]
839
+ # @r_match << lambda { |o| o.quality_grade == value }
840
+ when Array
841
+ @api_params[:quality_grade] = value
842
+ @db_where << [ "o.quality_grade IN (#{ (['?'] * value.size).join(',') })", value.map(&:to_db) ]
843
+ # @r_match << lambda { |o| value.include?(o.quality_grade) }
844
+ else
845
+ raise TypeError, "Invalid 'quality_grade' type: #{ value.inspect }!", caller[1..]
846
+ end
847
+ end
848
+
849
+ def initialize **params
850
+
851
+ @@counter ||= 0
852
+ @@counter += 1
853
+ @int_key = @@counter
854
+
855
+ @api_params = {}
856
+ @db_where = []
857
+ @r_match = []
858
+
859
+ debug "Initializing Query: #{ params.inspect }"
860
+
861
+ params.each do |key, value|
862
+ key = key.intern
863
+ case key
864
+ when :acc, :accuracy, :positional_accuracy
865
+ parse_accuracy value
866
+ when :acc_above
867
+ parse_acc_above value
868
+ when :acc_below
869
+ parse_acc_below value
870
+ when :acc_below_or_unknown
871
+ parse_acc_below_or_unknown value
872
+ when :captive
873
+ parse_captive value
874
+ when :endemic
875
+ parse_endemic value
876
+ when :introduced
877
+ parse_introduced value
878
+ when :native
879
+ parse_native value
880
+ when :mappable
881
+ parse_mappable value
882
+ when :geo, :location
883
+ parse_location value
884
+ when :lat, :lng, :radius
885
+ raise ArgumentError, "Query parameter '#{ key }' is unsupported. Use 'location' with Radius value instead!", caller
886
+ when :nelat, :nelng, :swlat, :swlng
887
+ raise ArgumentError, "Query parameter '#{ key }' is unsupported. Use 'location' with Sector value instead!", caller
888
+ when :popular
889
+ parse_popular value
890
+ when :photos
891
+ parse_photos value
892
+ when :sounds
893
+ parse_sounds value
894
+ when :taxon_is_active
895
+ parse_taxon_is_active value
896
+ when :threatened
897
+ parse_threatened value
898
+ when :verifiable
899
+ parse_verifiable value
900
+ when :licensed
901
+ parse_licensed value
902
+ when :photo_licensed
903
+ parse_photo_licensed value
904
+ when :id
905
+ parse_id value
906
+ when :not_id
907
+ parse_not_id value
908
+ when :license
909
+ parse_license value
910
+ when :photo_license
911
+ parse_photo_license value
912
+ when :sound_license
913
+ parse_sound_license value
914
+ when :place_id
915
+ parse_place_id value
916
+ when :place
917
+ parse_place value
918
+ when :project_id
919
+ parse_project_id value
920
+ when :project
921
+ parse_project value
922
+ when :rank
923
+ parse_rank value
924
+ when :hrank
925
+ parse_hrank value
926
+ when :lrank
927
+ parse_lrank value
928
+ when :taxon_id
929
+ parse_taxon_id value
930
+ when :taxon
931
+ parse_taxon value
932
+ when :without_taxon_id
933
+ parse_without_taxon_id value
934
+ when :without_taxon
935
+ parse_without_taxon value
936
+ when :taxon_name
937
+ parse_taxon_name value
938
+ when :user_id, :user_login
939
+ parse_user_login value
940
+ when :user
941
+ parse_user value
942
+ when :ident_user_id
943
+ parse_ident_user_id value
944
+ when :ident_user
945
+ parse_ident_user value
946
+ when :day
947
+ parse_day value
948
+ when :month
949
+ parse_month value
950
+ when :year
951
+ parse_year value
952
+ when :d1
953
+ parse_d1 value
954
+ when :d2
955
+ parse_d2 value
956
+ when :observed_on, :date
957
+ parse_observed_on value
958
+ when :created_d1
959
+ parse_created_d1 value
960
+ when :created_d2
961
+ parse_created_d2 value
962
+ when :created_on, :created_at
963
+ parse_created_on value
964
+ when :geoprivacy
965
+ parse_geoprivacy value
966
+ when :taxon_geoprivacy
967
+ parse_taxon_geoprivacy value
968
+ when :iconic_taxa
969
+ parse_iconic_taxa value
970
+ when :quality_grade
971
+ parse_quality_grade value
972
+ # TODO: other properties
973
+ # when :out_of_range not supported
974
+ # when :pcid not supported
975
+ # when :ofv_datatype not supported
976
+ # TODO: when :site_id not supported
977
+ # TODO: when :term_id not supported
978
+ # TODO: when :term_value_id not supported
979
+ # when :without_term_id not supported
980
+ # when :without_term_value_id not supported
981
+ # TODO: when :with_term_value_id not supported
982
+ # when :apply_project_rules_for
983
+ # TODO: when :cs
984
+ # TODO: when :csa
985
+ # TODO: when :sci
986
+ # when :identifications
987
+ # when :list_id
988
+ # when :not_in_project
989
+ # when :not_matching_project_rules_for
990
+ # when :q
991
+ # when :search_on
992
+ else
993
+ raise ArgumentError, "Query parameter '#{ key }' is unknown or not supported!", caller
994
+ end
995
+ end
996
+
997
+ end
998
+
999
+ private def parse_query query_string
1000
+ result = {}
1001
+ params = query_string.split '&'
1002
+ params.each do |param|
1003
+ para = param.split '='
1004
+ result[para[0].intern] = URI.decode_uri_component(para[1])
1005
+ end
1006
+ result
1007
+ end
1008
+
1009
+ private def array_covers own, other
1010
+ own = own.map { |i| i.to_s }
1011
+ other = other.map { |i| i.to_s }
1012
+ other.all? { |i| own.include?(i) }
1013
+ end
1014
+
1015
+ def in? query_string
1016
+ query_params = parse_query query_string
1017
+ query_params.each do |key, value|
1018
+ own_param = @api_params[key]
1019
+ case own_param
1020
+ when nil
1021
+ return false
1022
+ when Array
1023
+ if key.start_with?('not_') || key.start_with?('without')
1024
+ return false unless array_covers(own_param, value.split(','))
1025
+ else
1026
+ return false unless array_covers(value.split(','), own_param)
1027
+ end
1028
+ when Date
1029
+ value = Date::parse value
1030
+ if key.end_with?('1')
1031
+ return false unless own_param <= value
1032
+ elsif key.end_with?('2')
1033
+ return false unless own_param >= value
1034
+ end
1035
+ else
1036
+ return false unless own_param.to_query == value
1037
+ end
1038
+ end
1039
+ return true
1040
+ # TODO: обрабатывать вложенность таксонов, мест и проектов, а также координат.
1041
+ end
1042
+
1043
+ def api_query
1044
+ # TODO: подумать о сортировке массивов
1045
+ @api_params.map { |k, v| "#{ k }=#{ v.to_query }" }.sort.join("&")
1046
+ end
1047
+
1048
+ def db_where
1049
+ sql = []
1050
+ sql_args = []
1051
+ @db_where.each do |item|
1052
+ sql << item[0]
1053
+ sql_args << item[1]
1054
+ end
1055
+ [ sql.map { |s| "(#{ s })" }.join(' AND '), sql_args.flatten ]
1056
+ end
1057
+
1058
+ def match? observation
1059
+ @r_match.all? { |m| m === observation }
1060
+ end
1061
+
1062
+ def === observation
1063
+ match? observation
1064
+ end
1065
+
1066
+ def observations
1067
+ request = nil
1068
+ current_time = nil
1069
+ mode = G.config[:data][:update]
1070
+ mode = UpdateMode[mode] if Symbol === mode || Integer === mode
1071
+ mode = UpdateMode::parse(mode) if String === mode
1072
+ if mode != UpdateMode::OFFLINE
1073
+ actuals = []
1074
+ if mode != UpdateMode::RELOAD
1075
+ # 1. Проверяем наличие актуального реквеста
1076
+ actual_time = 0
1077
+ if mode != UpdateMode::MINIMAL
1078
+ actual_time = Time::new - Period::parse(G.config[:data][:update_period])
1079
+ end
1080
+ actuals = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE time >= ?", actual_time.to_db)).select { |rq| self.in?(rq.query) }
1081
+ end
1082
+ if actuals.empty? || mode == UpdateMode::FORCE
1083
+ # 2. Ищем чего бы обновить
1084
+ request = Request::from_db_rows(DB.execute("SELECT * FROM requests WHERE query = ?", api_query)).first
1085
+ updated_since = nil
1086
+ if request == nil
1087
+ query_string = api_query
1088
+ project_id = @api_params[:project_id]
1089
+ project_id = nil unless Integer === project_id
1090
+ request = Request::create query_string, project_id
1091
+ request.save
1092
+ else
1093
+ updated_since = request.time if mode != UpdateMode::RELOAD
1094
+ end
1095
+ if request.active
1096
+ Status::status nil, "Query \##{ @int_key } : waiting for request R\##{ request.id }..."
1097
+ while request.active do
1098
+ sleep 1.0
1099
+ end
1100
+ else
1101
+ request.active = true
1102
+ params = @api_params.dup
1103
+ params[:updated_since] = updated_since if updated_since && updated_since != Time::at(0)
1104
+ tt = nil
1105
+ cc = 0
1106
+ current_time = Time::new
1107
+ API::query(:observations, **params) do |json, total|
1108
+ tt ||= total
1109
+ cc += 1
1110
+ pc = cc * 100 / tt
1111
+ td = (Time::new - current_time) / cc
1112
+ te = (td * (tt - cc)).to_i
1113
+ pe = Period::make seconds: te
1114
+ pt = Period::make seconds: (Time::new - current_time).to_i
1115
+ Status::status nil, "Query \##{ @int_key } : R\##{ request.id } : parsed #{ format("%d of %d : %3d%% : time %s remain %s", cc, tt, pc, pt.to_hs, pe.to_hs) }"
1116
+ obs = Observation::parse json
1117
+ obs.save
1118
+ DB.execute "INSERT OR REPLACE INTO request_observations (request_id, observation_id) VALUES (?, ?);", request.id, obs.id
1119
+ end
1120
+ request.active = false
1121
+ end
1122
+ # Считываем свежедобаленное
1123
+ # TODO: разобраться с удалением устаревшего
1124
+ # NEED: разобраться с частичной загрузкой — большие проекты грузятся недопустимо долго
1125
+ # возможно, стоит запараллелить обработку
1126
+ request = Request::read(request.id).first
1127
+ end
1128
+ end
1129
+ # TODO: разобраться, где тупня
1130
+ sql, sql_args = db_where
1131
+ result = Observation::from_db_rows(DB.execute("SELECT * FROM observations o#{ sql.empty? && '' || ' WHERE ' }#{ sql };", *sql_args))
1132
+ if !@r_match.empty?
1133
+ result = result.filter { |o| self.match?(o) }
1134
+ end
1135
+ if request != nil
1136
+ request.update do
1137
+ request.time = current_time if current_time
1138
+ end
1139
+ request.save
1140
+ end
1141
+ Status::status nil, "Query \##{ @int_key } : DONE"
1142
+ result
1143
+ end
1144
+
1145
+ end