Skip to content

Commit

Permalink
MONGOID-4906 Implement bitwise operators (#4917)
Browse files Browse the repository at this point in the history
  • Loading branch information
ksadoff authored Nov 23, 2020
1 parent 213a70f commit fb4c8d4
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/mongoid/matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ module Matcher

require 'mongoid/matcher/all'
require 'mongoid/matcher/and'
require 'mongoid/matcher/bits'
require 'mongoid/matcher/bits_all_clear'
require 'mongoid/matcher/bits_all_set'
require 'mongoid/matcher/bits_any_clear'
require 'mongoid/matcher/bits_any_set'
require 'mongoid/matcher/elem_match'
require 'mongoid/matcher/elem_match_expression'
require 'mongoid/matcher/eq'
Expand Down
41 changes: 41 additions & 0 deletions lib/mongoid/matcher/bits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module Mongoid
module Matcher

# @api private
module Bits
def matches?(exists, value, condition)
case value
when BSON::Binary
value = value.data.split('').map { |n| '%02x' % n.ord }.join.to_i(16)
end
case condition
when Array
array_matches?(value, condition)
when BSON::Binary
int_cond = condition.data.split('').map { |n| '%02x' % n.ord }.join.to_i(16)
int_matches?(value, int_cond)
when Integer
if condition < 0
raise Errors::InvalidQuery, "Invalid value for $#{operator_name} argument: negative integers are not allowed: #{condition}"
end
int_matches?(value, condition)
when Float
if (int_cond = condition.to_i).to_f == condition
if int_cond < 0
raise Errors::InvalidQuery, "Invalid value for $#{operator_name} argument: negative numbers are not allowed: #{condition}"
end
int_matches?(value, int_cond)
else
raise Errors::InvalidQuery, "Invalid type for $#{operator_name} argument: not representable as an integer: #{condition}"
end
else
raise Errors::InvalidQuery, "Invalid type for $#{operator_name} argument: #{condition}"
end
end

module_function def operator_name
name.sub(/.*::/, '').sub(/\A(.)/) { |l| l.downcase }
end
end
end
end
20 changes: 20 additions & 0 deletions lib/mongoid/matcher/bits_all_clear.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Mongoid
module Matcher

# @api private
module BitsAllClear
include Bits
extend self

def array_matches?(value, condition)
condition.all? do |c|
value & (1<<c) == 0
end
end

def int_matches?(value, condition)
value & condition == 0
end
end
end
end
20 changes: 20 additions & 0 deletions lib/mongoid/matcher/bits_all_set.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Mongoid
module Matcher

# @api private
module BitsAllSet
include Bits
extend self

def array_matches?(value, condition)
condition.all? do |c|
value & (1<<c) > 0
end
end

def int_matches?(value, condition)
value & condition == condition
end
end
end
end
20 changes: 20 additions & 0 deletions lib/mongoid/matcher/bits_any_clear.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Mongoid
module Matcher

# @api private
module BitsAnyClear
include Bits
extend self

def array_matches?(value, condition)
condition.any? do |c|
value & (1<<c) == 0
end
end

def int_matches?(value, condition)
value & condition < condition
end
end
end
end
20 changes: 20 additions & 0 deletions lib/mongoid/matcher/bits_any_set.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Mongoid
module Matcher

# @api private
module BitsAnySet
include Bits
extend self

def array_matches?(value, condition)
condition.any? do |c|
value & (1<<c) > 0
end
end

def int_matches?(value, condition)
value & condition > 0
end
end
end
end
4 changes: 4 additions & 0 deletions lib/mongoid/matcher/field_operator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ module Matcher
module FieldOperator
MAP = {
'$all' => All,
'$bitsAllClear' => BitsAllClear,
'$bitsAllSet' => BitsAllSet,
'$bitsAnyClear' => BitsAnyClear,
'$bitsAnySet' => BitsAnySet,
'$elemMatch' => ElemMatch,
'$eq' => Eq,
'$exists' => Exists,
Expand Down
159 changes: 159 additions & 0 deletions spec/integration/matcher_operator_data/bits_all_clear.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
- name: existing field - matches with int
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: 35
matches: true
min_server_version: 3.2

- name: existing field - does not match with int
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: 24
matches: false
min_server_version: 3.2

- name: existing field - matches with binData
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: !ruby/object:BSON::Binary
data: !binary |-
IW==
type: :generic
matches: true
min_server_version: 3.2

- name: existing field - does not match with binData
document:
a: 21
binaryValueofA: "00010101"
query:
a:
$bitsAllClear: !ruby/object:BSON::Binary
data: !binary |-
IW==
type: :generic
matches: false
min_server_version: 3.2

- name: existing binData field matches
document:
a: !ruby/object:BSON::Binary
data: !binary |-
IW==
type: :generic
query:
a:
$bitsAllClear: 20
matches: true
min_server_version: 3.2

- name: existing binData field does not match
document:
a: !ruby/object:BSON::Binary
data: !binary |-
IW==
type: :generic
query:
a:
$bitsAllClear: 15
matches: false
min_server_version: 3.2

- name: existing field - matches with array
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: [0, 3]
matches: true
min_server_version: 3.2

- name: existing field - does not match with array
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: [0, 2]
matches: false
min_server_version: 3.2

- name: float condition representable as an integer
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: 35.0
matches: true
min_server_version: 3.2

- name: float condition not representable as an integer
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: 35.1
error: true
min_server_version: 3.2

- name: string condition
document:
a: 54
binaryValueofA: "00110110"
query:
a:
$bitsAllClear: hello
error: true
min_server_version: 3.2

- name: empty array condition
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: []
matches: true
min_server_version: 3.2

- name: integer 0 condition
document:
a: 20
binaryValueofA: "00010100"
query:
a:
$bitsAllClear: 0
matches: true
min_server_version: 3.2

- name: negative integer condition
document:
a: 54
binaryValueofA: "00110110"
query:
a:
$bitsAllClear: -1
error: true
min_server_version: 3.2

- name: negative float condition
document:
a: 54
binaryValueofA: "00110110"
query:
a:
$bitsAllClear: -1.0
error: true
min_server_version: 3.2
Loading

0 comments on commit fb4c8d4

Please sign in to comment.