inat-get 0.8.0.11

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