//------------------------------------------------------------------------------ #include "tiptopbd.h" #include "wxTools.h" #include "mathTools.h" #include "tcDebug.h" #include #include //------------------------------------------------------------------------------ //Преобразовать в строку в соответствии стипом wxString getSQLValue(wxString t,wxString v) { if( t==_T("object") ) { if (v==_T("-1") || v==_T("")) v=_T("NULL"); } else if( t==_T("i4") ) { if( v==_T("") ) v=_T("NULL"); } else if( t==_T("f8") ) { if( v==_T("") ) v=_T("NULL"); replaseChars(v,_T(','),_T('.')); //Разделитель целой и дробной части точка } else if(t==_T("b")) { if(v==_T("")) v=_T("NULL"); else if(v==_T("1") || v==_T("t")) v=_T("true"); else if(v==_T("0") || v==_T("f")) v=_T("false"); } else if(t==_T("string")) { if(v==_T("")) { v=_T("NULL"); } else { v=replaceStrings(v,_T("'"),_T("\\'")); //так как в SQL строку вставляется v=_T("'")+v+_T("'"); } } else v=_T("'")+v+_T("'"); return v; } //------------------------------------------------------------------------------ //Сохранить UTF8 строку не больше 256 байт void saveUTF8StringBD(TiptopStream *os, wxString str) { const wxCharBuffer buff = str.mb_str(wxConvUTF8); size_t len0 = strlen(buff); unsigned char len1=0; if(len0<=256) len1=(unsigned char)len0; os->Write(&len1,1); if(len1>0) os->Write(buff,len1); }; //------------------------------------------------------------------------------ //Загрузить UTF8 строку из файла, строка не больше 256 байт wxString loadUTF8StringBD(TiptopStream *is) { char c; is->Read(&c,1); char* buf = new char[c]; is->Read(buf,c); wxString str=wxString::FromUTF8(buf,c); delete[] buf; return str; } //------------------------------------------------------------------------------ /*TiptopIndexFreeSpase::TiptopIndexFreeSpase(TiptopTable* table) { m_table=table; m_count=0; //пытаемся открыть файл для чтения m_fis=new wxFileInputStream(m_table->m_bd->m_path+IntToStr(m_table->m_id)+_T("_0")); m_fos=new wxFileOutputStream(m_table->m_bd->m_path+IntToStr(m_table->m_id)+_T("_0")); } //------------------------------------------------------------------------------ TiptopIndexFreeSpase::~TiptopIndexFreeSpase() { delete m_fis; delete m_fos; }*/ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //Самый простой тип индекса просто список упоряд по возрастанию TiptopIndex::TiptopIndex(TiptopField* fl) { /* m_count=0; m_fl=fl; //Поле(столбец) таблицы wxString file=m_fl->m_tb->m_bd->m_path+IntToStr(m_fl->m_tb->m_id)+_T('_')+IntToStr(m_fl->m_id)+_T(".i"); if(wxFileExists(file)) { m_file=new wxFFile(file,_T("a+b")); m_file->Seek(0,wxFromStart); uint4 i=0; m_file->Read(&i,4); //id файла m_file->Read(&i,4); //Версия m_file->Read(&m_type,4); //Под версия m_file->Read(&m_count,4); }else { m_file=new wxFFile(file,_T("a+b")); m_file->Seek(0,wxFromStart); uint4 i=0; m_file->Write(&file_id,4); //id файла m_file->Write(&file_version,4); //Версия m_type=1; m_file->Write(&m_type,4); //Под версия m_file->Write(&i,4); //Количество } */ } //------------------------------------------------------------------------------ TiptopIndex::~TiptopIndex() { delete m_file; } //------------------------------------------------------------------------------ //Добавить позицию в список (значение храниться в файле таблицы) void TiptopIndex::add(uint4 pos,uint4 id) { uint4 p; int4 num; getPos(id,p,num); //Узнаём номер элемента совподающего с текущем либо меньшим //Вставляем следующем элементом за num m_file->Seek(16+(num*4)+4+1,wxFromStart); for(uint4 i=num+1; iRead(&buf,4); //надо проверить m_file->Write(&buf,4); } m_file->Seek(16+(num*4)+4+1,wxFromStart); m_file->Write(&pos,4); m_count++; m_file->Seek(12,wxFromStart); m_file->Write(&m_count,4); } //------------------------------------------------------------------------------ //Поиск позиции по значению в списке (Список по возрастанию) //return точное совпадение то true вернулась меньшая запись то false //val - Значение которое мы ищем //pos - позиция с начала файла Указывает на найденное значение либо на меньшее (если меньшее return false) bool TiptopIndex::getPos(uint4 val,uint4& pos,int4& num) { /* if(m_count==0) //В списке ещё нет ни одного элемента { pos=0; num=-1; return false; }else if(m_count==1) //в списке 1 элемент { m_file->Seek(16+1,wxFromStart); //Курсор на 1й элемент m_file->Read(&pos,4); if(!m_fl->m_tb->ReadRecord(pos)) {pos=0; return false;} //Прочитали теперь проверяем if(m_fl->getUint4Val() < val) l = center; else r = center; }else { //Ищём среди сортированного списка значений делением на 2 unsigned int l = 0; unsigned int r = m_count-1; unsigned int center=0; while (r-l>1) { center = (l+r) / 2; //Читаем запись по центру m_file->Seek(16+center,wxFromStart); unsigned int pos; m_file->Read(&pos,4); if(!m_fl->m_tb->ReadRecord(pos)) {pos=0; return false;} //Прочитали теперь проверяем if(m_fl->getUint4Val() < val) l = center; else r = center; } num=center; if(m_fl->getUint4Val()==val) return true; else return false; } */ return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ TiptopField::TiptopField(TiptopTable* tb) { m_tb=tb; m_size=0; m_value=NULL; m_idx=NULL; m_NULL=false; } //------------------------------------------------------------------------------ TiptopField::~TiptopField() { if(m_value!=NULL) free(m_value); if(m_idx!=NULL) delete m_idx; } //------------------------------------------------------------------------------ //value - данные (не забирает во владения) //size - Размер void TiptopField::setValue(void* value,uint4 size) { if(m_value!=NULL) { free(m_value); m_value=NULL; } m_size=size; if(size==0) return; m_value=malloc(m_size); //copy data for(uint4 i=0; iRead(m_value,size); } //------------------------------------------------------------------------------ void TiptopField::setValue(int value) { setValue(&value,4); } //------------------------------------------------------------------------------ void TiptopField::setValue(bool value) { setValue(&value,1); } //------------------------------------------------------------------------------ void TiptopField::setValue(double value) { setValue(&value,8); } //------------------------------------------------------------------------------ void TiptopField::setValue(wxString value) { const wxCharBuffer buff = value.mb_str(wxConvUTF8); setValue((void*)buff.data(),strlen(buff)); } //------------------------------------------------------------------------------ uint4 TiptopField::getUint4Val() { return *((uint4*)m_value); } //------------------------------------------------------------------------------ int4 TiptopField::getIntVal() { return *((int4*)m_value); } //------------------------------------------------------------------------------ double TiptopField::getDblVal() { return *((double*)m_value); } //------------------------------------------------------------------------------ //Клонировать узел TiptopField* TiptopField::cloneNode() { TiptopField* f=new TiptopField(NULL); f->m_type_id=m_type_id; f->m_size=m_size; f->m_value=malloc(m_size); memcpy(f->m_value,m_value,m_size); return f; } //------------------------------------------------------------------------------ //Размер данных с учётом размера под количество байт uint4 TiptopField::getAllSize() { uint4 size=m_size; switch(m_type_id) { case BD_UTF8_1: size+=1; break; case BD_UTF8_2: size+=2; break; case BD_GPS_8: size+=4; break; case BD_GPSS_8: size+=4; break; case BD_BLOB_4: size+=4; break; } return size; } //------------------------------------------------------------------------------ bool TiptopField::getBoolVal() { return *((bool*)m_value); } //------------------------------------------------------------------------------ //Преобразовать данные в строку wxString TiptopField::getStrVal() { if(m_value==NULL) return _T(""); //Этого недолжно происходить if(m_type_id==BD_BOOL) { if(*((uint1*)m_value)==0) return _T("false"); else return _T("true"); } else if(m_type_id==BD_UINT1) return IntToStr(*((uint1*)m_value)); else if(m_type_id==BD_UINT2) return IntToStr(*((uint2*)m_value)); else if(m_type_id==BD_UINT4) return IntToStr(*((uint4*)m_value)); else if(m_type_id==BD_INT4) return IntToStr(*((int4*)m_value)); else if(m_type_id==BD_FLOAT4) return FloatToStr(*((float*)m_value),-1); else if(m_type_id==BD_FLOAT8) return DoubleToStr(*((double*)m_value),-1); else if(m_type_id==BD_UTF8_1 || m_type_id==BD_UTF8_2) { return wxString::FromUTF8((char*)m_value, m_size); } return _T(""); } //------------------------------------------------------------------------------ //Преобразовать данные в строку в соответствии с типом для вставки в SQL wxString TiptopField::getStrSQLVal() { wxString val=getStrVal(); switch(m_type_id) { case BD_BOOL: case BD_UINT1: case BD_UINT2: case BD_UINT4: case BD_UINT8: case BD_INT1: case BD_INT2: case BD_INT4: case BD_INT8: case BD_FLOAT4: case BD_FLOAT8: if(val==_T("")) val=_T("NULL"); replaseChars(val,_T(','),_T('.')); //Разделитель целой и дробной части точка break; case BD_UTF8_1: case BD_UTF8_2: if(val==_T("")) { val=_T("NULL"); } else { //$v=str_replace('\'','\\\'',$v); //так как в SQL строку вставляется val=_T("'")+val+_T("'"); } break; default: val=_T("'")+val+_T("'"); break; } return val; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //Конструктор таблицы базы данных //bd - База данных //id - id таблицы TiptopTable::TiptopTable(TiptopBD* bd,uint4 id) { m_bd=bd; //База данных m_id=id; //id таблицы (Либо запроса если таблица используется для передачи данных) m_Null=NULL;//байты для чтения NULL значений m_NullSize=0;//количество байт для NULL, = cell(count fields/8) m_Type=0; //По умолчанию плотная таблица //m_is=NULL; //m_os=NULL; //m_fos=NULL; m_file=NULL; m_stream=new TiptopStream(); //m_maxid=0; //максимальный id для поля (Теперь по позиции) //m_ok=true; fields=new TSimpleList(10,true); m_count=0; //Кол-во записей m_IndexFreeSize=new TiptopIndexFreeSize(); } //------------------------------------------------------------------------------ TiptopTable::~TiptopTable() { delete fields; if(m_file!=NULL) delete m_file; //TODO удалить через потоки надо делать //if(m_is!=NULL) delete m_is; //if(m_osOwner && m_os!=NULL) delete m_os; delete m_stream; if(m_Null!=NULL)delete m_Null; delete m_IndexFreeSize; } //------------------------------------------------------------------------------ //Добавить поле к таблице становится владельцем bool TiptopTable::AddField(TiptopField* fl) { if(fields->count()>255) return false; fields->add(fl); m_NullSize=ceil(fields->count()/8.0f); //подсчитываем кол-во байтов нужных для NULL значений if(m_Null!=NULL) delete m_Null; m_Null=new uint1[m_NullSize];//байты для чтения NULL значений for(uint1 i=0; im_name=name; fl->m_type_id=type_id; AddField(fl); return fl; } //------------------------------------------------------------------------------ //Добавить поле к таблице + преобразование типа из текста TiptopField* TiptopTable::AddField(wxString name,wxString type) { uint1 type_id=0; //Типы данных из metadata.xml if(type==_T("b")) type_id=BD_BOOL; if(type==_T("i1")) type_id=BD_INT1; if(type==_T("i4") || type==_T("object")) type_id=BD_INT4; if(type==_T("i8")) type_id=BD_INT8; if(type==_T("f4")) type_id=BD_FLOAT4; if(type==_T("f8")) type_id=BD_FLOAT8; if(type==_T("string")) type_id=BD_UTF8_2; //max 65535 символов return AddField(name,type_id); } //------------------------------------------------------------------------------ //Прочитать таблицу из текущего открытого потока bool TiptopTable::OpenTable() { //if(!is->IsOk()) return false; uint4 i; uint2 s; uint1 count; //*****читаем заголовок таблицы***** m_stream->Read(&s,2); if(s!=file_id) return false; //id файла m_stream->Read(&s,2); if(s!=file_version) return false; //версия файла m_stream->Read(&m_id,4); //id файла (или запроса данных) m_stream->Read(&m_Type,1); //Тип таблицы 0-"плотная" или 1-"жидкая" m_name=loadUTF8StringBD(m_stream); //название таблицы m_stream->Read(&count,1); //количество полей (столбцов) for(i=0; im_name=loadUTF8StringBD(m_stream); //название поля m_stream->Read(&tf->m_type_id,1);//id типа поля fields->add(tf); } m_NullSize=ceil(fields->count()/8.0f); //подсчитываем кол-во байтов нужных для NULL значений m_Null=new uint1[m_NullSize];//байты для чтения NULL значений return true; /* wxString path=m_bd->m_path+IntToStr(m_id)+_T(".t"); //по id ищем файл в этой же папке и считываем настройки if(wxFileExists(path)) { m_is=new wxFileInputStream(path); //wxFileOutputStream* m_fos=new wxFileOutputStream(path); //m_file=new wxFFile(path,_T("a+b")); //m_file->Seek(0,wxFromStart); OpenTable(m_is); }else return false; return true; */ } //------------------------------------------------------------------------------ //Прочитать из файла (создаётся поток файл всегда открыт) bool TiptopTable::OpenTable(wxString path) { if(!wxFileExists(path)) return false; wxFileInputStream* is=new wxFileInputStream(path); //Читаем в память (TODO надо сделать работу с файлом) m_stream->Clear(); m_stream->Write(is); delete is; return OpenTable(); } //------------------------------------------------------------------------------ //Прочитать заголовок таблицы из потока ///\param is - Откуда читаем данне (не становится владельцем) /*bool TiptopTable::OpenTable(wxInputStream* is) { if(!is->IsOk()) return false; uint4 i; uint2 s; uint1 count; //*****читаем заголовок таблицы***** is->Read(&s,2); if(s!=file_id) return false; //id файла is->Read(&s,2); if(s!=file_version) return false; //версия файла is->Read(&m_id,4); //id файла (или запроса данных) is->Read(&m_Type,1); //Тип таблицы 0-"плотная" или 1-"жидкая" m_name=loadUTF8String(is); //название таблицы is->Read(&count,1); //количество полей (столбцов) for(i=0;im_name=loadUTF8String(is); //название поля is->Read(&tf->m_type_id,1);//id типа поля fields->add(tf); } m_NullSize=ceil(fields->count()/8.0f); //подсчитываем кол-во байтов нужных для NULL значений m_Null=new uint1[m_NullSize];//байты для чтения NULL значений return true; }*/ //------------------------------------------------------------------------------ //Прочитать таблицу из потока в оперативную память ///\param is - Содержит заголовок и данные (не становится вледельцем) bool TiptopTable::ReadTableToMem(wxInputStream* is) { if(is==NULL) return false; m_stream->Clear(); //Очищяем память m_stream->SeekWrite(0); m_stream->Write(is); return OpenTable(); //Читаем заголовок } //------------------------------------------------------------------------------ //Переместить курсор чтения на первую запись (чтоб потом можно было последовательно прочитать всю таблицу) bool TiptopTable::SeekToStart() { if(m_Pos.count()<1) return false; m_stream->SeekRead(m_Pos[0]); return true; } //------------------------------------------------------------------------------ /*bool TiptopTable::SetOutputStream(wxMemoryOutputStream* os, bool owner) { if(m_osOwner && m_os!=NULL) delete m_os; m_os=os; m_osOwner=owner; return true; }*/ //------------------------------------------------------------------------------ //Найти и прочитать запись с заданным значением (использую для поска по идентификатору поля) //field - Название поля //val - Значение поля bool TiptopTable::ReadRecord(wxString field, uint4 val) { TiptopField* fl=getField(field); if(fl && fl->m_idx) { //uint4 pos=fl->m_idx->getPos(val); uint4 pos; int4 num; return fl->m_idx->getPos(val,pos,num); //return ReadRecord(pos); } return false; } //------------------------------------------------------------------------------ //is - Уже установлен на позицию с которой надо читать запись bool TiptopTable::ReadRecord(TiptopStream* is) { //if(is==NULL || !is->CanRead() || is->Eof()) return false; uint4 size=0; //Сколько байт прочитали из потока uint4 pos=0; //Позиция начала записи pos=is->TellRead(); if(pos>=is->GetSize()) return false; is->Read(m_Null,m_NullSize); size+=m_NullSize;//is->LastRead(); //Размер под NULL for(uint4 i=0; icount(); i++) { if(testBit(m_Null,i)) //Если есть данные то читаем { TiptopField* fl=fields->get(i); if(fl->m_type_id==BD_UINT1 || fl->m_type_id==BD_BOOL) //0 { fl->setValue(is,1); size+=1; } else if(fl->m_type_id==BD_UINT4) //3 { fl->setValue(is,4); size+=4; } else if(fl->m_type_id==BD_INT4) //13 { fl->setValue(is,4); size+=4; } else if(fl->m_type_id==BD_INT8) //17 { fl->setValue(is,8); size+=8; } else if(fl->m_type_id==BD_FLOAT4) //20 { fl->setValue(is,4); size+=4; } else if(fl->m_type_id==BD_FLOAT8) //22 { fl->setValue(is,8); size+=8; } else if(fl->m_type_id==BD_UTF8_1) //Не более 255 байт { uint1 i; is->Read(&i,1); fl->setValue(is,i); size+=1+i; } else if(fl->m_type_id==BD_UTF8_2) //Не более 65535 байт { uint2 i; is->Read(&i,2); fl->setValue(is,i); size+=2+i; } else if(fl->m_type_id==BD_GPS_8) //131 { uint4 i; is->Read(&i,4); fl->setValue(is,i); size+=4+i; } else if(fl->m_type_id==BD_GPSS_8) //132 как двоичные данные { uint4 i; is->Read(&i,4); fl->setValue(is,i); size+=4+i; } else if(fl->m_type_id==BD_BLOB_4) //143 { uint4 i; is->Read(&i,4); fl->setValue(is,i); size+=4+i; } else return false; } else fields->get(i)->setValue((void*)NULL,0); //NULL запись } if(size>0) { m_RecSize = size; m_RecPos = pos; return true; } else return false; } //------------------------------------------------------------------------------ //Перемещяем курсор в заданную позицию и читаем запись //pos - позиция в файле если -1 то не смещяемся bool TiptopTable::ReadRecord(int pos) { if(pos>0) m_stream->SeekRead(pos); return ReadRecord(m_stream); } //------------------------------------------------------------------------------ //Прочитать следующую запись из таблицы //savePos - Сохранять ли позицию записи при чтении bool TiptopTable::ReadNextRecord(bool savePos) { if(m_Type!=0) return false; //Пока только для плотной таблицы те. записи идут подподрят wxFileOffset ff=m_stream->TellRead(); //Начальная позиция записи if(ReadRecord(-1)) { if(savePos) m_Pos.add(ff); //Для плотной таблицы нет индекса поэтому пишем позицию записи в файле m_count++; //Количество записей return true; } return false; } //------------------------------------------------------------------------------ //Прочитать запись по её порядковому номеру (Если списка позиций нет то последовательно читать с начала (TODO и создаёт этот список)) //num - Номер записи нумерация с 0 bool TiptopTable::ReadRecordByNum(uint4 num) { if(numIsOk()) return false; m_stream->Write(&file_id,2); m_stream->Write(&file_version,2); m_stream->Write(&m_id,4); uint1 t=0; m_stream->Write(&t,1); //Протная таблица saveUTF8StringBD(m_stream,m_name); //max 256 байт t=fields->count(); m_stream->Write(&t,1); //Не больше 256 столбцов for(uint4 i=0; icount(); i++) { saveUTF8StringBD(m_stream,fields->get(i)->m_name); m_stream->Write(&fields->get(i)->m_type_id,1); } return true; } //------------------------------------------------------------------------------ //Записать 1 запись в m_os из полей //savePos - сохранять позицию записи //findPos - Искать свободное место в потоке, если нет то надо установить курсор в нужное место заранее. bool TiptopTable::WriteRecord(bool savePos, bool findPos) { uint4 newSize=0; uint4 newPos=0; if(findPos) //Потому что некоторые потоки не умеют выполнять Seek { //подсчитываем размер (потом сделать автоматический подсчёт при обновлении полей) newSize=m_NullSize; for(uint4 i=0; icount(); i++) { newSize+=fields->get(i)->getAllSize(); } newPos = findFreeSize(newSize); //Находим пустое место куда можно записать данные заданного размера (обычно в конец потока) m_stream->SeekWrite(newPos); } if(savePos) addRecPos(m_stream->TellWrite()); //Сохраняем адрес в потоке для текущей записи if(!WriteRecord()) return false; //закончили запись в поток отмечаем что место не пустое в индексе пустых мест if(findPos) m_IndexFreeSize->upd(newPos,newSize); return true; } //------------------------------------------------------------------------------ //Записать запись из полей в текущее положение потока m_os bool TiptopTable::WriteRecord() { //if(m_os==NULL || !m_os->IsOk() || m_Null==NULL) return false; //Записываем NULL значения for(uint1 i=0; icount(); i++) { if(fields->get(i)->m_value==NULL || fields->get(i)->m_size==0) setBit(m_Null,i,false); else setBit(m_Null,i,true); } m_stream->Write(m_Null,m_NullSize); //if(m_stream->LastWrite()!=m_NullSize) return false; //Записываем сами данные for(uint4 i=0; icount(); i++) { if(fields->get(i)->m_value!=NULL && fields->get(i)->m_size!=0) { switch(fields->get(i)->m_type_id) //Так как у разных типов своя структура. { case BD_UTF8_1: { uint1 ch=fields->get(i)->m_size; m_stream->Write(&ch,1); m_stream->Write(fields->get(i)->m_value,fields->get(i)->m_size); } break; case BD_UTF8_2: { uint2 ch=fields->get(i)->m_size; m_stream->Write(&ch,2); m_stream->Write(fields->get(i)->m_value,fields->get(i)->m_size); } break; case BD_BLOB_4: { uint4 ch=fields->get(i)->m_size; m_stream->Write(&ch,4); m_stream->Write(fields->get(i)->m_value,fields->get(i)->m_size); } break; default: //Для типов которым ненадо записывать размер m_stream->Write(fields->get(i)->m_value,fields->get(i)->m_size); } } } return true; } //------------------------------------------------------------------------------ //Обновить(удалить потом записать) 1 запись перед удалением запись должна быть прочитана для выяснения её позиции и размера. bool TiptopTable::UpdateRecord() { //if(m_os==NULL || !m_os->IsOk() || m_Null==NULL) return false; //if(m_noindex) return false; //Проверяем есть ли индекс позиций для всей таблицы //Подсчитываем размер новой записи если она равна старой то записываем в туже позицию uint4 newPos=m_RecPos; uint4 newSize=m_NullSize; for(uint4 i=0; icount(); i++) { newSize+=fields->get(i)->getAllSize(); } if(newSize==m_RecSize) //Позиция и размер записи не меняется { m_stream->SeekWrite(m_RecPos); WriteRecord(); } else { m_IndexFreeSize->add(m_RecPos, m_RecSize); //Добавляем пустое место в список m_Type=1; //Ставим признак не плотной таблицы newPos = findFreeSize(newSize); //Находим пустое место куда можно записать данные заданного размера (обычно в конец потока) m_stream->SeekWrite(newPos); if(WriteRecord()) { unsigned int p; if(m_Pos.Position(m_RecPos,p)) m_Pos.get(p)=newPos; //Обновляем позицию записи m_IndexFreeSize->upd(newPos,newSize); //Обновляем размер } } return true; // return false; } //------------------------------------------------------------------------------ bool TiptopTable::delRecPos(uint8 pos) //Удалить позицию записи в памяти { unsigned int p; if(m_Pos.Position(pos,p)) { m_Pos.del(p); return true; } else return false; //return false; }; //------------------------------------------------------------------------------ /*bool TiptopTable::isOk() { return m_ok; }*/ //------------------------------------------------------------------------------ //получить поле по имени TiptopField* TiptopTable::getField(wxString name) { name.Trim(true); name.Trim(false); //удалить пробелы с права и с лева for(uint4 i=0; icount(); i++) if(fields->get(i)->m_name==name) return fields->get(i); return NULL; } //------------------------------------------------------------------------------ //Добавить записи из заданной в текущую (поля должны совпадать по имени и по типу) //Начинает читать со следующей позиции из tb без сохранения позиции //tb - Таблица с которой читаем записи //spw - сохранять ли позицию при записи //spr - сохранять ли позицию при чтении bool TiptopTable::AppendTable(TiptopTable* tb, bool spw, bool spr) { if(tb==NULL) return false; bool result=true; int* fi0=new int[fields->count()]; //Позиции полей в локальной таблице int* fi1=new int[fields->count()]; //Позиции полей в присваиваемой таблице int pos=0; //Сколько нашли полей for(unsigned int i=0; icount(); i++) { for(unsigned int j=0; jfields->count(); j++) { if(fields->get(i)->m_name==tb->fields->get(j)->m_name && fields->get(i)->m_type_id == tb->fields->get(j)->m_type_id) { fi0[pos]=i; fi1[pos]=j; pos++; break; } } } //Обнулим чтоб в данные не попал мусор for(unsigned int i=0; icount(); i++) fields->get(i)->setValue((void*)NULL,0); //Ищем соответствующие поля if(pos>0) { while(tb->ReadNextRecord(spr)) //При считывании сохраняем позицию записи { for(int i=0; iget(fi0[i])->setValue(tb->fields->get(fi1[i])->m_value,tb->fields->get(fi1[i])->m_size); } if(!WriteRecord(spw,true)) { result=false; break; } } } delete fi0; delete fi1; return result; } //------------------------------------------------------------------------------ //Обновить записи из заданной в текущую (поля должны совпадать по имени и по типу) //tb - Таблица с которой читаем записи bool TiptopTable::UpdateTable(TiptopTable* tb) { if(tb==NULL) return false; bool result=true; //строим табличку соответствия номеров одинаковых полей int* fi0=new int[fields->count()]; //Позиции полей в локальной таблице int* fi1=new int[fields->count()]; //Позиции полей в присваиваемой таблице unsigned int cnt=0; //Сколько нашли одинаковых полей for(unsigned int i=0; icount(); i++) { for(unsigned int j=0; jfields->count(); j++) { if(fields->get(i)->m_name==tb->fields->get(j)->m_name && fields->get(i)->m_type_id == tb->fields->get(j)->m_type_id) { fi0[cnt]=i; fi1[cnt]=j; cnt++; break; } } } //Обновляем записи (пока по простому по равенству с полем id потом надо передавать условия) if(cnt>0) { buildPosIndex(); //Строим индекс если ещё не создан tb->SeekToStart(); while(tb->ReadNextRecord(true)) //При считывании сохраняем позицию записи { //Ищем и читаем локальную запись uint4 pos,num; if(FindFirstRecord(tb->getField(_T("id")),pos,num)) { //Переписываем поля for(unsigned int i=0; iget(fi0[i])->setValue(tb->fields->get(fi1[i])->m_value,tb->fields->get(fi1[i])->m_size); } //Записываем в память if(!UpdateRecord()) { result=false; break; } } } } delete fi0; delete fi1; return result; } //------------------------------------------------------------------------------ //Построить индекс позиций прочитав таблицу он начала до конца (только для плотной таблицы) bool TiptopTable::buildPosIndex() { if(m_Type!=0) return false; //только для плотной таблицы if(m_Pos.count()>0) ReadRecord(m_Pos.get(m_Pos.count()-1)); //Если есть хоть 1 позиция в списке позиций то перемещяемся на неё while(ReadNextRecord(true)) {} //В цикле читаем оставшиеся и запоминаем позиции return true; } //------------------------------------------------------------------------------ //создать таблицу по SQL запросу bool TiptopTable::CreateTable(wxString SQL) { m_name=getAfterLast(getBeforeFirst(SQL,_T("(")),' '); if(m_bd!=NULL && m_bd->TableExist(m_name)) return false; //добавляем поля по умолчанию id uint4,seq uint4,del uint1 TiptopField* field; field=new TiptopField(this); //field->m_id=++m_maxid; //id поля field->m_name=_T("id"); //название поля field->m_type_id=BD_UINT4; //id типа данных field->m_NULL=false; //Может ли быть NULL field->m_index=true; //Нуждается ли в индексации field->m_idx=new TiptopIndex(field); //Индекс поля (пока только для поля id) fields->add(field); field=new TiptopField(this); //field->m_id=++m_maxid; //id поля field->m_name=_T("seq"); //название поля field->m_type_id=BD_UINT4; //id типа данных field->m_NULL=false; fields->add(field); field=new TiptopField(this); //field->m_id=++m_maxid; //id поля field->m_name=_T("del"); //название поля field->m_type_id=BD_UINT1; //id типа данных field->m_NULL=false; fields->add(field); //перебираем поля в SQL запросе и добавляем к списку //CREATE TABLE tablename(id uint1,data uint2,data3 double); wxString fields=getBeforeLast(getAfterFirst(SQL,_T("(")),')')+_T(","); while (fields!=_T("")) { wxString sField=getBeforeFirst(fields,_T(",")); sField.Trim(true); sField.Trim(false); //удалить пробелы с права и с лева wxString sName=getBeforeFirst(sField,_T(" ")); wxString sType=getAfterLast(sField,' '); field=new TiptopField(this); field->m_type_id=0; field->m_NULL=true; field->m_index=false; field->m_name=sName; //название поля if(sType.Lower()==_T("uint1")) field->m_type_id=BD_UINT1; //id типа данных else if(sType.Lower()==_T("uint2")) field->m_type_id=BD_UINT2; //id типа данных else if(sType.Lower()==_T("uint4")) field->m_type_id=BD_UINT4; //id типа данных else if(sType.Lower()==_T("utf8_1")) field->m_type_id=BD_UTF8_1; //id типа данных else if(sType.Lower()==_T("gps_8")) field->m_type_id=BD_GPS_8; //id типа данных else if(sType.Lower()==_T("gpss_8")) field->m_type_id=BD_GPSS_8; //id типа данных if(field->m_type_id!=0) { //field->m_id=++m_maxid; //id поля this->fields->add(field); } else delete field; fields=getAfterFirst(fields,_T(",")); } //байты для чтения NULL значений m_NullSize=ceil(this->fields->count()/8.0f); m_Null=new uint1[m_NullSize]; //создаём файл таблицы по собранным данным m_file=new wxFFile(m_bd->m_path+IntToStr(m_id)+_T(".t"),_T("a+b")); m_file->Seek(0,wxFromStart); uint1 i=0,t=0; m_file->Write(&file_id,4); m_file->Write(&file_version,4); m_file->Write(&t,1); //тип таблицы 0=системный, 1=пользовательский,... m_file->Write(&i,4); //резерв saveUTF8String(m_file,m_name); m_file->Write(&i,4); //время последнего обновления таблицы секунд с 2000 года m_file->Write(&i,4); //резерв //m_file->Write(&m_maxid,4); //max id поля uint4 count=this->fields->count(); m_file->Write(&count,4); for(uint4 i=0; ifields->get(i); //m_file->Write(&tf->m_id,4); //id поля saveUTF8String(m_file,tf->m_name); //название m_file->Write(&tf->m_type_id,4); //id типа данных m_file->Write(&tf->m_NULL,1); //Может ли быть пустым m_file->Read(&tf->m_index,1); //Нужен ли индекс для этого поля (резерв) } return true; } //------------------------------------------------------------------------------ //добавить запись в таблицу а если есть индексы то и в индексы //INSERT INTO test(id,seq,del,name)values(1,2,0,\"Проба"" текста\"); bool TiptopTable::InsertInto(wxString SQL) { for(uint4 i=0; icount(); i++) fields->get(i)->setValue((void*)NULL,0); //обнуляем список столбцов wxString sFields=getBeforeFirst(getAfterFirst(SQL,_T("(")),_T(")"))+_T(","); //название полей через запятую wxString sValues=getBeforeLast(getAfterFirst(getAfterFirst(SQL,_T(")")),_T("(")),')')+_T(","); //значения через запятую while(sFields!=_T("")) { wxString name=cutFirstSubStr(sFields,_T(',')); wxString value=_T(""); TiptopField* fld=getField(name); if(fld==NULL) return false; //кроме текстового поля значения выбираются до запятой if(fld->m_type_id==BD_UTF8_1) { value=cutFirstSubStr(sValues,_T(',')); //TODO изменить только для тестирования (сейчас в тексте не должно быть запятых) value=_T("_")+value.SubString(1,value.length()-2); //1й символ размер строки wxCharBuffer buff = value.mb_str(wxConvUTF8); uint1 len=(uint1)strlen(buff); buff.data()[0]=len-1; //первый символ размер строки fld->setValue(buff.data(),len); } else if(fld->m_type_id==BD_UINT1) { value=cutFirstSubStr(sValues,_T(',')); uint1 v_uint=StrToInt(value); fld->setValue(&v_uint,1); } else if(fld->m_type_id==BD_UINT4) { value=cutFirstSubStr(sValues,_T(',')); uint4 v_uint=StrToInt(value); fld->setValue(&v_uint,4); } else if((fld->m_type_id==BD_GPS_4)||(fld->m_type_id==BD_GPS_8)||(fld->m_type_id==BD_GPSS_8)||(fld->m_type_id==BD_BLOB_4)) //если это массив байт то в качестве значения передаётся размер для выделения памяти { value=cutFirstSubStr(sValues,_T(',')); uint4 v_uint=StrToInt(value); if(v_uint>0) fld->setValue(&v_uint,4); } } //все данные подготовленны теперь ищется свободное место в файле для записи данных uint4 size=m_NullSize; //байт под NULL for(uint4 i=0; icount(); i++) { if((fields->get(i)->m_type_id==BD_GPS_4)||(fields->get(i)->m_type_id==BD_GPS_8)||(fields->get(i)->m_type_id==BD_GPSS_8)||(fields->get(i)->m_type_id==BD_BLOB_4)) { uint4 s=*(uint4*)fields->get(i)->m_value; size+=s+4; //размер + место под размер } else size+=fields->get(i)->getSize(); } uint4 pos=findFreeSize(size); //позиция в файле куда можно записать данную запись getField(_T("id"))->m_idx->add(pos,getField(_T("id"))->getUint4Val()); //сохраняем в индексе позицию m_file->Seek(pos,wxFromStart); for(uint4 i=0; icount(); i++) setBit(m_Null,i,(fields->get(i)->m_size!=0)); m_file->Write(m_Null,m_NullSize); for(uint4 i=0; icount(); i++) if(fields->get(i)->m_size>0) { m_file->Write(fields->get(i)->m_value,fields->get(i)->m_size); //пишем данные //если это даные переменной длины то записываем только размер а курсор перемещяем в конец данных if((fields->get(i)->m_type_id==BD_GPS_4)||(fields->get(i)->m_type_id==BD_GPS_8)||(fields->get(i)->m_type_id==BD_GPSS_8)||(fields->get(i)->m_type_id==BD_BLOB_4)) { uint4 s=*(uint4*)fields->get(i)->m_value; m_file->Seek(s,wxFromCurrent); } } return true; } //------------------------------------------------------------------------------ //найти позицию с свободным местом для записи данных (из индекса свободных мест с начала файла) //size - Размер необходимой свободной памяти 0 то макс свободное место uint4 TiptopTable::findFreeSize(uint4 size) { unsigned int pos=0; if(m_IndexFreeSize->get(size,pos)) { return pos; } else { return m_stream->GetSize(); //Позиция конца файла } } //------------------------------------------------------------------------------ ///<Найти позицию первой попавшейся записи с заданными значениями поля, результат позиция записи в памяти. //fl - Поле не пренадлежащие таблице по которому будет искаться запись //pos - результат позиция записи в потоке //num - результат порядковый номер записи bool TiptopTable::FindFirstRecord(TiptopField* fl,uint4 &pos,uint4 &num) { int4 p=getNumFieldOnName(fl->m_name); //Найти позицию записи с таким же именем if(fields->get(p)->m_type_id!=fl->m_type_id) return false; //Несовпали типы полей //Если нету индекса то поиск будет осуществлятся простым перебором if(m_Type==0) SeekToStart(); num=0; while(true) { if(m_Type!=0) //Не плотная таблица обязаны ходить по индексам { if(num>=m_Pos.count()) return true; m_stream->SeekRead(m_Pos[num]); } wxFileOffset ff=m_stream->TellRead(); //Начальная позиция записи if(!ReadRecord(-1)) return false; if(fields->get(p)->m_size==fl->m_size) { if(memcmp(fields->get(p)->m_value,fl->m_value,fl->m_size)==0) { pos=ff; return true; } } num++; } return false; } //------------------------------------------------------------------------------ int4 TiptopTable::getNumFieldOnName(wxString name) { for(uint4 i=0; icount(); i++) if(fields->get(i)->m_name==name) return (int4)i; return -1; } //------------------------------------------------------------------------------ //Установить курсор в начало BLOB блока //TODO надо сделать свой защищённый "поток" wxFFile* TiptopTable::getStream(uint4 id,wxString field) { /* //Через индекстный файл находим позицию первого байта записи по id getField(_T("id"))-> //Ищем uint4 seek=0; m_file->Read(m_Null,m_NullSize); int4 num=getNumFieldOnName(field); if(!testBit(m_Null,num)) return NULL; //если поле NULL то вернём NULL for(uint1 i=0;iget(i)->m_type_id==BD_UTF8_1) //Размер в 1м байте { m_file->Seek(seek,wxFromCurrent); uint1 size=0; m_file->Read(&size,1); m_file->Seek(size,wxFromCurrent); seek=0; }else if((fields->get(i)->m_type_id==BD_GPS_4)||(fields->get(i)->m_type_id==BD_GPS_8)||(fields->get(i)->m_type_id==BD_GPSS_8)||(fields->get(i)->m_type_id==BD_BLOB_4)) //Размер в 4х байтах { m_file->Seek(seek,wxFromCurrent); uint4 size=0; m_file->Read(&size,4); m_file->Seek(size,wxFromCurrent); seek=0; } else seek+=fields->get(i)->m_size; } } m_file->Seek(seek,wxFromCurrent); */ return m_file; //TODO надо как поток вернуть } //------------------------------------------------------------------------------ //Создаёт новый поток в который помещяет таблицу для чтения (TiptopTable не остаётся владельцем) /*wxMemoryInputStream* TiptopTable::GetInputStream() { wxStreamBuffer* osb=m_os->GetOutputStreamBuffer(); wxMemoryInputStream* mis = new wxMemoryInputStream(osb->GetBufferStart(),osb->GetBufferSize()); return mis; }*/ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ TiptopBD::TiptopBD() { m_maxid=0; m_fis=NULL; m_fos=NULL; list=new TSimpleList(10,true); } //------------------------------------------------------------------------------ TiptopBD::~TiptopBD() { delete list; delete m_fos; delete m_fis; } //------------------------------------------------------------------------------ //задаётся путь к ini файлу в котором список таблиц bool TiptopBD::Open(wxString path) { m_path=path; //открываем файл базы и читаем настройки if(wxFileExists(path+_T("bd.bd"))) { uint4 i,cnt,id; m_fis=new wxFileInputStream(path+_T("bd.bd")); m_fos=new wxFileOutputStream(path+_T("bd.bd")); m_fis->Read(&i,4); if(i!=file_id) goto Exit; //id файла m_fis->Read(&i,4); if(i!=file_version) goto Exit; //версия файла m_fis->Read(&m_maxid,4); //max id таблиц m_fis->Read(&cnt,4); //количество таблиц for(i=0; iRead(&id,4);//id таблицы TiptopTable* tb=new TiptopTable(this,id); if(tb->OpenTable()) list->add(tb); else delete tb; } Exit: m_ok=false; } else //создаём новый файл { if(!wxDirExists(path)) wxMkdir(path); m_fos=new wxFileOutputStream(path+_T("bd.bd")); m_fis=new wxFileInputStream(path+_T("bd.bd")); m_fos->Write(&file_id,4); m_fos->Write(&file_version,4); uint4 i=0; m_fos->Write(&i,4); //max id таблиц m_fos->Write(&i,4); //количество таблиц } return true; } //------------------------------------------------------------------------------ //существует ли таблица bool TiptopBD::TableExist(wxString name) { for(uint4 i=0; icount(); i++) if(list->get(i)->m_name==name) return true; return false; } //------------------------------------------------------------------------------ //взять таблицу TiptopTable* TiptopBD::getTable(wxString name) { for(uint4 i=0; icount(); i++) if(list->get(i)->m_name==name) return list->get(i); return NULL; } //------------------------------------------------------------------------------ bool TiptopBD::ExecSQL(wxString SQL) //выполнить SQL запрос без результата { //TODO анализатор SQL надо сделать по символам //CREATE TABLE tablename(id uint1,data uint2,data3 double); //id,seq,del - создаются автоматически if(SQL.Find(_T("CREATE TABLE"))!=wxNOT_FOUND) { TiptopTable* tb=new TiptopTable(this,m_maxid+1); if(tb->CreateTable(SQL)) { if(addTable(m_maxid+1)) //Запишем в файл списка таблиц list->add(tb); } else { delete tb; return false; } } else //INSERT INTO test(id,seq,del,name)values(1,2,0,"Проба текста"); if(SQL.Find(_T("INSERT INTO"))!=wxNOT_FOUND) { TiptopTable* tb=getTable(getAfterLast(getBeforeFirst(SQL,_T("(")),' ')); if(tb!=NULL) tb->InsertInto(SQL); else return false; } return true; } //------------------------------------------------------------------------------ //Записать id в файл списка таблиц, текущее количество +1 bool TiptopBD::addTable(uint4 id) { uint4 cnt,max; m_fis->SeekI(8,wxFromStart); m_fis->Read(&max,4); m_fis->Read(&cnt,4); if(max>=id) return false; m_fos->SeekO(8,wxFromStart); m_fos->Write(&id,4); cnt++; m_fos->Write(&cnt,4); m_fos->SeekO((cnt-1)*4,wxFromCurrent); m_fos->Write(&id,4); m_maxid=id; return true; } //------------------------------------------------------------------------------ //Загрузить UTF8 строку из файла, строка не больше 256 байт wxString loadUTF8String(wxInputStream *is) { char c; is->Read(&c,1); char* buf = new char[c]; is->Read(buf,c); wxString str=wxString::FromUTF8(buf,c); delete[] buf; return str; } //------------------------------------------------------------------------------ //Загрузить UTF8 строку из файла, строка не больше 256 байт wxString loadUTF8String(wxFFile *is) { char c; is->Read(&c,1); char* buf = new char[c]; is->Read(buf,c); wxString str=wxString::FromUTF8(buf,c); delete[] buf; return str; } //------------------------------------------------------------------------------