require 'digest/md5' class BtrieveTable include Btrieve attr_reader :tablename, :session, :pos_buffer, :schema def initialize(tablename, session, schema = nil) @tablename = tablename @session = session @pos_buffer = Btrieve.create_string_buffer(POS_BLOCK_SIZE) @schema = schema ? schema : @session.btrieve_schema.load_schema(@tablename) end def open(mode = NORMAL_MODE) btr_op(@session, OPEN, @pos_buffer, NULL_BUFFER, @schema[:filename], mode) end def find_in_unique_index(match, index_number=nil) finder(match, index_number) do |key_buffer, index| btr_record = BtrieveRecord.new(self) batch(){ btr_op(@session, GET_EQUAL, @pos_buffer, btr_record.data_buffer, key_buffer, index) } btr_record end end def find_in_index(match, index_number=nil) finder(match, index_number) do |key_buffer, index| absolute_match = match.keys.inject({}){|vals, key| vals[key] = match[key] if(match[key] != nil); vals} absolute_match_keys = absolute_match.keys result = [] batch() do btr_record = BtrieveRecord.new(self) btr_op(@session, GET_GREATER_THAN_OR_EQUAL, @pos_buffer, btr_record.data_buffer, key_buffer, index) result << btr_record while(true) btr_record = BtrieveRecord.new(self) op_result = btr_op(@session, GET_NEXT, @pos_buffer, btr_record.data_buffer, key_buffer, index_number, [OK, EOF, 21]) break unless btr_record.get_attributes(absolute_match_keys)==absolute_match and op_result!=EOF and op_result!=21 result << btr_record end end result end end def close btr_op(@session, CLOSE, @pos_buffer, NULL_BUFFER, NULL_BUFFER, NULL_KEY) end def each_record(mode = READ_ONLY_MODE, &block) batch(mode) do while(record = step_next) do yield record end end end def batch(mode = READ_ONLY_MODE, &block) open(mode) yield close end def version() Digest::MD5.hexdigest(BtrieveSchema.sort(schema[:columns]).inject([]){|array,column|array<vals[0].to_i,PK_KEYS[1]=>vals[1].to_i,PK_KEYS[2]=>vals[2],PK_KEYS[3]=>vals[3].to_i,PK_KEYS[4]=>vals[4].to_i} end def self.tablename_to_url(tablename) tablename.gsub(' & ','.and.').gsub('a/c ', 'ac.').gsub('es - o','es.o').gsub(' ','.') end def self.url_to_tablename(url_tablename) url_tablename.gsub('.and.', ' & ').gsub('ac.', 'a/c ').gsub('es.o','es - o').gsub('.',' ') end private def step_next btr_record = BtrieveRecord.new(self) result = btr_op(@session, STEP_NEXT, @pos_buffer, btr_record.data_buffer, NULL_BUFFER, NULL_KEY, [OK, EOF]) return false if(result == EOF) btr_record end def find_matching_index(match) keys = match.keys indices = @schema[:indices] indices.each_index(){ |index| return index if(indices[index].include_all?(keys)) } raise Exception.new("No matching index for #{keys}") end INTEGER_TYPES=['TINYINT','SMALLINT','INTEGER','BIT','UTINYINT','USMALLINT','UINTEGER','UBIGINT'] FLOAT_TYPES=['REAL','DOUBLE'] def finder(match, index_number = nil, &block) index_number ||= find_matching_index(match) index = @schema[:indices][index_number] columns = @schema[:columns] key_components = index.inject([]) do |vals, key| datatype = BtrieveSchema.lookup(columns[key][:datatype], columns[key][:size])[:name] puts "#{key}=>#{datatype}" value = match[key] value = value.to_f if FLOAT_TYPES.include?(datatype) value = value.to_i if INTEGER_TYPES.include?(datatype) vals << value vals end key_packer = index.inject("") do |packer, key| datatype = BtrieveSchema.lookup(columns[key][:datatype], columns[key][:size]) if(match[key] != nil) packer += "#{datatype[:unpacker]}#{datatype[:name].match('CHAR') ? columns[key][:size] : ''}" else packer += 'x'*columns[key][:size] end packer end key_buffer = key_components.pack(key_packer) yield(key_buffer, index_number) end end