module FDB::Tuple
def self._code_for(v)
def self._code_for(v) if v.nil? @@NULL_CODE elsif v.kind_of? String if v.encoding == Encoding::BINARY || v.encoding == Encoding::ASCII @@BYTES_CODE elsif v.encoding == Encoding::UTF_8 @@STRING_CODE else raise ArgumentError, "unsupported encoding #{v.encoding.name}" end elsif v.kind_of? Integer @@INT_ZERO_CODE elsif v.kind_of? TrueClass @@TRUE_CODE elsif v.kind_of? FalseClass @@FALSE_CODE elsif v.kind_of? SingleFloat @@FLOAT_CODE elsif v.kind_of? Float @@DOUBLE_CODE elsif v.kind_of? UUID @@UUID_CODE elsif v.kind_of? Array @@NESTED_CODE else raise ArgumentError, "unsupported type #{v.class}" end end
def self._compare_elems(v1, v2)
def self._compare_elems(v1, v2) c1 = _code_for(v1) c2 = _code_for(v2) return c1 <=> c2 unless c1 == c2 if c1 == @@NULL_CODE 0 elsif c1 == @@DOUBLE_CODE _compare_floats(v1, v2, true) elsif c1 == @@NESTED_CODE compare(v1, v2) # recurse else v1 <=> v2 end end
def self._compare_floats(f1, f2, is_double)
def self._compare_floats(f1, f2, is_double) # This converts the floats to their byte representation and then # does the comparison. Why? # 1) NaN comparison - Ruby doesn't really do this # 2) -0.0 == 0.0 in Ruby but not in our representation # It would be better to just take the floats and compare them, but # this way handles the edge cases correctly. b1 = float_adjust([f1].pack(is_double ? ">G" : ">g"), 0, (is_double ? 8 : 4), true) b2 = float_adjust([f2].pack(is_double ? ">G" : ">g"), 0, (is_double ? 8 : 4), true) b1 <=> b2 end
def self.bisect_left(list, item)
def self.bisect_left(list, item) count = 0 list.each{|i| return count if i >= item count += 1 } nil end
def self.compare(tuple1, tuple2)
def self.compare(tuple1, tuple2) i = 0 while i < tuple1.length && i < tuple2.length c = self._compare_elems(tuple1[i], tuple2[i]) return c unless c == 0 i += 1 end tuple1.length <=> tuple2.length end
def self.decode(v, pos)
def self.decode(v, pos) code = v.getbyte(pos) if code == @@NULL_CODE [nil, pos+1] elsif code == @@BYTES_CODE epos = find_terminator(v, pos+1) [v.slice(pos+1, epos-pos-1).gsub("\x00\xFF", "\x00"), epos+1] elsif code == @@STRING_CODE epos = find_terminator(v, pos+1) [v.slice(pos+1, epos-pos-1).gsub("\x00\xFF", "\x00").force_encoding("UTF-8"), epos+1] elsif code >= @@INT_ZERO_CODE && code <= @@POS_INT_END n = code - @@INT_ZERO_CODE [("\x00" * (8-n) + v.slice(pos+1, n)).unpack("Q>")[0], pos+n+1] elsif code >= @@NEG_INT_START and code < @@INT_ZERO_CODE n = @@INT_ZERO_CODE - code [("\x00" * (8-n) + v.slice(pos+1, n)).unpack("Q>")[0]-@@size_limits[n], pos+n+1] elsif code == @@FALSE_CODE [false, pos+1] elsif code == @@TRUE_CODE [true, pos+1] elsif code == @@FLOAT_CODE [SingleFloat.new(float_adjust(v, pos+1, 4, false).unpack("g")[0]), pos+5] elsif code == @@DOUBLE_CODE [float_adjust(v, pos+1, 8, false).unpack("G")[0], pos+9] elsif code == @@UUID_CODE [UUID.new(v.slice(pos+1, 16)), pos+17] elsif code == @@NESTED_CODE epos = pos+1 nested = [] while epos < v.length if v.getbyte(epos) == @@NULL_CODE if epos+1 < v.length and v.getbyte(epos+1) == 0xFF nested << nil epos += 2 else break end else r, epos = decode(v, epos) nested << r end end [nested, epos+1] else raise "Unknown data type in DB: " + code.ord.to_s end end
def self.encode(v, nested=false)
def self.encode(v, nested=false) if v.nil? if nested "\x00\xFF" else @@NULL_CODE.chr end elsif v.kind_of? String if v.encoding == Encoding::BINARY || v.encoding == Encoding::ASCII @@BYTES_CODE.chr + v.gsub("\x00", "\x00\xFF") + 0.chr elsif v.encoding == Encoding::UTF_8 @@STRING_CODE.chr + v.dup.force_encoding("BINARY").gsub("\x00", "\x00\xFF") + 0.chr else raise ArgumentError, "unsupported encoding #{v.encoding.name}" end elsif v.kind_of? Integer raise RangeError, "value outside inclusive range -2**64+1 to 2**64-1" if v < -2**64+1 || v > 2**64-1 if v == 0 @@INT_ZERO_CODE.chr elsif v > 0 n = bisect_left( @@size_limits, v ) (20+n).chr + [v].pack("Q>").slice(8-n, n) else n = bisect_left( @@size_limits, -v ) (20-n).chr + [@@size_limits[n]+v].pack("Q>").slice(8-n, n) end elsif v.kind_of? TrueClass @@TRUE_CODE.chr elsif v.kind_of? FalseClass @@FALSE_CODE.chr elsif v.kind_of? SingleFloat @@FLOAT_CODE.chr + float_adjust([v.value].pack("g"), 0, 4, true) elsif v.kind_of? Float @@DOUBLE_CODE.chr + float_adjust([v].pack("G"), 0, 8, true) elsif v.kind_of? UUID @@UUID_CODE.chr + v.data elsif v.kind_of? Array @@NESTED_CODE.chr + (v.map { |el| encode(el, true).force_encoding("BINARY") }).join + 0.chr else raise ArgumentError, "unsupported type #{v.class}" end end
def self.find_terminator(v, pos)
def self.find_terminator(v, pos) while true pos = v.index("\x00", pos) if !pos return v.length elsif pos+1 == v.length || v[pos+1] != "\xff" return pos end pos += 2 end end
def self.float_adjust(v, pos, length, encode)
def self.float_adjust(v, pos, length, encode) if (encode and v[pos].ord & 0x80 != 0x00) or (not encode and v[pos].ord & 0x80 == 0x00) v.slice(pos, length).chars.map { |b| (b.ord ^ 0xff).chr } .join else ret = v.slice(pos, length) ret[0] = (ret[0].ord ^ 0x80).chr ret end end
def self.pack(t)
def self.pack(t) (t.each_with_index.map {|el, i| begin (encode el).force_encoding("BINARY") rescue raise $!, "#{$!} at index #{i}", $!.backtrace end }).join end
def self.range(tuple=[])
def self.range(tuple=[]) p = pack(tuple) [p+"\x00", p+"\xFF"] end
def self.unpack(key)
def self.unpack(key) key = key.dup.force_encoding("BINARY") pos = 0 res = [] while pos < key.length r, pos = decode(key, pos) res << r end res end