noiseless 0.7.1 → 0.7.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b661712a5930c3da3a455f6e70c2d99cfbc424cf4e007c58abd587ab170854f
4
- data.tar.gz: 547b60b50d5efb9b32da878523d96d5a58c5a7a645ab8bed13ebec40269370b7
3
+ metadata.gz: 3a2305879802aa874c213e1a5a87707ec7222af979d21834f21f4fd2c994cb48
4
+ data.tar.gz: 065cf12ecb097ac17ee1e33d99d5df0ccb79ca4ea6713640f82f6bcac2fad51a
5
5
  SHA512:
6
- metadata.gz: 17a625f20fb2c879f427d429c82ecb4b9c2b338ffec03bd69de83ee9c2b800800c57eaef0bb6f4d2e32876e91497c5b12bdf389a2f1edcb055bc228764b03473
7
- data.tar.gz: 4799c4ba712b8d097b30553ac800caa798349d70c0ecbc7ca901b3f54752a9ed48ce2fcb4d3784d76117c0b7598801efc6f9e3685c5c945e34639740970a8d2e
6
+ metadata.gz: dc5302df3e73a9a413e1a7382db1334e11dd28ee2564bb5ae9e0f5d6a2d62f7b241584463678ef6f0db6112740c70d5687fdc425903979056c9d61ff10140bc6
7
+ data.tar.gz: baba1a26426413fef74f6f900d3146aa9c7bcda385030dd3b722e8277c5dc5b8a25bb79adbf6dc9d0c695e4d3b43e4353e4fe99e3bffb7ec5fc5a0d18ff566cf
@@ -169,11 +169,11 @@ module Noiseless
169
169
  when AST::MultiMatch
170
170
  apply_multi_match(scope, node, model)
171
171
  when AST::Wildcard
172
- apply_wildcard(scope, node)
172
+ apply_wildcard(scope, node, model)
173
173
  when AST::Range
174
- apply_range(scope, node)
174
+ apply_range(scope, node, model)
175
175
  when AST::Prefix
176
- apply_prefix(scope, node)
176
+ apply_prefix(scope, node, model)
177
177
  else
178
178
  scope
179
179
  end
@@ -190,11 +190,12 @@ module Noiseless
190
190
  # cannot be satisfied here; fail closed rather than matching everything.
191
191
  return scope.none unless column?(model, field)
192
192
 
193
- # Use pg_trgm similarity for fuzzy matching with unaccent
193
+ # Use pg_trgm similarity for fuzzy matching, accent-insensitive when
194
+ # the unaccent extension is present.
194
195
  if trgm_available? && text_column?(model, field)
195
196
  scope.where(
196
- "unaccent(#{quoted_column(field)}) % unaccent(?) OR " \
197
- "unaccent(#{quoted_column(field)}) ILIKE unaccent(?)",
197
+ "#{fuzzy_column(field)} % #{fuzzy_param} OR " \
198
+ "#{fuzzy_column(field)} ILIKE #{fuzzy_param}",
198
199
  value,
199
200
  "%#{sanitize_like(value)}%"
200
201
  )
@@ -213,8 +214,8 @@ module Noiseless
213
214
 
214
215
  conditions = fields.map do |field|
215
216
  if trgm_available? && text_column?(model, field)
216
- "(unaccent(#{quoted_column(field)}) % unaccent(?) OR " \
217
- "unaccent(#{quoted_column(field)}) ILIKE unaccent(?))"
217
+ "(#{fuzzy_column(field)} % #{fuzzy_param} OR " \
218
+ "#{fuzzy_column(field)} ILIKE #{fuzzy_param})"
218
219
  else
219
220
  "#{quoted_column(field)} ILIKE ?"
220
221
  end
@@ -231,15 +232,19 @@ module Noiseless
231
232
  scope.where(conditions.join(" OR "), *params)
232
233
  end
233
234
 
234
- def apply_wildcard(scope, node)
235
+ def apply_wildcard(scope, node, model = nil)
235
236
  field = node.field.to_s
237
+ return scope.none if model && !column?(model, field)
238
+
236
239
  # Convert OpenSearch wildcards to SQL: * -> %, ? -> _
237
240
  pattern = node.value.to_s.tr("*", "%").tr("?", "_")
238
241
 
239
242
  scope.where("#{quoted_column(field)} ILIKE ?", pattern)
240
243
  end
241
244
 
242
- def apply_range(scope, node)
245
+ def apply_range(scope, node, model = nil)
246
+ return scope.none if model && !column?(model, node.field.to_s)
247
+
243
248
  field = quoted_column(node.field.to_s)
244
249
 
245
250
  scope = scope.where("#{field} >= ?", node.gte) if node.gte
@@ -250,7 +255,9 @@ module Noiseless
250
255
  scope
251
256
  end
252
257
 
253
- def apply_prefix(scope, node)
258
+ def apply_prefix(scope, node, model = nil)
259
+ return scope.none if model && !column?(model, node.field.to_s)
260
+
254
261
  scope.where("#{quoted_column(node.field.to_s)} ILIKE ?", "#{sanitize_like(node.value)}%")
255
262
  end
256
263
 
@@ -266,6 +273,8 @@ module Noiseless
266
273
  # A filter on a mapping-only field cannot be enforced;
267
274
  # silently dropping it would broaden results, so fail closed.
268
275
  scope.none
276
+ elsif model && array_column?(model, node.field.to_s)
277
+ apply_array_filter(scope, node.field.to_s, value, model)
269
278
  else
270
279
  scope.where(node.field => value)
271
280
  end
@@ -274,6 +283,13 @@ module Noiseless
274
283
  scope
275
284
  end
276
285
 
286
+ def apply_array_filter(scope, field, value, model)
287
+ cast = "#{model.columns_hash[field].sql_type.sub(/\[\]\z/, '')}[]"
288
+ operator = value.is_a?(Array) ? "&&" : "@>"
289
+
290
+ scope.where("#{quoted_column(field)} #{operator} ARRAY[?]::#{cast}", value)
291
+ end
292
+
277
293
  def apply_geo_filter(scope, node)
278
294
  # Requires PostGIS
279
295
  geo_config = node.value[:geo_distance]
@@ -297,17 +313,20 @@ module Noiseless
297
313
  end
298
314
 
299
315
  def apply_sorting(scope, sort_nodes, model = nil)
300
- return scope if sort_nodes.empty?
301
-
316
+ sorted_fields = []
302
317
  order_clauses = sort_nodes.filter_map do |node|
303
318
  field = node.field.to_s
304
319
  # Sorting on a mapping-only field is cosmetic — drop it instead of
305
320
  # erroring the whole query.
306
321
  next if model && !column?(model, field)
307
322
 
323
+ sorted_fields << field
308
324
  direction = node.direction.to_s.upcase == "DESC" ? "DESC" : "ASC"
309
325
  "#{quoted_column(field)} #{direction}"
310
326
  end
327
+
328
+ primary_key = model.respond_to?(:primary_key) ? model.primary_key : nil
329
+ order_clauses << "#{quoted_column(primary_key)} ASC" if primary_key && !sorted_fields.include?(primary_key.to_s)
311
330
  return scope if order_clauses.empty?
312
331
 
313
332
  scope.order(Arel.sql(order_clauses.join(", ")))
@@ -415,6 +434,18 @@ module Noiseless
415
434
  column && %i[string text citext].include?(column.type) && !column.array
416
435
  end
417
436
 
437
+ def array_column?(model, field)
438
+ model.columns_hash[field.to_s]&.array
439
+ end
440
+
441
+ def fuzzy_column(field)
442
+ unaccent_available? ? "unaccent(#{quoted_column(field)})" : quoted_column(field)
443
+ end
444
+
445
+ def fuzzy_param
446
+ unaccent_available? ? "unaccent(?)" : "?"
447
+ end
448
+
418
449
  def quoted_column(field)
419
450
  ActiveRecord::Base.connection.quote_column_name(field)
420
451
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Noiseless
4
- VERSION = "0.7.1"
4
+ VERSION = "0.7.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noiseless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih