Files
Tools_CPP/lib/tiptopbd.cpp
2024-11-01 12:23:13 +05:00

1455 lines
49 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//------------------------------------------------------------------------------
#include "tiptopbd.h"
#include "wxTools.h"
#include "mathTools.h"
#include "tcDebug.h"
#include <wx/wfstream.h>
#include <math.h>
//------------------------------------------------------------------------------
//Преобразовать в строку в соответствии стипом
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; i<m_count; i++) //Сдвигаем элементы
{
uint4 buf;
m_file->Read(&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; i<m_size; i++) ((char*)m_value)[i]=((char*)value)[i];
}
//------------------------------------------------------------------------------
//Читаем из потока
void TiptopField::setValue(TiptopStream* is,uint4 size)
{
if(m_value!=NULL)
{
free(m_value);
m_value=NULL;
}
m_size=size;
if(size==0) return;
m_value=malloc(m_size);
//read data
is->Read(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<TiptopField*>(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; i<m_NullSize; i++) m_Null[i]=0;
return true;
}
//------------------------------------------------------------------------------
//Добавить поле к таблице становится владельцем
TiptopField* TiptopTable::AddField(wxString name,uint1 type_id)
{
TiptopField* fl=new TiptopField(this);
fl->m_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; i<count; i++)
{
TiptopField* tf=new TiptopField(this);
tf->m_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;i<count;i++)
{
TiptopField* tf=new TiptopField(this);
tf->m_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; i<fields->count(); 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(num<m_Pos.count())
return ReadRecord(m_Pos[num]);
return false;
}
//------------------------------------------------------------------------------
//Записать заголовок таблицы в поток m_os
bool TiptopTable::WriteHeader()
{
//if(m_os==NULL || !m_os->IsOk()) 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; i<fields->count(); 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; i<fields->count(); 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; i<m_NullSize; i++) m_Null[i]=0;
for(uint4 i=0; i<fields->count(); 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; i<fields->count(); 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; i<fields->count(); 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; i<fields->count(); 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; i<fields->count(); i++)
{
for(unsigned int j=0; j<tb->fields->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; i<fields->count(); i++)
fields->get(i)->setValue((void*)NULL,0);
//Ищем соответствующие поля
if(pos>0)
{
while(tb->ReadNextRecord(spr)) //При считывании сохраняем позицию записи
{
for(int i=0; i<pos; i++)
{
fields->get(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; i<fields->count(); i++)
{
for(unsigned int j=0; j<tb->fields->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; i<cnt; i++)
{
fields->get(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; i<count; i++)
{
TiptopField* tf=this->fields->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; i<fields->count(); 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; i<fields->count(); 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; i<fields->count(); i++) setBit(m_Null,i,(fields->get(i)->m_size!=0));
m_file->Write(m_Null,m_NullSize);
for(uint4 i=0; i<fields->count(); 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; i<fields->count(); 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;i<num;i++)
{
if(testBit(m_Null,i)) //если не NULL то учитываем размер
{
if(fields->get(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<TiptopTable*>(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; i<cnt; i++)
{
m_fis->Read(&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; i<list->count(); i++)
if(list->get(i)->m_name==name) return true;
return false;
}
//------------------------------------------------------------------------------
//взять таблицу
TiptopTable* TiptopBD::getTable(wxString name)
{
for(uint4 i=0; i<list->count(); 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;
}
//------------------------------------------------------------------------------