Icontem

File: lib-js/xbstable.js

Recommend this page to a friend!
  Classes of Alexey Znaev  >  XBSDB  >  lib-js/xbstable.js  >  Download  
File: lib-js/xbstable.js
Role: Class source
Content type: text/plain
Description: Table class
Class: XBSDB
Manipulate arrays with an SQL-like language
Author: By
Last change: changed Listing priority
Date: 8 years ago
Size: 27,549 bytes
 

Contents

Class file image Download
///////////////////////////////////////////////////////////////////////////
//
//    XBSDB - Cross-Browser JavaScript Database library
//    Copyright (C) 2010 Alexey A.Znayev
//
//    This file is part of XBSDB.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//    Alexey A.Znayev, znaeff@mail.ru
//
///////////////////////////////////////////////////////////////////////////

// This flie contains private class(es) and/or method(s) and may be changed any time

///////////////////////////////////////////////////////////////////////////
// table (name, structure, number of records, hash of columns)
// aStructure - array of arrays [ name, type ]
function XBSTable(aStructure){
 // public fields
 this.sResultCode = 'OK';

 this._aStructure = aStructure;

 this._oCols = {};       // hash 'name' => 'column'
 this._aFieldNames = []; // solid array of names
 this._oFieldTypes = {}; // hash 'name' => 'type'

 for(var i = 0; i < this._aStructure.length; i++){ // solid
  if(typeof this._aStructure[i][1] != 'string'){
   this.sResultCode = 'TABLE_STRUCTURE_BAD';
   break;
  }
  switch(this._aStructure[i][1]){
   case 'string' :
   case 'number' :
    this._oCols[this._aStructure[i][0]] = []; // this way to store records provides the minimum of dereferences (3) to access a row value, see sFieldBefore & sFieldAfter
    this._aFieldNames.push(this._aStructure[i][0]);
    this._oFieldTypes[this._aStructure[i][0]] = this._aStructure[i][1];
    break;
   default :
    this.sResultCode = 'TABLE_FIELD_TYPE_UN';
  }//switch
  if(this.sResultCode != 'OK'){
   break;
  }
 }//for
 if(this.sResultCode == 'OK'){
  this._nRecords = 0;
  this._oKeys = {}; // keys hash ['id'] => [XBSKey object]
  this._aDeleted = []; // not solid not packed array 'recodr number' => 'true' for deleted records
 }
}

//XBSTable.prototype.sFieldBefore = 'table._oCols.';
//XBSTable.prototype.sFieldBefore = 'table._aRecords[n][table._oCols["';
XBSTable.prototype.sFieldBefore = 'table._oCols.';

//XBSTable.prototype.sFieldAfter = '.GetValue(n)';
//XBSTable.prototype.sFieldAfter = '"]]';
XBSTable.prototype.sFieldAfter = '[n]';

XBSTable.prototype.oAllowedKeyOperations = {
// 'a' : ['+'],
// 'n' : ['+','-','*','/'],
// 's' : ['+']
}

XBSTable.prototype.Load = function(aRows){
 this.sResultCode = 'OK';
 var nFields = this._aFieldNames.length;
 this._aDeleted = [];
 this._nRecords = aRows.length;
 for(var nRec = 0; nRec < this._nRecords; nRec++){
  for(var nField = 0; nField < nFields; nField++){
   this._oCols[this._aFieldNames[nField]][nRec] = aRows[nRec][nField];
  }
 }
 return true;
}

XBSTable.prototype.Dump = function(){
 this.sResultCode = 'OK';
 var oDump = {}, nFields = this._aStructure.length, aRecord;

 oDump['aStructure'] = [];
 for(var nField = 0; nField < nFields; nField++){
  oDump['aStructure'][nField] = [];
  oDump['aStructure'][nField][0] = this._aStructure[nField][0];
  oDump['aStructure'][nField][1] = this._aStructure[nField][1];
 }
 
 oDump['aData'] = [];
 for(var nRec = 0; nRec < this._nRecords; nRec++){
  if(!this._aDeleted[nRec]){
   aRecord = [];
   for(var nField = 0; nField < nFields; nField++){
    aRecord[nField] = this._oCols[this._aStructure[nField][0]][nRec];
   }
   oDump['aData'].push(aRecord);
  }
 }
 
 return oDump;
}

XBSTable.prototype.InsertOneByFields = function(aFields,aValues){
 this.sResultCode = 'OK';
 var nRecNum, i;
 if(this._aDeleted.length > 0){
  for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then store number and exit, not solid, with check
   if(this._aDeleted[i]){
    nRecNum = i;
    this._aDeleted.pop();
    break;
   }else{
    this._aDeleted.pop();
   }
  }
  for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then exit, not solid, with check
   if(this._aDeleted[i]){
    break;
   }else{
    this._aDeleted.pop();
   }
  }
 }else{
  nRecNum = this._nRecords;
 }
 if(nRecNum == this._nRecords){
  this._nRecords++;
 }
 for(i = 0; i < aFields.length; i++){ // solid
  this._oCols[aFields[i]][nRecNum] = aValues[i];
 }
 
 for(i in this._oKeys){
  if(!this._oKeys[i].Rebuild()){
   this.sResultCode = this._oKeys[i].sResultCode;
   return false;
  }
 }
 return true;
}

XBSTable.prototype.InsertOneByOrder = function(aValues){
 this.sResultCode = 'OK';
 var nRecNum, i;
 if(this._aDeleted.length > 0){
  for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then store number and exit, not solid, with check
   if(this._aDeleted[i]){
    nRecNum = i;
    this._aDeleted.pop();
    break;
   }else{
    this._aDeleted.pop();
   }
  }
  for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then exit, not solid, with check
   if(this._aDeleted[i]){
    break;
   }else{
    this._aDeleted.pop();
   }
  }
 }else{
  nRecNum = this._nRecords;
 }
 if(nRecNum == this._nRecords){
  this._nRecords++;
 }

 for(i = 0; (i < aValues.length) && (i < this._aFieldNames.length) ; i++){ // solid
  this._oCols[this._aFieldNames[i]][nRecNum] = aValues[i];
 }

 for(i in this._oKeys){
  if(!this._oKeys[i].Rebuild()){
   this.sResultCode = this._oKeys[i].sResultCode;
   return false;
  }
 }
 return true;
}

XBSTable.prototype.InsertManyByFields = function(aFields,aRows){
 this.sResultCode = 'OK';
 var nRecNum, nInserted = 0, aNewRecord, j, i;
 if(aRows.length > 0){
  for(j = 0; j < aRows.length; j++){ // solid
   if(this._aDeleted.length > 0){
    for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then store number and exit, not solid, with check
     if(this._aDeleted[i]){
      nRecNum = i;
      this._aDeleted.pop();
      break;
     }else{
      this._aDeleted.pop();
     }
    }
    for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then exit, not solid, with check
     if(this._aDeleted[i]){
      break;
     }else{
      this._aDeleted.pop();
     }
    }
   }else{
    nRecNum = this._nRecords;
   }
   if(nRecNum == this._nRecords){
    this._nRecords++;
   }

   for(i = 0; i < aFields.length; i++){ // solid
    this._oCols[aFields[i]][nRecNum] = aRows[j][i];
   }

   nInserted++;
  } //for j
  for(i in this._oKeys){
   if(!this._oKeys[i].Rebuild()){
    this.sResultCode = this._oKeys[i].sResultCode;
    return false;
   }
  }
 }
 return nInserted;
}

XBSTable.prototype.InsertManyByOrder = function(aRows){
 this.sResultCode = 'OK';
 var nRecNum, nInserted = 0, aNewRecord, j, i;
 if(aRows.length > 0){
  for(j = 0; j < aRows.length; j++){ // solid
   if(this._aDeleted.length > 0){
    for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then store number and exit, not solid, with check
     if(this._aDeleted[i]){
      nRecNum = i;
      this._aDeleted.pop();
      break;
     }else{
      this._aDeleted.pop();
     }
    }
    for(i = this._aDeleted.length - 1; i >= 0; i--){ // go back, seek true, delete elements from tail, when find true then exit, not solid, with check
     if(this._aDeleted[i]){
      break;
     }else{
      this._aDeleted.pop();
     }
    }
   }else{
    nRecNum = this._nRecords;
   }
   if(nRecNum == this._nRecords){
    this._nRecords++;
   }

   for(i = 0; (i < aRows[j].length) && (i < this._aFieldNames.length); i++){ // subarray solid
    this._oCols[this._aFieldNames[i]][nRecNum] = aRows[j][i];
   }

   nInserted++;
  }
  for(i in this._oKeys){
   if(!this._oKeys[i].Rebuild()){
    this.sResultCode = this._oKeys[i].sResultCode;
    return false;
   }
  }
 }

 return nInserted;
}

XBSTable.prototype.UpdateByFields = function(aFields,aValues,aRecords){
 this.sResultCode = 'OK';
 if(aRecords.length <= 0){
  return 0;
 }
 var nUpdated = 0, j, i;
 for(j = 0; j < aRecords.length; j++){ // solid
  if(this._IsRecordNumberOK(aRecords[j])){
   for(i = 0; i < aFields.length; i++){ // solid
    this._oCols[aFields[i]][aRecords[j]] = aValues[i];
   }
   nUpdated++;
  }
 }
 if(nUpdated>0){
  for(i in this._oKeys){
   // check names in indexes!
   for(j = 0; j < aFields.length; j++){// solid
    if(this._oKeys[i].oFields[aFields[j]]){
     this._oKeys[i].Rebuild();
     break;
    }
   }
  }
 }
 return nUpdated;
}

XBSTable.prototype.UpdateByOrder = function(aValues,aRecords){
 this.sResultCode = 'OK';
 if(aRecords.length <= 0){
  return 0;
 }
 var nUpdated = 0, j, i;
 for(j = 0; j < aRecords.length; j++){ // solid
  if(this._IsRecordNumberOK(aRecords[j])){
   for(i = 0; (i < aValues.length) && (i < this._aFieldNames.length); i++){ // solid
    this._oCols[this._aFieldNames[i]][aRecords[j]] = aValues[i];
   }
   nUpdated++;
  }
 }
 if(nUpdated>0){
  for(i in this._oKeys){
   // check names in indexes!
   for(j = 0; (j < aValues.length) && (j < this._aFieldNames.length); j++){// solid
    if(this._oKeys[i].oFields[this._aFieldNames[j]]){
     this._oKeys[i].Rebuild();
     break;
    }
   }
  }
 }
 return nUpdated;
}

XBSTable.prototype.Delete = function(aRecords){
 this.sResultCode = 'OK';
 if(aRecords.length > 0){
  var i, j;
  for(i = 0; i < aRecords.length; i++){ // solid
   for(j = 0; j < this._aFieldNames.length; j++){ // solid
    delete this._oCols[this._aFieldNames[j]][aRecords[i]];
   }
   this._aDeleted[aRecords[i]] = true;
   if(aRecords[i] == this._nRecords - 1){
    this._nRecords--;
   }
  }
  for(i in this._oKeys){
   this._oKeys[i].Rebuild();
  }
 }
 return aRecords.length;
}

XBSTable.prototype.AddKey = function(sExpression,aNumbersInit){
 this.sResultCode = 'OK';
 var oParser = new XBSEParser();
 var vParserResult = oParser.Parse(sExpression,{'aFieldNames':this._aFieldNames, 'sFieldBefore':this.sFieldBefore, 'sFieldAfter':this.sFieldAfter, 'sExprType':'KEY'});
 if(vParserResult && (vParserResult.sType == 'object_key_scalar')){
  var oKey = new XBSKey(this,vParserResult.sExprFieldsX,vParserResult.oFields);
  if(aNumbersInit && Object.prototype.toString.call(aNumbersInit) === '[object Array]'){
   oKey.Load(aNumbersInit);
   this._oKeys[vParserResult.sExprFields] = oKey;
   return vParserResult.sExprFields;
  }else{
   if(oKey.Rebuild()){
    this._oKeys[vParserResult.sExprFields] = oKey;
    return vParserResult.sExprFields;
   }else{
    this.sResultCode = oKey.sResultCode;
    return false;
   }
  }
 }else{
  this.sResultCode = oParser.sResultCode;
  return false;
 }
}

XBSTable.prototype.IsCol = function(sColName){
 if(this._oCols[sColName]){
  return true;
 }else{
  return false;
 }
}

XBSTable.prototype.GetColTypeByName = function(sColName){
 return this._oFieldTypes[sColName];
}

XBSTable.prototype.GetColTypeByNumber = function(nColNumber){
 return this._oFieldTypes[this._aFieldNames[nColNumber]];
}

XBSTable.prototype.IsKey = function(sKeyID){
 this.sResultCode = 'OK';
 if(this._oKeys[sKeyID]){
  return true;
 }else{
  return false;
 }
}

XBSTable.prototype.GetKeyIDs = function(){
 this.sResultCode = 'OK';
 var aKeyIDs = [];
 for(var i in this._oKeys){
  aKeyIDs.push(i);
 }
 return aKeyIDs;
}

XBSTable.prototype.DropKey = function(sKeyID){
 this.sResultCode = 'OK';
 if(this._oKeys[sKeyID]){
  delete this._oKeys[sKeyID];
 }
 return true;
}

XBSTable.prototype.GetKeyExpressionParsed = function(sExpr,aOpers){
 this.sResultCode = 'OK';
 if(!aOpers){ aOpers = ['+','-','*','/']} // think about possible using of this :-)
 var oParser = new XBSEParser();
 var vParserResult = oParser.Parse(sExpr,{'aFieldNames':this._aFieldNames, 'sFieldBefore':this.sFieldBefore, 'sFieldAfter':this.sFieldAfter, 'sExprType':'KEY'});
 if(vParserResult){
  return vParserResult;
 }else{
  this.sResultCode = oParser.sResultCode;
  return false;
 }
}

XBSTable.prototype.GetRecordsWhere = function(sWhere){
// base method, parse sWhere, process result and create record numbers array
 this.sResultCode = 'OK';
 var oParser = new XBSEParser();
 var vParserResult = oParser.Parse(sWhere,{'aFieldNames':this._aFieldNames, 'sFieldBefore':this.sFieldBefore, 'sFieldAfter':this.sFieldAfter, 'sExprType':'WHERE'});
 if(!vParserResult){
  this.sResultCode = oParser.sResultCode;
  return false;
 }

 var aFirstLevelRecords = this.GetRecordsAllUnPacked(); // temporary record numbers array, not packed
 var aResultRecords = this._GetRecordsByParsedArray(vParserResult,aFirstLevelRecords,1);
 
 if(aResultRecords){
  return aResultRecords;
 }else{
  return false;
 }
}

XBSTable.prototype.GetRows = function(aFields,aRecords,nOffset,nLimit){
 this.sResultCode = 'OK';
 var aRet = [], aRecord, nStart, nEnd, j, i;

 if(nOffset < 0){
  nStart = 0;
 }else if(nOffset > aRecords.length - 1){
  return aRet;
 }else{
  nStart = nOffset;
 }

 if(nLimit <= 0){
  return aRet;
 }else if(nLimit > aRecords.length - 1){
  nEnd = aRecords.length;
 }else{
  nEnd = nOffset + nLimit;
  if(nEnd > aRecords.length){
   nEnd = aRecords.length;
  }
 }

 for(i = nStart; i < nEnd; i++){ // solid
  aRecord = [];
  for(j = 0; j < aFields.length; j++){ // solid
   aRecord.push(this._oCols[aFields[j]][aRecords[i]]);
  }
  aRet.push(aRecord);
 }
 return aRet;
}

XBSTable.prototype.GetRowsAll = function(aRecords,nOffset,nLimit){
 this.sResultCode = 'OK';
 var aRet = [], aRecord, nStart, nEnd, j, i;

 if(nOffset < 0){
  nStart = 0;
 }else if(nOffset > aRecords.length - 1){
  return aRet;
 }else{
  nStart = nOffset;
 }

 if(nLimit <= 0){
  return aRet;
 }else if(nLimit > aRecords.length - 1){
  nEnd = aRecords.length;
 }else{
  nEnd = nOffset + nLimit;
  if(nEnd > aRecords.length){
   nEnd = aRecords.length;
  }
 }

 for(i = nStart; i < nEnd; i++){ // solid
  aRecord = [];
  for(j = 0; j < this._aFieldNames.length; j++){ // solid
   aRecord.push(this._oCols[this._aFieldNames[j]][aRecords[i]]);
  }
  aRet.push(aRecord);
 }
 return aRet;
}

XBSTable.prototype.GetRecordsAllPacked = function(){
 var aRet = [], i;
 for(i = 0; i < this._nRecords; i++){
  if(!this._aDeleted[i]){
   aRet.push(i);
  }
 }
 return aRet;
}

XBSTable.prototype.GetRecordsAllUnPacked = function(){
 return this._aRecordsUnPack(this.GetRecordsAllPacked());
}

XBSTable.prototype.SortRecordsOrderBy = function(aRecords,sOrderBy,bReverse){
 this.sResultCode = 'OK';
 var oParser = new XBSEParser();
 var vParserResult = oParser.Parse(sOrderBy,{'aFieldNames':this._aFieldNames, 'sFieldBefore':this.sFieldBefore, 'sFieldAfter':this.sFieldAfter, 'sExprType':'KEY'});
 if(vParserResult && (vParserResult.sType == 'object_key_scalar')){
  var oKey = new XBSKey(this,vParserResult.sExprFieldsX,vParserResult.oFields);
  if(oKey.Rebuild(aRecords)){
   if(bReverse){
    aRecords.reverse();
   }
   return true;
  }else{
   this.sResultCode = oKey.sResultCode;
   return false;
  }
 }else{
  this.sResultCode = oParser.sResultCode;
  return false;
 }
}

XBSTable.prototype.GetRecordsGroupBy = function(aRecords,sGroupBy){
 this.sResultCode = 'OK';
 var oParser = new XBSEParser();
 var vParserResult = oParser.Parse(sGroupBy,{'aFieldNames':this._aFieldNames, 'sFieldBefore':this.sFieldBefore, 'sFieldAfter':this.sFieldAfter, 'sExprType':'KEY'});
 if(vParserResult && (vParserResult.sType == 'object_key_scalar')){
  var vCurrValue, vPrevValue, aResultRecords = [], i, fFuncGroup = new Function('table', 'n', 'return(' + vParserResult.sExprFieldsX + ');');
  for(i = 0; i < aRecords.length; i++){ // solid
   vCurrValue = fFuncGroup(this,aRecords[i]); // because packed
   if(vCurrValue != vPrevValue){
    aResultRecords.push(aRecords[i]);
    vPrevValue = vCurrValue; 
   }
  }
  return aResultRecords;
 }else{
  this.sResultCode = oParser.sResultCode;
  return false;
 }
}

XBSTable.prototype.GetStructure = function(){
 this.sResultCode = 'OK';
 var aResult = [];
 for(var i=0; i<this._aStructure.length; i++){
  aResult.push([this._aStructure[i][0],this._aStructure[i][1]]);
 }
 return aResult;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods
///////////////////////////////////////////////////////////////////////////////////////////////////////////

XBSTable.prototype._GetRecordsByParsedArray = function(oPA,aUpperRecords,nLevel){
//array_OR (OR array)
//array_AND (AND array)
//object_calc_scalar (scalar calculation object)
//object_calc_logical (logical calculation object) - not done yet

// aUpperRecords - not packed, for faster intersection of temporary results with it
 var aResultRecords = [], aResultRecordsU = [], i, j, vConstant;
 switch(oPA.sType){
  case 'array_OR' : // (a)rray of (o)r elements
  // processing of OR-elements, result - union
  // aResultRecords - NOT PACKED here for faster unitization, will be packed after cycle
   var sExprOR = '', aBufRecords = [];
   for(i = 0; i < oPA.aOR.length; i++){ // solid
    switch(oPA.aOR[i].sType){
     case 'object_calc_scalar' :
      try{
       vConstant = eval(oPA.aOR[i].sConstant);
      }catch(e){
       this.sResultCode = "TABLE_BAD_CONSTANT : " + oPA.aOR[i].sConstant;
       return false;
      }
      if(this.IsKey(oPA.aOR[i].sExprFields)){ // key exists
       if(nLevel>1){
        // not first level, upper level records exist, intersect result with it
        aBufRecords = this._GetRecordsByKeyPacked(oPA.aOR[i].sExprFields,oPA.aOR[i].sOperation,vConstant,aUpperRecords);
       }else{
        // first level, no upper level records exist
        aBufRecords = this._GetRecordsByKeyPacked(oPA.aOR[i].sExprFields,oPA.aOR[i].sOperation,vConstant);
       }
       if(!aBufRecords){
        return false;
       }
       for(j = 0; j < aBufRecords.length; j++){ // solid
        aResultRecords[aBufRecords[j]] = true;
       }
      }else{
       if(sExprOR.length > 0){
        sExprOR += '||';
       }
       sExprOR += '(' + '(' + oPA.aOR[i].sExprFieldsX + ') ' + oPA.aOR[i].sOperation + ' "' + vConstant.toString().replace('"','\"','g') + '"'+ ')';
      }
      break;
     case 'object_calc_logical' : // (o)ject of (c)alculation of (l)ogical value
      if(sExprOR.length > 0){
       sExprOR += '||';
      }
      sExprOR += '(' + oPA.aOR[i].sExprFieldsX + ')';
      break;
     case 'array_OR' :
     case 'array_AND' :
      aBufRecords = this._GetRecordsByParsedArray(oPA.aOR[i],aUpperRecords,nLevel+1);
      if(!aBufRecords){
       return false;
      }
      for(j = 0; j < aBufRecords.length; j++){ // solid
       aResultRecords[aBufRecords[j]] = true;
      }
      break;
    }//switch
   }//for
   if(sExprOR.length > 0){
    aBufRecords = this._GetRecordsByExpr(sExprOR,aUpperRecords);
    if(!aBufRecords){
     return false;
    }
    for(j = 0; j < aBufRecords.length; j++){ // solid
     aResultRecords[aBufRecords[j]] = true;
    }
   }
   aResultRecords = this._aRecordsPack(aResultRecords);
   break;
  case 'array_AND' : // (a)rray of (a)nd elements
  // processing of AND-elements, result - intersection
  // aResultRecords - PACKED here for faster intersection, it will be used next seek on next condition of same key
   var sExprAND = '', aBufRecords = [], bFirstKeyCalc = true, bResultNotReady = true, sPrevKey = '', aBufRecords2;
   for(i = 0; i < oPA.aAND.length; i++){ // solid
    switch(oPA.aAND[i].sType){
     case 'object_calc_scalar' :
      try{
       vConstant = eval(oPA.aAND[i].sConstant);
      }catch(e){
       this.sResultCode = "TABLE_BAD_CONSTANT : " + oPA.aAND[i].sConstant;
       return false;
      }
      if(this.IsKey(oPA.aAND[i].sExprFields)){ // key exists
       if(bFirstKeyCalc){
        if(nLevel>1){
         aResultRecords = this._GetRecordsByKeyPacked(oPA.aAND[i].sExprFields,oPA.aAND[i].sOperation,vConstant,aUpperRecords);
        }else{
         aResultRecords = this._GetRecordsByKeyPacked(oPA.aAND[i].sExprFields,oPA.aAND[i].sOperation,vConstant);
        }
        if(!aResultRecords){
         return false;
        }
        bFirstKeyCalc = false;
       }else{
        if(sPrevKey==oPA.aAND[i].sExprFields){
         aResultRecords = this._GetRecordsByKeyPacked(oPA.aAND[i].sExprFields,oPA.aAND[i].sOperation,vConstant,null,aResultRecords);
        }else{
         aResultRecords = this._GetRecordsByKeyPacked(oPA.aAND[i].sExprFields,oPA.aAND[i].sOperation,vConstant,this._aRecordsUnPack(aResultRecords));
        }
       }
       if(!aResultRecords){
        return false;
       }
       bResultNotReady = false;
       sPrevKey = oPA.aAND[i].sExprFields;
      }else{
       if(sExprAND.length > 0){
        sExprAND += '&&';
       }
       sExprAND += '(' + '(' + oPA.aAND[i].sExprFieldsX + ') ' + oPA.aAND[i].sOperation + ' "' + vConstant.toString().replace('"','\"') + '"'+ ')';
      }
      break;
     case 'object_calc_logical' : // (o)ject of (c)alculation of (l)ogical value
      if(sExprAND.length > 0){
       sExprAND += '&&';
      }
      sExprAND += '(' + oPA.aAND[i].sExprFieldsX + ')';
      break;
     case 'array_OR' :
     case 'array_AND' :
      if(bFirstKeyCalc){
       aResultRecords = this._GetRecordsByParsedArray(oPA.aAND[i],aUpperRecords,nLevel+1);
      }else{
       aResultRecords = this._GetRecordsByParsedArray(oPA.aAND[i],this._aRecordsUnPack(aResultRecords),nLevel+1);
      }
      if(!aResultRecords){
       return false;
      }
      bResultNotReady = false;
      break;
    }//switch
    if(!bFirstKeyCalc && aResultRecords.length == 0){
     break;
    }
   }//for
   if(sExprAND.length > 0){
    if(bResultNotReady){
     aResultRecords = this._GetRecordsByExpr(sExprAND,aUpperRecords);
     if(!aResultRecords){
      return false;
     }
    }else{
     if(aResultRecords.length > 0){
      aResultRecords = this._GetRecordsByExpr(sExprAND,this._aRecordsUnPack(aResultRecords));
      if(!aResultRecords){
       return false;
      }
     }
    }
   }
   break;
  case 'object_calc_scalar' :
  // select records on object fields - expression with fields, comparsion, value
   try{
    vConstant = eval(oPA.sConstant);
   }catch(e){
    this.sResultCode = "TABLE_BAD_CONSTANT : " + oPA.sConstant;
    return false;
   }
   if(this.IsKey(oPA.sExprFields)){ // key exists
    if(nLevel>1){
     aResultRecords = this._GetRecordsByKeyPacked(oPA.sExprFields,oPA.sOperation,vConstant,aUpperRecords);
    }else{
     aResultRecords = this._GetRecordsByKeyPacked(oPA.sExprFields,oPA.sOperation,vConstant);
    }
    if(!aResultRecords){
     return false;
    }
   }else{
    var sExpr = '(' + oPA.sExprFieldsX + ') ' + oPA.sOperation + ' "' + vConstant.toString().replace('"','\"') + '"';
    aResultRecords = this._GetRecordsByExpr(sExpr,aUpperRecords);
    if(!aResultRecords){
     return false;
    }
   }
   break;
  case 'object_calc_logical' :
  // select records on object fields - expression with fields only
    var sExpr = '(' + oPA.sExprFieldsX + ')';
    aResultRecords = this._GetRecordsByExpr(sExpr,aUpperRecords);
    if(!aResultRecords){
      return false;
    }
   break;
 }
 if(this.sResultCode == 'OK'){
  return aResultRecords;
 }else{
  return false;
 }
}

XBSTable.prototype._aRecordsPack = function(aRecordsU){
 var aRecordsP = [], i;
 for(i = 0; i < aRecordsU.length; i++){ // solid or not - no difference, pack true values only
  if(aRecordsU[i] == true){
   aRecordsP.push(i);
  }
 }
 return aRecordsP;
}

XBSTable.prototype._aRecordsUnPack = function(aRecordsP){
 var aRecordsU = [], i;
 for(i = 0; i < aRecordsP.length; i++){ // solid
  aRecordsU[aRecordsP[i]] = true;
 }
 return aRecordsU;
}

XBSTable.prototype._GetRecordsByKeyUnpacked = function(sKeyID,sKeyOper,vKeyValue,aUpperRecords,aExtRecords){
 var aResultRecords;
 var oKey = this._oKeys[sKeyID];
 switch(sKeyOper){
  case '>=' : aResultRecords = oKey.GetRecordsUnpackedGREQ(vKeyValue,aUpperRecords,aExtRecords); break; //! index and value types match check
  case '<=' : aResultRecords = oKey.GetRecordsUnpackedLEEQ(vKeyValue,aUpperRecords,aExtRecords); break; 
  case '!=' : aResultRecords = oKey.GetRecordsUnpackedNE(vKeyValue,aUpperRecords,aExtRecords);   break; 
  case '>'  : aResultRecords = oKey.GetRecordsUnpackedGR(vKeyValue,aUpperRecords,aExtRecords);   break;
  case '<'  : aResultRecords = oKey.GetRecordsUnpackedLE(vKeyValue,aUpperRecords,aExtRecords);   break;
  case '==' : aResultRecords = oKey.GetRecordsUnpackedEQ(vKeyValue,aUpperRecords,aExtRecords);   break;
 }
 if(!aResultRecords){
  this.sResultCode = oKey.sResultCode;
  return false;
 }
 return aResultRecords;
}

XBSTable.prototype._GetRecordsByKeyPacked = function(sKeyID,sKeyOper,vKeyValue,aUpperRecords,aExtRecords){
 var aResultRecords;
 var oKey = this._oKeys[sKeyID];
 switch(sKeyOper){
  case '>=' : aResultRecords = oKey.GetRecordsPackedGREQ(vKeyValue,aUpperRecords,aExtRecords); break; //! index and value types match check
  case '<=' : aResultRecords = oKey.GetRecordsPackedLEEQ(vKeyValue,aUpperRecords,aExtRecords); break; 
  case '!=' : aResultRecords = oKey.GetRecordsPackedNE(vKeyValue,aUpperRecords,aExtRecords);   break; 
  case '>'  : aResultRecords = oKey.GetRecordsPackedGR(vKeyValue,aUpperRecords,aExtRecords);   break;
  case '<'  : aResultRecords = oKey.GetRecordsPackedLE(vKeyValue,aUpperRecords,aExtRecords);   break;
  case '==' : aResultRecords = oKey.GetRecordsPackedEQ(vKeyValue,aUpperRecords,aExtRecords);   break;
 }
 if(!aResultRecords){ 
  this.sResultCode = oKey.sResultCode;
  return false;
 }
 return aResultRecords;
}

XBSTable.prototype._GetRecordsByExpr = function(sExpr,aUpperRecords){
 var aResultRecords = [], i, fFunc = new Function('table', 'n', 'return(' + sExpr + ');');
 try{
  for(i = 0; i < aUpperRecords.length; i++){ // check solid or not
   if(aUpperRecords[i] && fFunc(this,i)){ // aUpperRecords[i] check true or not BEFORE function call
    aResultRecords.push(i);
   }
  }
 }catch(e){
  this.sResultCode = 'TABLE_NOT_GET_RECORDS_BY_EXPR_UN';
  return false;
 }
 return aResultRecords;
}

XBSTable.prototype._IsRecordNumberOK = function(nNumber){
 if(nNumber < 0 || nNumber >= this._nRecords){
  return false;
 }
 for(var i = 0; i < this._aDeleted.length; i++){ // not solid, not packed
  if(this._aDeleted[nNumber]){
   return false;
  }
 }
 return true;
}