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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +16 -0
- data/Rakefile +4 -0
- data/bin/inat-get +59 -0
- data/docs/logo.png +0 -0
- data/inat-get.gemspec +33 -0
- data/lib/extra/enum.rb +184 -0
- data/lib/extra/period.rb +252 -0
- data/lib/extra/uuid.rb +90 -0
- data/lib/inat/app/application.rb +50 -0
- data/lib/inat/app/config/messagelevel.rb +22 -0
- data/lib/inat/app/config/shiftage.rb +24 -0
- data/lib/inat/app/config/updatemode.rb +20 -0
- data/lib/inat/app/config.rb +296 -0
- data/lib/inat/app/globals.rb +80 -0
- data/lib/inat/app/info.rb +21 -0
- data/lib/inat/app/logging.rb +35 -0
- data/lib/inat/app/preamble.rb +27 -0
- data/lib/inat/app/status.rb +74 -0
- data/lib/inat/app/task/context.rb +47 -0
- data/lib/inat/app/task/dsl.rb +24 -0
- data/lib/inat/app/task.rb +75 -0
- data/lib/inat/data/api.rb +218 -0
- data/lib/inat/data/cache.rb +9 -0
- data/lib/inat/data/db.rb +87 -0
- data/lib/inat/data/ddl.rb +18 -0
- data/lib/inat/data/entity/comment.rb +29 -0
- data/lib/inat/data/entity/flag.rb +22 -0
- data/lib/inat/data/entity/identification.rb +45 -0
- data/lib/inat/data/entity/observation.rb +172 -0
- data/lib/inat/data/entity/observationphoto.rb +25 -0
- data/lib/inat/data/entity/observationsound.rb +26 -0
- data/lib/inat/data/entity/photo.rb +31 -0
- data/lib/inat/data/entity/place.rb +57 -0
- data/lib/inat/data/entity/project.rb +94 -0
- data/lib/inat/data/entity/projectadmin.rb +21 -0
- data/lib/inat/data/entity/projectobservationrule.rb +50 -0
- data/lib/inat/data/entity/request.rb +58 -0
- data/lib/inat/data/entity/sound.rb +27 -0
- data/lib/inat/data/entity/taxon.rb +94 -0
- data/lib/inat/data/entity/user.rb +67 -0
- data/lib/inat/data/entity/vote.rb +22 -0
- data/lib/inat/data/entity.rb +291 -0
- data/lib/inat/data/enums/conservationstatus.rb +30 -0
- data/lib/inat/data/enums/geoprivacy.rb +14 -0
- data/lib/inat/data/enums/iconictaxa.rb +23 -0
- data/lib/inat/data/enums/identificationcategory.rb +13 -0
- data/lib/inat/data/enums/licensecode.rb +16 -0
- data/lib/inat/data/enums/projectadminrole.rb +11 -0
- data/lib/inat/data/enums/projecttype.rb +37 -0
- data/lib/inat/data/enums/qualitygrade.rb +12 -0
- data/lib/inat/data/enums/rank.rb +60 -0
- data/lib/inat/data/model.rb +551 -0
- data/lib/inat/data/query.rb +1145 -0
- data/lib/inat/data/sets/dataset.rb +104 -0
- data/lib/inat/data/sets/list.rb +190 -0
- data/lib/inat/data/sets/listers.rb +15 -0
- data/lib/inat/data/sets/wrappers.rb +137 -0
- data/lib/inat/data/types/extras.rb +88 -0
- data/lib/inat/data/types/location.rb +89 -0
- data/lib/inat/data/types/std.rb +293 -0
- data/lib/inat/report/table.rb +135 -0
- data/lib/inat/utils/deep.rb +30 -0
- 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
|