diff options
Diffstat (limited to 'Src/nde/win')
29 files changed, 7619 insertions, 0 deletions
diff --git a/Src/nde/win/Binary32Field.cpp b/Src/nde/win/Binary32Field.cpp new file mode 100644 index 00000000..507682b1 --- /dev/null +++ b/Src/nde/win/Binary32Field.cpp @@ -0,0 +1,77 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Binary32Field Class +Field data layout: +[4 bytes] length +[length bytes] binary data +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "Binary32Field.h" +#include "../ndestring.h" +//--------------------------------------------------------------------------- +Binary32Field::Binary32Field(const uint8_t *_Data, size_t len) : BinaryField(_Data, (int)len) +{ + InitField(); +} + +//--------------------------------------------------------------------------- +void Binary32Field::InitField(void) +{ + Type = FIELD_BINARY32; +} + +//--------------------------------------------------------------------------- +Binary32Field::Binary32Field() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +void Binary32Field::ReadTypedData(const uint8_t *data, size_t len) +{ + size_t pos = 0; + CHECK_INT(len); //len-=4; + uint32_t c = GET_INT(); pos += 4; + if (c && c<=len) + { + Size = c; + ndestring_release((wchar_t *)Data); + Data = (uint8_t *)ndestring_malloc(c); + GET_BINARY(Data, data, c, pos); + } +} + +//--------------------------------------------------------------------------- +void Binary32Field::WriteTypedData(uint8_t *data, size_t len) +{ + uint32_t c; + size_t pos = 0; + + CHECK_INT(len); //len-=4; + + if (Data && Size<=len) + { + c = (uint32_t)Size; + PUT_INT(c); pos += 4; + if (Data) + PUT_BINARY(data, (unsigned char*)Data, c, pos); + } + else + { + PUT_INT(0); + } +} + +//--------------------------------------------------------------------------- +size_t Binary32Field::GetDataSize(void) +{ + if (!Data) return 4; + return Size + 4; +}
\ No newline at end of file diff --git a/Src/nde/win/Binary32Field.h b/Src/nde/win/Binary32Field.h new file mode 100644 index 00000000..0684146b --- /dev/null +++ b/Src/nde/win/Binary32Field.h @@ -0,0 +1,32 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Binary32Field Class Prototypes + +--------------------------------------------------------------------------- */ + +#ifndef __NDE_BINARY32FIELD_H +#define __NDE_BINARY32FIELD_H + +#include "BinaryField.h" +#include <bfc/platform/types.h> + +class Binary32Field : public BinaryField +{ +protected: + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + void InitField(void); + +public: + Binary32Field(const uint8_t *Data, size_t len); + Binary32Field(); +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/BinaryField.cpp b/Src/nde/win/BinaryField.cpp new file mode 100644 index 00000000..0641fdaf --- /dev/null +++ b/Src/nde/win/BinaryField.cpp @@ -0,0 +1,172 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + BinaryField Class +Field data layout: +[2 bytes] length +[length bytes] binary data +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "BinaryField.h" +#include "../NDEString.h" +//--------------------------------------------------------------------------- +BinaryField::BinaryField(const uint8_t *_Data, int len) +{ + InitField(); + Type = FIELD_BINARY; + if (_Data && len > 0) + { + Data = (uint8_t *)ndestring_malloc(len); + memcpy(Data, _Data, len); + Size = len; + } +} + +//--------------------------------------------------------------------------- +void BinaryField::InitField(void) +{ + Type = FIELD_BINARY; + Data = NULL; + Size = 0; +} + +//--------------------------------------------------------------------------- +BinaryField::BinaryField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +BinaryField::~BinaryField() +{ + ndestring_release((wchar_t *)Data); +} + +//--------------------------------------------------------------------------- +void BinaryField::ReadTypedData(const uint8_t *data, size_t len) +{ + unsigned short c; + int pos = 0; + + CHECK_SHORT(len); + + c = GET_SHORT(); pos += 2; + if (c && c<=len) + { + Size = c; + ndestring_release((wchar_t *)Data); + Data = (uint8_t *)ndestring_malloc(c); + GET_BINARY(Data, data, c, pos); + } +} + +//--------------------------------------------------------------------------- +void BinaryField::WriteTypedData(uint8_t *data, size_t len) +{ + size_t pos = 0; + + CHECK_SHORT(len); + + if (Data && Size<=len) + { + unsigned short c = (unsigned short)Size; + PUT_SHORT(c); pos += 2; + if (Data) + PUT_BINARY(data, (unsigned char*)Data, c, pos); + } + else + { + PUT_SHORT(0); + } +} + +//--------------------------------------------------------------------------- +const uint8_t *BinaryField::GetData(size_t *len) +{ + if (len) + *len = Size; + return Data; +} + +//--------------------------------------------------------------------------- +void BinaryField::SetData(const uint8_t *_Data, size_t len) +{ + if (!_Data || !len) return; + ndestring_release((wchar_t *)Data); + Size = 0; + Data = (uint8_t *)ndestring_malloc(len); + memcpy(Data, _Data, len); + Size = len; +} + +//--------------------------------------------------------------------------- +size_t BinaryField::GetDataSize(void) +{ + if (!Data) return 2; + return Size + 2; +} + +//--------------------------------------------------------------------------- +int BinaryField::Compare(Field *Entry) +{ + if (!Entry) return -1; + size_t compare_length; + const uint8_t *compare_data = ((BinaryField*)Entry)->GetData(&compare_length); + return memcmp(Data, compare_data, min(compare_length, Size)); +} + +//--------------------------------------------------------------------------- +bool BinaryField::ApplyFilter(Field *FilterData, int op) +{ + size_t l, s; + const uint8_t *p = ((BinaryField *)FilterData)->GetData(&l); + const uint8_t *d = GetData(&s); + if (!p) + p = (const uint8_t *)""; + if (!d) + d = (const uint8_t *)""; + bool r; + switch (op) + { + case FILTER_EQUALS: + if (l != s) + r = false; + else + r = !memcmp(d, p, min(s, l)); + break; + case FILTER_CONTAINS: + if (l > s) + r = FALSE; + else + r = !!memmem(d, p, s, l); + break; + case FILTER_ABOVE: + r = (memcmp(d, p, min(s, l)) > 0); + break; + case FILTER_BELOW: + r = (memcmp(d, p, min(s, l)) < 0); + break; + case FILTER_BELOWOREQUAL: + r = (memcmp(d, p, min(s, l)) <= 0); + break; + case FILTER_ABOVEOREQUAL: + r = (memcmp(d, p, min(s, l)) >= 0); + break; + case FILTER_ISEMPTY: + r = (s == 0); + break; + case FILTER_ISNOTEMPTY: + r = (s != 0); + break; + default: + r = true; + break; + } + return r; +}
\ No newline at end of file diff --git a/Src/nde/win/BinaryField.h b/Src/nde/win/BinaryField.h new file mode 100644 index 00000000..d720ee3b --- /dev/null +++ b/Src/nde/win/BinaryField.h @@ -0,0 +1,38 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + BinaryField Class Prototypes + +--------------------------------------------------------------------------- */ + +#ifndef __BINARYFIELD_H +#define __BINARYFIELD_H + +#include "Field.h" +#include <bfc/platform/types.h> +class BinaryField : public Field +{ +protected: + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + virtual int Compare(Field *Entry); + virtual bool ApplyFilter(Field *Data, int flags); + void InitField(void); + uint8_t *Data; + size_t Size; + +public: + ~BinaryField(); + BinaryField(const uint8_t *Data, int len); + BinaryField(); + const uint8_t *GetData(size_t *len); + void SetData(const uint8_t *Data, size_t len); +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/ColumnField.cpp b/Src/nde/win/ColumnField.cpp new file mode 100644 index 00000000..321f5bdb --- /dev/null +++ b/Src/nde/win/ColumnField.cpp @@ -0,0 +1,184 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +ColumnField Class +Windows implementation + +Field data layout: +[1 byte] Field Type +[1 byte] Unused (maybe convert to 'searchable') +[1 byte] Name Length +[Name Length bytes] Name (UTF-8) +--------------------------------------------------------------------------- */ + +#include "../nde.h" + +//--------------------------------------------------------------------------- +ColumnField::ColumnField(unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, Table *parentTable) +{ + InitField(); + Type = FIELD_COLUMN; + MyType = FieldType; + Name = ndestring_wcsdup(FieldName); + ID = FieldID; +} + +//--------------------------------------------------------------------------- +void ColumnField::InitField(void) +{ + searchable = false; + MyType = FIELD_UNKNOWN; + Type = FIELD_COLUMN; + Name = NULL; + ID = 0; +} + +//--------------------------------------------------------------------------- +ColumnField::ColumnField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +ColumnField::~ColumnField() +{ + ndestring_release(Name); +} + +void ColumnField::SetSearchable(bool val) +{ + searchable=val; +} + +bool ColumnField::IsSearchableField() const +{ + return searchable; +} + +//--------------------------------------------------------------------------- +void ColumnField::ReadTypedData(const uint8_t *data, size_t len) +{ + unsigned char c; + int pos=0; + + // [1 byte] Field Type + CHECK_CHAR(len); + MyType = GET_CHAR(); + pos++; + + // [1 byte] unused + CHECK_CHAR(len); +//cut: indexUnique = (BOOL)GET_CHAR(); + pos++; + + // [1 byte] string length + CHECK_CHAR(len); + c = GET_CHAR(); + pos++; + + if (c) + { + CHECK_BIN(len, c); + + int string_length = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, 0, 0); + Name = ndestring_malloc((string_length+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, Name, string_length); + Name[string_length]=0; + } +} + +//--------------------------------------------------------------------------- +void ColumnField::WriteTypedData(uint8_t *data, size_t len) +{ + int pos = 0; + + // [1 byte] Field Type + CHECK_CHAR(len); + PUT_CHAR(MyType); + pos++; + + // [1 byte] unused + CHECK_CHAR(len); + PUT_CHAR(0/*(char)indexUnique*/); + pos++; + + CHECK_CHAR(len); + if (Name) + { + size_t string_length = WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0); + if (string_length) + { + PUT_CHAR((uint8_t)string_length-1); + pos++; + + CHECK_BIN(len, string_length-1); + WideCharToMultiByte(CP_UTF8, 0, Name, -1, (LPSTR)(data+pos), (int)string_length-1, 0, 0); + } + else + { + PUT_CHAR(0); + } + } + else + { + PUT_CHAR(0); + } +} + +//--------------------------------------------------------------------------- +unsigned char ColumnField::GetDataType(void) +{ + return MyType; +} + +//--------------------------------------------------------------------------- +void ColumnField::SetDataType(unsigned char type) +{ + if ((MyType == FIELD_INTEGER || MyType == FIELD_BOOLEAN || MyType == FIELD_DATETIME || MyType == FIELD_LENGTH || MyType == FIELD_INT64) && + (type == FIELD_INTEGER || type == FIELD_BOOLEAN || type == FIELD_DATETIME || type == FIELD_LENGTH || type == FIELD_INT64)) { + MyType = type; + } + // going from string to filename or filename to string is OK + if ((MyType == FIELD_FILENAME && type == FIELD_STRING) + || (MyType == FIELD_STRING && type == FIELD_FILENAME)) + { + MyType = type; + } +} + +//--------------------------------------------------------------------------- +wchar_t *ColumnField::GetFieldName(void) +{ + return Name; +} + +//--------------------------------------------------------------------------- +void ColumnField::SetFieldName(wchar_t *name) +{ + if (Name) ndestring_release(Name); + Name = ndestring_wcsdup(name); +} + +//--------------------------------------------------------------------------- +size_t ColumnField::GetDataSize(void) +{ + size_t s=3; + if (Name) + { + int string_length=WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0); + if (string_length) + s+=string_length-1; + } + return s; +} + +//--------------------------------------------------------------------------- +int ColumnField::Compare(Field * /*Entry*/) +{ + return 0; +}
\ No newline at end of file diff --git a/Src/nde/win/ColumnField.h b/Src/nde/win/ColumnField.h new file mode 100644 index 00000000..380b417c --- /dev/null +++ b/Src/nde/win/ColumnField.h @@ -0,0 +1,49 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +ColumnField Class Prototypes + +--------------------------------------------------------------------------- */ + +#ifndef __COLUMNFIELD_H +#define __COLUMNFIELD_H + +#include "Field.h" +#include "LinkedList.h" +#include "Table.h" + +#include "Scanner.h" + +class ColumnField : public Field +{ +public: + ColumnField(unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, Table *parentTable); + ColumnField(); + ~ColumnField(); + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + virtual int Compare(Field *Entry); + void InitField(void); + + bool IsSearchableField() const; + void SetSearchable(bool val); + + unsigned char GetDataType(void); + void SetDataType(unsigned char type); + + wchar_t *GetFieldName(void); // not const because it's an NDE string + void SetFieldName(wchar_t *name); + +protected: + bool searchable; + wchar_t *Name; + unsigned char MyType; +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/FilenameField.cpp b/Src/nde/win/FilenameField.cpp new file mode 100644 index 00000000..c658c33f --- /dev/null +++ b/Src/nde/win/FilenameField.cpp @@ -0,0 +1,128 @@ +#include "FilenameField.h" +#include "../nde.h" + + +//--------------------------------------------------------------------------- +FilenameField::FilenameField(const wchar_t *Str, int strkind) : StringField(Str, strkind) +{ + Type = FIELD_FILENAME; +} + +//--------------------------------------------------------------------------- +FilenameField::FilenameField() +{ + Type = FIELD_FILENAME; +} + +//--------------------------------------------------------------------------- +int FilenameField::Compare(Field *Entry) +{ + if (!Entry) return -1; + if (!CompatibleFields(Entry->GetType(), GetType())) + return 0; + return mywcsicmp_fn(GetStringW(), ((FilenameField*)Entry)->GetStringW()); +} + +//--------------------------------------------------------------------------- +int FilenameField::Starts(Field *Entry) +{ + if (!Entry) return -1; + if (!CompatibleFields(Entry->GetType(), GetType())) + return 0; + const wchar_t *p = ((StringField*)Entry)->GetStringW(); + const wchar_t *d = GetStringW(); + if (!d || !p) return 0; + return nde_fnbegins(d, p); +} + +//--------------------------------------------------------------------------- +int FilenameField::Contains(Field *Entry) +{ + if (!Entry) return -1; + if (!CompatibleFields(Entry->GetType(), GetType())) + return 0; + const wchar_t *p = ((StringField*)Entry)->GetStringW(); + const wchar_t *d = GetStringW(); + if (!d || !p) return 0; + return nde_fncontains(GetStringW(), ((StringField*)Entry)->GetStringW()); +} + +Field *FilenameField::Clone(Table *pTable) +{ + FilenameField *clone = new FilenameField(StringW, STRING_IS_NDESTRING); + clone->Pos = FIELD_CLONE; + clone->ID = ID; + clone->MaxSizeOnDisk = (uint32_t)GetDataSize(); + return clone; +} + +// TODO: make file system string comparison functions +//--------------------------------------------------------------------------- +bool FilenameField::ApplyFilter(Field *Data, int op) +{ + // TODO: maybe do this? + if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY) + { + bool r = (op == FILTER_ISEMPTY); + if (!StringW) + return r; + + if (StringW && StringW[0] == 0) + return r; + + return !r; + } + // + bool r; + wchar_t *p=0; + if (Data->GetType() == FIELD_STRING) + p = ((StringField *)Data)->GetStringW(); + else + p = ((FilenameField *)Data)->GetStringW(); + wchar_t *d = GetStringW(); + if (!p) + p = L""; + if (!d) + d = L""; + + switch (op) + { + case FILTER_EQUALS: + r = !nde_wcsicmp_fn(d, p); + break; + case FILTER_NOTEQUALS: + r = !!nde_wcsicmp_fn(d, p); + break; + case FILTER_CONTAINS: + r = nde_fncontains(d, p); + break; + case FILTER_NOTCONTAINS: + r = !nde_fncontains(d, p); + break; + case FILTER_ABOVE: + r = (bool)(nde_wcsicmp_fn(d, p) > 0); + break; + case FILTER_ABOVEOREQUAL: + r = (bool)(nde_wcsicmp_fn(d, p) >= 0); + break; + case FILTER_BELOW: + r = (bool)(nde_wcsicmp_fn(d, p) < 0); + break; + case FILTER_BELOWOREQUAL: + r = (bool)(nde_wcsicmp_fn(d, p) <= 0); + break; + case FILTER_BEGINS: + r = nde_fnbegins(d, p); + break; + case FILTER_ENDS: + r = nde_fnends(d, p); + break; + case FILTER_LIKE: + r = (bool)(nde_wcsicmp_fn(d, p) == 0); + break; + default: + r = true; + break; + } + return r; +}
\ No newline at end of file diff --git a/Src/nde/win/FilenameField.h b/Src/nde/win/FilenameField.h new file mode 100644 index 00000000..017e8bc4 --- /dev/null +++ b/Src/nde/win/FilenameField.h @@ -0,0 +1,26 @@ +#ifndef NDE_FILENAMEFIELD_H +#define NDE_FILENAMEFIELD_H + +/* + Mostly the same as StringField + but this implements OS-dependent string comparisons that make sense for the file system +*/ + +#include "../nde.h" +#include "../NDEString.h" + +class FilenameField : public StringField +{ +protected: + virtual int Compare(Field *Entry); + virtual int Starts(Field *Entry); + virtual int Contains(Field *Entry); + virtual bool ApplyFilter(Field *Data, int op); + virtual Field *Clone(Table *pTable); + +public: + FilenameField(const wchar_t *Str, int strkind=STRING_IS_WCHAR); + FilenameField(); +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/IndexField.cpp b/Src/nde/win/IndexField.cpp new file mode 100644 index 00000000..8b619b41 --- /dev/null +++ b/Src/nde/win/IndexField.cpp @@ -0,0 +1,145 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +IndexField Class +Windows implementation + +Field data layout +[4 bytes] Position +[4 bytes] Data Type +[1 byte] Name Length +[Name Length bytes] Name (UTF-8) +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "../ndestring.h" +//--------------------------------------------------------------------------- +IndexField::IndexField(unsigned char id, int Pos, int type, const wchar_t *FieldName) +{ + InitField(); + Type = FIELD_INDEX; + Name = ndestring_wcsdup(FieldName); + ID = id; + Position = Pos; + DataType = type; +} + +//--------------------------------------------------------------------------- +void IndexField::InitField(void) +{ + index = 0; + Type = FIELD_INDEX; + Name = NULL; + ID = 0; + Position = -1; + DataType = FIELD_UNKNOWN; +} + +//--------------------------------------------------------------------------- +IndexField::IndexField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +IndexField::~IndexField() +{ + ndestring_release(Name); + delete index; +} + +//--------------------------------------------------------------------------- +void IndexField::ReadTypedData(const uint8_t *data, size_t len) +{ + unsigned char c; + int pos=0; + CHECK_INT(len); + Position = GET_INT(); pos += 4; + CHECK_INT(len); + DataType = GET_INT(); pos += 4; + CHECK_CHAR(len); + c = GET_CHAR(); pos++; + if (c) + { + CHECK_BIN(len, c); + + int string_length = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, 0, 0); + Name = ndestring_malloc((string_length+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, Name, string_length); + Name[string_length]=0; + } +} + +//--------------------------------------------------------------------------- +void IndexField::WriteTypedData(uint8_t *data, size_t len) +{ + int pos=0; + CHECK_INT(len); + PUT_INT(Position); pos += 4; + + CHECK_INT(len); + PUT_INT(DataType); pos += 4; + CHECK_CHAR(len); + + CHECK_CHAR(len); + + if (Name) + { + size_t string_length = WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0); + if (string_length) + { + PUT_CHAR((uint8_t)string_length-1); + pos++; + + CHECK_BIN(len, string_length-1); + WideCharToMultiByte(CP_UTF8, 0, Name, -1, (LPSTR)(data+pos), (int)string_length-1, 0, 0); + } + else + { + PUT_CHAR(0); + } + } + else + { + PUT_CHAR(0); + } +} + +//--------------------------------------------------------------------------- +wchar_t *IndexField::GetIndexName(void) +{ + return Name; +} + +//--------------------------------------------------------------------------- +size_t IndexField::GetDataSize(void) +{ + size_t s=9; + if (Name) + { + int string_length=WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0); + if (string_length) + s+=string_length-1; + } + s++; + return s; +} + +//--------------------------------------------------------------------------- +int IndexField::Compare(Field * /*Entry*/) +{ + return 0; +} + +//--------------------------------------------------------------------------- +int IndexField::TranslateToIndex(int Id, IndexField *toindex) +{ + if (index && toindex && toindex->index) + return index->TranslateIndex(Id, toindex->index); + return -1; +}
\ No newline at end of file diff --git a/Src/nde/win/IndexField.h b/Src/nde/win/IndexField.h new file mode 100644 index 00000000..88a46aae --- /dev/null +++ b/Src/nde/win/IndexField.h @@ -0,0 +1,37 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + IndexField Class Prototypes + +--------------------------------------------------------------------------- */ + +#ifndef __INDEXFIELD_H +#define __INDEXFIELD_H + +class IndexField : public Field +{ +public: + IndexField(unsigned char id, int Pos, int type, const wchar_t *FieldName); + IndexField(); + ~IndexField(); + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + virtual int Compare(Field *Entry); + void InitField(void); + + wchar_t *GetIndexName(void); + int TranslateToIndex(int Id, IndexField *index); + Index *index; // TODO: make protected +protected: + int Position; + int DataType; + wchar_t *Name; +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/IndexRecord.cpp b/Src/nde/win/IndexRecord.cpp new file mode 100644 index 00000000..06405ba1 --- /dev/null +++ b/Src/nde/win/IndexRecord.cpp @@ -0,0 +1,169 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +IndexRecord Class + +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include <stdio.h> + +IndexRecord::IndexRecord(int RecordPos, int insertionPoint, VFILE *TableHandle, Table *ParentTable) +{ + InsertionPoint = insertionPoint; + if (RecordPos != 0) + { + int n=0; + uint32_t ThisPos = RecordPos; + while (ThisPos) + { + if (n >= 128) + break; + Vfseek(TableHandle, ThisPos, SEEK_SET); + Field Entry (ThisPos); + Field *TypedEntry = Entry.ReadField(ParentTable, ThisPos, &ThisPos); + + if (!TypedEntry) break; // some db error? + + AddField(TypedEntry); + // ThisPos = TypedEntry->GetNextFieldPos(); + n++; + } + } +} + +void IndexRecord::BuildCollaboration() +{ + for (FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++) + { + IndexField *p = (IndexField *)(* itr); + if ((itr + 1) != Fields.end()) + p->index->Colaborate((IndexField *)(*(itr+1))); + else + p->index->Colaborate((IndexField *)*(Fields.begin())); + } +} + +bool IndexRecord::NeedFix() +{ + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + IndexField *p = (IndexField *)*itr; + if (p->index->NeedFix()) + return true; + } + return false; +} + +IndexField *IndexRecord::GetIndexByName(const wchar_t *name) +{ + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + IndexField *p = (IndexField *)*itr; + if (!_wcsicmp(p->GetIndexName(), name)) + return p; + } + return NULL; +} + +bool IndexRecord::CheckIndexing(int v) +{ + int i = v; + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + IndexField *p = (IndexField *)*itr; + v = p->index->GetCooperative(v); + } + return v == i; +} + +Field *RecordBase::GetField( unsigned char ID ) +{ + for ( FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++ ) + { + IndexField *p = (IndexField *)*itr; + if ( p->GetFieldId() == ID ) + return p; + } + + return NULL; +} + +void RecordBase::AddField(Field *field) +{ + if (!field) + return; + + if (GetField(field->ID)) + return; + + Fields.push_back(field); +} + +int IndexRecord::WriteFields( Table *ParentTable ) +{ + Field *l_previous_field = NULL; + IndexField *l_index_field = NULL; + + for ( FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++ ) + { + l_index_field = (IndexField *)(* itr); + + Field* nextField = (itr + 1) != Fields.end() ? *(itr + 1) : nullptr; + + //l_index_field->WriteField(ParentTable, l_previous_field, (Field*)l_index_field->next); + l_index_field->WriteField(ParentTable, l_previous_field, nextField); + + l_previous_field = l_index_field; + } + + return WriteIndex( ParentTable ); +} + + +int IndexRecord::WriteIndex( Table *ParentTable ) +{ + int P = 0; + + if ( !Fields.empty() ) + P = ( *Fields.begin() )->GetFieldPos(); + + return ParentTable->index->Update( INDEX_RECORD_NUM, P, this, FALSE ); +} + +void RecordBase::RemoveField(Field *field) +{ + if (!field) + return; + + //Fields.erase(field); + //delete field; + + for (auto it = Fields.begin(); it != Fields.end(); it++) + { + Field* f = *it; + if (f == field) + { + Fields.erase(it); + delete f; + break; + } + } +} + +void IndexRecord::WalkFields(FieldsWalker callback, void *context) +{ + if (callback) + { + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + if (!callback(this, *itr, context)) + break; + } + } +}
\ No newline at end of file diff --git a/Src/nde/win/IndexRecord.h b/Src/nde/win/IndexRecord.h new file mode 100644 index 00000000..97a73bd5 --- /dev/null +++ b/Src/nde/win/IndexRecord.h @@ -0,0 +1,18 @@ +#pragma once +#include <stdio.h> +#include "Record.h" + +class IndexRecord : public RecordBase +{ +public: + IndexRecord(int RecordPos, int insertionPoint, VFILE *FileHandle,Table *p); + void BuildCollaboration(); + bool NeedFix(); + IndexField *GetIndexByName(const wchar_t *name); + int GetColumnCount() { return (int)(Fields.size() - 1); } + bool CheckIndexing(int v); + int WriteFields(Table *ParentTable); + int WriteIndex(Table *ParentTable); + typedef bool (*FieldsWalker)(IndexRecord *record, Field *entry, void *context); + void WalkFields(FieldsWalker callback, void *context); +};
\ No newline at end of file diff --git a/Src/nde/win/IntegerField.cpp b/Src/nde/win/IntegerField.cpp new file mode 100644 index 00000000..9a9830d3 --- /dev/null +++ b/Src/nde/win/IntegerField.cpp @@ -0,0 +1,1011 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience + --------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + IntegerField Class + Windows implementation + +Field data layout: +[4 bytes] value + --------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "Query.h" +#include <time.h> +#include <malloc.h> + +//--------------------------------------------------------------------------- +IntegerField::IntegerField(int Val) +{ + InitField(); + Type = FIELD_INTEGER; + Value = Val; +} + +//--------------------------------------------------------------------------- +void IntegerField::InitField(void) +{ + Type = FIELD_INTEGER; + Value=0; +} + +//--------------------------------------------------------------------------- +IntegerField::IntegerField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +IntegerField::~IntegerField() +{ +} + +//--------------------------------------------------------------------------- +void IntegerField::ReadTypedData(const uint8_t *data, size_t len) +{ + CHECK_INT(len); + Value = *((int *)data); +} + +//--------------------------------------------------------------------------- +void IntegerField::WriteTypedData(uint8_t *data, size_t len) +{ + CHECK_INT(len); + *((int *)data) = Value; +} + +//--------------------------------------------------------------------------- +int IntegerField::GetValue(void) +{ + return Value; +} + +//--------------------------------------------------------------------------- +void IntegerField::SetValue(int Val) +{ + Value = Val; +} + +#include <limits.h> +//--------------------------------------------------------------------------- +size_t IntegerField::GetDataSize(void) +{ + return 4; +} + +//--------------------------------------------------------------------------- +int IntegerField::Compare(Field *Entry) +{ + if (!Entry) return -1; + return GetValue() < ((IntegerField*)Entry)->GetValue() ? -1 : (GetValue() > ((IntegerField*)Entry)->GetValue() ? 1 : 0); +} + +//--------------------------------------------------------------------------- +bool IntegerField::ApplyFilter(Field *Data, int op) +{ + bool r; + switch (op) + { + case FILTER_EQUALS: + r = Value == ((IntegerField *)Data)->GetValue(); + break; + case FILTER_NOTEQUALS: + r = Value != ((IntegerField *)Data)->GetValue(); + break; + case FILTER_NOTCONTAINS: + r = (bool)!(Value & ((IntegerField *)Data)->GetValue()); + break; + case FILTER_CONTAINS: + r = !!(Value & ((IntegerField *)Data)->GetValue()); + break; + case FILTER_ABOVE: + r = (bool)(Value > ((IntegerField *)Data)->GetValue()); + break; + case FILTER_BELOW: + r = (bool)(Value < ((IntegerField *)Data)->GetValue()); + break; + case FILTER_BELOWOREQUAL: + r = (bool)(Value <= ((IntegerField *)Data)->GetValue()); + break; + case FILTER_ABOVEOREQUAL: + r = (bool)(Value >= ((IntegerField *)Data)->GetValue()); + break; + case FILTER_ISEMPTY: + r = (Value == 0 || Value == -1); + break; + case FILTER_ISNOTEMPTY: + r = !(Value == 0 || Value == -1); + break; + default: + r = true; + break; + } + return r; +} + +//--------------------------------------------------------------------------- +typedef struct { + const wchar_t *token; + int tid; +} tokenstruct; + +enum { + TOKEN_AGO = 128, + TOKEN_NOW, + TOKEN_YESTERDAY, + TOKEN_TOMORROW, + TOKEN_TODAY, + TOKEN_OF, + TOKEN_THE, + TOKEN_DATE, + TOKEN_FROM, + TOKEN_BEFORE, + TOKEN_AFTER, + TOKEN_THIS, + TOKEN_SUNDAY, + TOKEN_MONDAY, + TOKEN_TUESDAY, + TOKEN_WEDNESDAY, + TOKEN_THURSDAY, + TOKEN_FRIDAY, + TOKEN_SATURDAY, + + TOKEN_MIDNIGHT, + TOKEN_NOON, + + TOKEN_AM, + TOKEN_PM, + + TOKEN_JANUARY, + TOKEN_FEBRUARY, + TOKEN_MARCH, + TOKEN_APRIL, + TOKEN_MAY, + TOKEN_JUNE, + TOKEN_JULY, + TOKEN_AUGUST, + TOKEN_SEPTEMBER, + TOKEN_OCTOBER, + TOKEN_NOVEMBER, + TOKEN_DECEMBER, + + TOKEN_TIME, + TOKEN_SECOND, + TOKEN_MINUTE, + TOKEN_HOUR, + TOKEN_DAY, + TOKEN_WEEK, + TOKEN_MONTH, + TOKEN_YEAR, + TOKEN_AT, +}; + +tokenstruct Int_Tokens[] = { // Feel free to add more... + {L"ago", TOKEN_AGO}, + {L"now", TOKEN_NOW}, + {L"am", TOKEN_AM}, + {L"pm", TOKEN_PM}, + {L"this", TOKEN_THIS}, + {L"date", TOKEN_DATE}, + {L"time", TOKEN_TIME}, + {L"of", TOKEN_OF}, + {L"at", TOKEN_AT}, + {L"the", TOKEN_THE}, + {L"yesterday", TOKEN_YESTERDAY}, + {L"tomorrow", TOKEN_TOMORROW}, + {L"today", TOKEN_TODAY}, + {L"from", TOKEN_FROM}, + {L"before", TOKEN_BEFORE}, + {L"after", TOKEN_AFTER}, + {L"past", TOKEN_AFTER}, + {L"monday", TOKEN_MONDAY}, + {L"mon", TOKEN_MONDAY}, + {L"tuesday", TOKEN_TUESDAY}, + {L"tue", TOKEN_TUESDAY}, + {L"wednesday", TOKEN_WEDNESDAY}, + {L"wed", TOKEN_WEDNESDAY}, + {L"thursday", TOKEN_THURSDAY}, + {L"thu", TOKEN_THURSDAY}, + {L"friday", TOKEN_FRIDAY}, + {L"fri", TOKEN_FRIDAY}, + {L"saturday", TOKEN_SATURDAY}, + {L"sat", TOKEN_SATURDAY}, + {L"sunday", TOKEN_SUNDAY}, + {L"sun", TOKEN_SUNDAY}, + {L"midnight", TOKEN_MIDNIGHT}, + {L"noon", TOKEN_NOON}, + {L"second", TOKEN_SECOND}, + {L"seconds", TOKEN_SECOND}, + {L"sec", TOKEN_SECOND}, + {L"s", TOKEN_SECOND}, + {L"minute", TOKEN_MINUTE}, + {L"minutes", TOKEN_MINUTE}, + {L"min", TOKEN_MINUTE}, + {L"mn", TOKEN_MINUTE}, + {L"m", TOKEN_MINUTE}, + {L"hour", TOKEN_HOUR}, + {L"hours", TOKEN_HOUR}, + {L"h", TOKEN_HOUR}, + {L"day", TOKEN_DAY}, + {L"days", TOKEN_DAY}, + {L"d", TOKEN_DAY}, + {L"week", TOKEN_WEEK}, + {L"weeks", TOKEN_WEEK}, + {L"w", TOKEN_WEEK}, + {L"month", TOKEN_MONTH}, + {L"months", TOKEN_MONTH}, + {L"year", TOKEN_YEAR}, + {L"years", TOKEN_YEAR}, + {L"y", TOKEN_YEAR}, + {L"january", TOKEN_JANUARY}, + {L"jan", TOKEN_JANUARY}, + {L"february", TOKEN_FEBRUARY}, + {L"feb", TOKEN_FEBRUARY}, + {L"march", TOKEN_MARCH}, + {L"mar", TOKEN_MARCH}, + {L"april", TOKEN_APRIL}, + {L"apr", TOKEN_APRIL}, + {L"may", TOKEN_MAY}, + {L"june", TOKEN_JUNE}, + {L"jun", TOKEN_JUNE}, + {L"july", TOKEN_JULY}, + {L"jul", TOKEN_JULY}, + {L"august", TOKEN_AUGUST}, + {L"aug", TOKEN_AUGUST}, + {L"september", TOKEN_SEPTEMBER}, + {L"sep", TOKEN_SEPTEMBER}, + {L"october", TOKEN_OCTOBER}, + {L"oct", TOKEN_OCTOBER}, + {L"november", TOKEN_NOVEMBER}, + {L"nov", TOKEN_NOVEMBER}, + {L"december", TOKEN_DECEMBER}, + {L"dec", TOKEN_DECEMBER}, +}; + +//--------------------------------------------------------------------------- +int IntegerField::LookupToken(const wchar_t *t) { + for (int i=0;i<sizeof(Int_Tokens)/sizeof(tokenstruct);i++) { + if (!_wcsicmp(Int_Tokens[i].token, t)) + return Int_Tokens[i].tid; + } + return TOKEN_IDENTIFIER; +} + +static int myatoi(const wchar_t *p, int len) { + wchar_t *w = (wchar_t *)_malloca((len+1)*sizeof(wchar_t)); + wcsncpy(w, p, len); + w[len] = 0; + int a = (w ? wcstol(w, 0, 10) : 0); + _freea(w); + return a; +} + +static int isallnum(const wchar_t *p) +{ + while (p && *p) { + if (*p < L'0' || *p > L'9') return 0; + p++; + } + return 1; +} + +//--------------------------------------------------------------------------- +int IntegerField::ApplyConversion(const wchar_t *format, TimeParse *tp) { + int size; + + int value = GetValue(); + wchar_t *token = 0; + bool ago = false; + bool from = false; + bool kthis = false; + int what = TOKEN_MINUTE; + + int lastnumber = value; + + if (tp) { + tp->is_relative = 0; + tp->offset_value = 0; + tp->offset_whence = -1; + tp->offset_what = -1; + tp->offset_used = 0; + tp->relative_year = -1; + tp->relative_month = -1; + tp->relative_day = -1; + tp->relative_hour = -1; + tp->relative_min = -1; + tp->relative_sec = -1; + tp->relative_kwday = -1; + tp->absolute_hastime = 0; + tp->absolute_hasdate = 0; + } + + time_t now; + time(&now); + + struct tm *o = localtime(&now); + struct tm origin = *o; + struct tm origin_flags = {0,0,0,0,0,0,0,0,0}; + + struct tm onow = *o; + + const wchar_t *p = format; + int t = -1; + int lastt = -1; + + origin.tm_isdst = -1; + + while (1) { + int save_lastt = lastt; + lastt = t; + t = Scanner::Query_GetNextToken(p, &size, &token, 1); + if (t == TOKEN_EOQ) break; + + switch (t) { + case TOKEN_THIS: + kthis = true; + break; + case TOKEN_AGO: + case TOKEN_BEFORE: // before defaults to before now (= ago) + ago = true; + if (tp) { + tp->is_relative = 1; + tp->offset_whence = 1; + tp->offset_used = 1; + } + break; + case TOKEN_AFTER: // if after, ago is discarded, coz 5 mn ago after x has no meaning, so we get it as 5 mn after x + ago = false; + // no break + case TOKEN_FROM: + from = true; + if (tp) { + tp->is_relative = 1; + tp->offset_whence = 0; + tp->offset_used = 1; + } + break; + case TOKEN_DATE: { + if (!kthis) break; + kthis = false; + origin.tm_year = onow.tm_year; + origin_flags.tm_year = 1; + origin.tm_mon = onow.tm_mon; + origin_flags.tm_mon = 1; + origin.tm_mday = onow.tm_mday - onow.tm_wday; + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) { + tp->relative_year = -1; + tp->relative_month = -1; + tp->relative_day = -1; + } + break; + } + case TOKEN_TIME: { + if (!kthis) break; + kthis = false; + origin.tm_hour = onow.tm_hour; + origin_flags.tm_hour = 1; + origin.tm_min = onow.tm_min; + origin_flags.tm_min = 1; + origin.tm_sec = onow.tm_sec; + origin_flags.tm_sec = 1; + if (tp) { + tp->relative_sec = -1; + tp->relative_min = -1; + tp->relative_hour = -1; + } + break; + } + case TOKEN_SECOND: + case TOKEN_MINUTE: + case TOKEN_HOUR: + case TOKEN_DAY: + case TOKEN_WEEK: + case TOKEN_MONTH: + case TOKEN_YEAR: + if (kthis) { + kthis = false; + switch (t) { + case TOKEN_SECOND: + origin.tm_sec = onow.tm_sec; + origin_flags.tm_sec = 1; + if (tp) tp->relative_sec = -1; + break; + case TOKEN_MINUTE: + origin.tm_min = onow.tm_min; + origin_flags.tm_min = 1; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_min = -1; + break; + case TOKEN_HOUR: + origin.tm_hour = onow.tm_hour; + origin_flags.tm_hour = 1; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_hour = -1; + break; + case TOKEN_DAY: + origin.tm_mday = onow.tm_mday; + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_day = -1; + break; + case TOKEN_WEEK: + origin.tm_mday = onow.tm_mday - onow.tm_wday; + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_day = -2; + break; + case TOKEN_MONTH: + origin.tm_mon = onow.tm_mon; + origin_flags.tm_mon = 1; + if (!origin_flags.tm_mday) + origin.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_month = -1; + break; + case TOKEN_YEAR: + origin.tm_year = onow.tm_year; + origin_flags.tm_year = 1; + if (!origin_flags.tm_mon) + origin.tm_mon = 0; + if (!origin_flags.tm_mday) + origin.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_year = -1; + break; + } + break; + } + if (lastnumber > 0) { + value = lastnumber; + lastnumber = 0; + if (tp) tp->offset_value = value; + } + what = t; + if (tp) { + switch (what) { + case TOKEN_SECOND: + tp->offset_what = 6; break; + case TOKEN_MINUTE: + tp->offset_what = 5; break; + case TOKEN_HOUR: + tp->offset_what = 4; break; + case TOKEN_DAY: + tp->offset_what = 3; break; + case TOKEN_WEEK: + tp->offset_what = 2; break; + case TOKEN_MONTH: + tp->offset_what = 1; break; + case TOKEN_YEAR: + tp->offset_what = 0; break; + } + } + break; + case TOKEN_SUNDAY: + case TOKEN_MONDAY: + case TOKEN_TUESDAY: + case TOKEN_WEDNESDAY: + case TOKEN_THURSDAY: + case TOKEN_FRIDAY: + case TOKEN_SATURDAY: { + kthis = false; + int dow = t-TOKEN_MONDAY; + if (dow > onow.tm_mday) + origin.tm_mday = 7 - (dow - onow.tm_mday); + else + origin.tm_mday = dow; + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + } + if (tp) tp->relative_kwday = t-TOKEN_SUNDAY; + break; + case TOKEN_MIDNIGHT: + kthis = false; + origin.tm_hour = 0; + origin_flags.tm_hour = 1; + if (!origin_flags.tm_min) { + if (tp) tp->relative_min = 0; + origin.tm_min = 0; + origin_flags.tm_min = 1; + } + if (!origin_flags.tm_sec) { + if (tp) tp->relative_sec = 0; + origin.tm_sec = 0; + origin_flags.tm_sec = 1; + } + if (tp) tp->relative_hour = 0; + break; + case TOKEN_NOON: + kthis = false; + origin.tm_hour = 12; + origin_flags.tm_hour = 1; + if (!origin_flags.tm_min) { + if (tp) tp->relative_min = 0; + origin.tm_min = 0; + origin_flags.tm_min = 1; + } + if (!origin_flags.tm_sec) { + if (tp) tp->relative_sec = 0; + origin.tm_sec = 0; + origin_flags.tm_sec = 1; + } + if (tp) tp->relative_hour = 12; + break; + case TOKEN_AM: + kthis = false; + if (lastnumber > 0) { + origin.tm_hour = lastnumber; + if (!origin_flags.tm_min) { + if (tp) tp->relative_min = 0; + origin.tm_min = 0; + origin_flags.tm_min = 1; + } + if (!origin_flags.tm_sec) { + if (tp) tp->relative_sec = 0; + origin.tm_sec = 0; + origin_flags.tm_sec = 1; + } + if (tp) tp->relative_hour = lastnumber; + lastnumber = 0; + } else { + if (origin.tm_hour > 12) origin.tm_hour -= 12; + if (tp) tp->relative_hour = origin.tm_hour; + } + origin_flags.tm_hour = 1; + break; + case TOKEN_PM: + kthis = false; + if (lastnumber > 0) { + origin.tm_hour = lastnumber > 12 ? lastnumber : lastnumber + 12; + if (!origin_flags.tm_min) { + if (tp) tp->relative_min = 0; + origin.tm_min = 0; + origin_flags.tm_min = 1; + } + if (!origin_flags.tm_sec) { + if (tp) tp->relative_sec = 0; + origin.tm_sec = 0; + origin_flags.tm_sec = 1; + } + if (tp) tp->relative_hour = lastnumber; + lastnumber = 0; + } else { + if (origin.tm_hour <= 12) origin.tm_hour += 12; + if (tp) tp->relative_hour = origin.tm_hour; + } + origin_flags.tm_hour = 1; + break; + case TOKEN_NOW: + kthis = false; + if (!origin_flags.tm_year) { + if (tp) tp->relative_year = -1; + origin.tm_year = onow.tm_year; + } + origin_flags.tm_year = 1; + if (!origin_flags.tm_mon) { + if (tp) tp->relative_month = -1; + origin.tm_mon = onow.tm_mon; + } + origin_flags.tm_mon = 1; + if (!origin_flags.tm_mday) { + if (tp) tp->relative_day = -1; + origin.tm_mday = onow.tm_mday; + } + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) { + if (tp) tp->relative_hour = -1; + origin.tm_hour = onow.tm_hour; + } + origin_flags.tm_hour = 1; + if (!origin_flags.tm_min) { + if (tp) tp->relative_min = -1; + origin.tm_min = onow.tm_min; + } + origin_flags.tm_min = 1; + if (!origin_flags.tm_sec) { + if (tp) tp->relative_sec = -1; + origin.tm_sec = onow.tm_sec; + } + break; + case TOKEN_YESTERDAY: + kthis = false; + origin.tm_mday = onow.tm_mday - 1; + origin_flags.tm_mday = 1; + if (tp) tp->relative_kwday = 7; + break; + case TOKEN_TODAY: + origin.tm_mday = onow.tm_mday; + origin_flags.tm_mday = 1; + if (tp) tp->relative_kwday = 8; + break; + case TOKEN_TOMORROW: + kthis = false; + origin.tm_mday = onow.tm_mday + 1; + origin_flags.tm_mday = 1; + if (tp) tp->relative_kwday = 9; + break; + case TOKEN_JANUARY: + case TOKEN_FEBRUARY: + case TOKEN_MARCH: + case TOKEN_APRIL: + case TOKEN_MAY: + case TOKEN_JUNE: + case TOKEN_JULY: + case TOKEN_AUGUST: + case TOKEN_SEPTEMBER: + case TOKEN_OCTOBER: + case TOKEN_NOVEMBER: + case TOKEN_DECEMBER: + kthis = false; + if (lastnumber > 0) { + origin.tm_mday = lastnumber; + origin_flags.tm_mday = 1; + lastnumber = 0; + } + origin.tm_mon = t-TOKEN_JANUARY; + if (!origin_flags.tm_mday) + origin.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + origin_flags.tm_mon = 1; + if (tp) tp->relative_month = t-TOKEN_JANUARY; + break; + case TOKEN_IDENTIFIER: + { + kthis = false; + + // check for a year value + int i = wcstol(token,0,10); + if (i > 1970 && i < 2038 && isallnum(token)) { // max time_t range + origin.tm_year = i-1900; + if (!origin_flags.tm_mday) + origin.tm_mday = 1; + if (!origin_flags.tm_mon) + origin.tm_mon = 0; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_year = i; + break; + } + + // check for 1st, 2nd, 3rd, 4th, etc. + wchar_t *z; + int tokenLen=(int)wcslen(token); + if (tokenLen>=2) + { + z = token+tokenLen-2; + if (!_wcsicmp(z, L"st") || !_wcsicmp(z, L"nd") || !_wcsicmp(z, L"rd") || !_wcsicmp(z, L"th")) { + int j = myatoi(token, (int)(z-token)); + if (j >= 1 && j <= 31) { + origin.tm_mday = j; + origin_flags.tm_mday = 1; + if (tp) tp->relative_day = j; + break; + } + } + } + + // check for a time string (##:##:##) + z = wcschr(token, L':'); + if (z) + { + if (tp) tp->absolute_hastime = 1; + wchar_t *zz = wcschr(z+1, L':'); + int a, b = 0, c = 0; + a = myatoi(token, (int)(z-token)); + if (zz && *(zz+1) == 0) zz = NULL; + if (zz && !isallnum(zz+1)) zz = NULL; + if (zz) { b = myatoi(z+1, (int)(zz-(z+1))); c = wcstol(zz+1,0,10); } + else b = wcstol(z+1,0,10); + origin.tm_hour = a; + origin.tm_min = b; + if (tp) { + tp->relative_hour = a; + tp->relative_min = b; + } + if (zz && !origin_flags.tm_sec) { + origin.tm_sec = c; + if (tp) tp->relative_sec = c; + } else if (!origin_flags.tm_sec) { + origin.tm_sec = 0; + } + origin_flags.tm_sec = 1; + origin_flags.tm_hour = 1; + origin_flags.tm_min = 1; + break; + } + + // check for a date string in the format ##/##/## + z = wcschr(token, L'/'); + if (z) { + if (tp) tp->absolute_hasdate = 1; + wchar_t *zz = wcschr(z+1, L'/'); + int a, b = 0, c = onow.tm_year; + a = myatoi(token, (int)(z-token)); + if (zz && !isallnum(zz+1)) zz = NULL; + if (zz && *(zz+1) == 0) zz = NULL; + if (zz) { b = myatoi(z+1, (int)(zz-(z+1))); c = wcstol(zz+1,0,10); } + else b = _wtoi(z+1); + if (b > 1969 && b < 2038) { + // mm/yyyy + origin.tm_year = b-1900; + origin_flags.tm_year = 1; + origin.tm_mon = a-1; + origin_flags.tm_mon = 1; + if (!origin_flags.tm_mday) + origin.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) { + tp->relative_year = b; + tp->relative_month = a-1; + } + } else { + // mm/dd(/yy[yy]) + if (c < 70) c += 100; + if (c > 138) c -= 1900; + origin.tm_year = c; + origin.tm_mon = a-1; + origin.tm_mday = b == 0 ? 1 : b; + origin_flags.tm_year = 1; + origin_flags.tm_mon = 1; + origin_flags.tm_mday = 1; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) { + tp->relative_year = c+1900; + tp->relative_month = a-1; + tp->relative_day = b; + } + } + origin_flags.tm_year = 1; + origin_flags.tm_mon = 1; + origin_flags.tm_mday = 1; + break; + } + + if (isallnum(token)) + { + lastnumber = i; + switch (lastt) { + case TOKEN_JANUARY: + case TOKEN_FEBRUARY: + case TOKEN_MARCH: + case TOKEN_APRIL: + case TOKEN_MAY: + case TOKEN_JUNE: + case TOKEN_JULY: + case TOKEN_AUGUST: + case TOKEN_SEPTEMBER: + case TOKEN_OCTOBER: + case TOKEN_NOVEMBER: + case TOKEN_DECEMBER: + origin.tm_mday = lastnumber; + origin_flags.tm_mday = 1; + lastnumber = 0; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_day = lastnumber; + break; + case TOKEN_AT: { + origin.tm_hour = lastnumber; + origin.tm_min = 0; + origin.tm_sec = 0; + origin_flags.tm_hour = 1; + origin_flags.tm_min = 1; + origin_flags.tm_sec = 1; + if (tp) { + tp->relative_hour = lastnumber; + tp->relative_min = 0; + tp->relative_sec = 0; + } + lastnumber = 0; + break; + } + } + } + break; + } + default: + lastt = save_lastt; + break; + } + p += size; + } + + if (lastnumber) { + switch (lastt) { + case TOKEN_JANUARY: + case TOKEN_FEBRUARY: + case TOKEN_MARCH: + case TOKEN_APRIL: + case TOKEN_MAY: + case TOKEN_JUNE: + case TOKEN_JULY: + case TOKEN_AUGUST: + case TOKEN_SEPTEMBER: + case TOKEN_OCTOBER: + case TOKEN_NOVEMBER: + case TOKEN_DECEMBER: + origin.tm_mday = lastnumber; + lastnumber = 0; + if (!origin_flags.tm_hour) + origin.tm_hour = 0; + if (!origin_flags.tm_min) + origin.tm_min = 0; + if (!origin_flags.tm_sec) + origin.tm_sec = 0; + if (tp) tp->relative_day = lastnumber; + break; + } + } + + if (ago) { // if ago (or before), from is optional since if it wasn't specified we use now + switch (what) { + case TOKEN_SECOND: + origin.tm_sec -= value; + break; + case TOKEN_MINUTE: + origin.tm_min -= value; + break; + case TOKEN_HOUR: + origin.tm_hour -= value; + break; + case TOKEN_DAY: + origin.tm_mday -= value; + break; + case TOKEN_WEEK: + origin.tm_mday -= value*7; + break; + case TOKEN_MONTH: + origin.tm_mon -= value; + break; + case TOKEN_YEAR: + origin.tm_year -= value; + break; + } + time_t o = mktime(&origin); + SetValue((int)o); + ndestring_release(token); + + if (tp) tp->absolute_datetime = GetValue(); + return 1; + } else if (from) { // from (or after) was specified, but not ago, 5 mn from x is x + 5 mn + switch (what) { + case TOKEN_SECOND: + origin.tm_sec += value; + break; + case TOKEN_MINUTE: + origin.tm_min += value; + break; + case TOKEN_HOUR: + origin.tm_hour += value; + break; + case TOKEN_DAY: + origin.tm_mday += value; + break; + case TOKEN_WEEK: + origin.tm_mday += value*7; + break; + case TOKEN_MONTH: + origin.tm_mon += value; + break; + case TOKEN_YEAR: + origin.tm_year += value; + break; + } + time_t o = mktime(&origin); + SetValue((int)o); + + ndestring_release(token); + + if (tp) tp->absolute_datetime = GetValue(); + return 1; + } else { // none of ago/from/before/after were specified, just make a date/time with what we got and ignore our old value + time_t o = mktime(&origin); + SetValue((int)o); + + ndestring_release(token); + + if (tp) tp->absolute_datetime = GetValue(); + return 1; + } + ndestring_release(token); + + if (tp) tp->absolute_datetime = GetValue(); + + return 0; +} + +//--------------------------------------------------------------------------- +DateTimeField::DateTimeField(int Val) : IntegerField(Val) +{ + Type = FIELD_DATETIME; +} + +//--------------------------------------------------------------------------- +DateTimeField::DateTimeField() +{ + Type = FIELD_DATETIME; +} + +//--------------------------------------------------------------------------- +DateTimeField::~DateTimeField() +{ +} + +//--------------------------------------------------------------------------- +LengthField::LengthField(int Val) : IntegerField(Val) +{ + Type = FIELD_LENGTH; +} + +//--------------------------------------------------------------------------- +LengthField::LengthField() +{ + Type = FIELD_LENGTH; +} + +//--------------------------------------------------------------------------- +LengthField::~LengthField() +{ +} diff --git a/Src/nde/win/IntegerField.h b/Src/nde/win/IntegerField.h new file mode 100644 index 00000000..022932ab --- /dev/null +++ b/Src/nde/win/IntegerField.h @@ -0,0 +1,95 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + IntegerField Class Prototypes + +--------------------------------------------------------------------------- */ + +#ifndef __INTEGERFIELD_H +#define __INTEGERFIELD_H + +#include <bfc/platform/types.h> + +class TimeParse { +public: + int is_relative; // ago/after/before used + int absolute_datetime; // if not is_relative, this is the date/time, otherwise it's the resulting date/time if the query was ran now + int absolute_hasdate; // if not, use only the time portion of absolute_datetime, the rest is now + int absolute_hastime; // if not, use only the date portion of absolute_datetime, the rest is now + int relative_year; // -1 = this year + int relative_month; // -1 = this month + int relative_day; // -1 = this day, -2 = this week + int relative_hour; // -1 = this hour + int relative_min; // -1 = this minute + int relative_sec; // -1 = this second + int relative_kwday; // not used(-1), 0 to 6 for sunday to saturday, yesterday(7), today(8), tomorrow(9) + int offset_value; // timeago/offsetby numerical edit field + int offset_what; // not used(-1) years(0), months(1), weeks(2), days(3), hours(4), minutes(5), seconds(6) + int offset_whence; // not used(-1), after(0), ago/before(1) + int offset_used; // if 1, 'time ago' / 'offset by' should be checked +}; + +class IntegerField : public Field + { + protected: + + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + virtual int Compare(Field *Entry); + virtual bool ApplyFilter(Field *Data, int op); + void InitField(void); + int Value; + + enum { + WHAT_YEARS, + WHAT_MONTHS, + WHAT_WEEKS, + WHAT_DAYS, + WHAT_HOURS, + WHAT_MINUTES, + WHAT_SECONDS, + }; + + enum { + FROM_BARE, + FROM_AGO, + FROM_SINCE, + }; + + public: + + ~IntegerField(); + IntegerField(int); + IntegerField(); + int GetValue(void); + void SetValue(int); + int ApplyConversion(const wchar_t *format, TimeParse *tp=NULL); +#ifdef _WIN32 + static int LookupToken(const wchar_t *t); +#else + static int LookupToken(CFStringRef t); +#endif + }; + + +class DateTimeField : public IntegerField { + public: + DateTimeField(); + DateTimeField(int Val); + virtual ~DateTimeField(); +}; + +class LengthField : public IntegerField { + public: + LengthField(); + LengthField(int Val); + virtual ~LengthField(); +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/Query.cpp b/Src/nde/win/Query.cpp new file mode 100644 index 00000000..54c9e6dd --- /dev/null +++ b/Src/nde/win/Query.cpp @@ -0,0 +1,961 @@ +#include "../nde.h" +#include "../NDEString.h" +#include "Query.h" + +//--------------------------------------------------------------------------- + +BOOL Scanner::Query(const wchar_t *query) +{ + if (!query) return FALSE; + ndestring_release(last_query); + last_query = ndestring_wcsdup(query); + RemoveFilters(); + in_query_parser = 1; + + BOOL r = Query_Parse(query); + if (r == FALSE) + { + if (!disable_date_resolution) RemoveFilters(); + last_query_failed = TRUE; + } + in_query_parser = 0; + Query_CleanUp(); + return r & CheckFilters(); +} + +const wchar_t *Scanner::GetLastQuery() +{ + return last_query; +} + +typedef struct +{ + const wchar_t *token; + int tid; +} tokenstruct; + +tokenstruct Tokens[] = // Feel free to add more... +{ + {L"AND", TOKEN_AND }, + {L"OR", TOKEN_OR }, + {L"HAS", TOKEN_CONTAINS }, + {L"NOTHAS",TOKEN_NOTCONTAINS}, + {L"BEGINS", TOKEN_BEGINS }, + {L"ENDS", TOKEN_ENDS }, + {L"ISEMPTY", TOKEN_ISEMPTY}, + {L"ISNOTEMPTY",TOKEN_ISNOTEMPTY}, + {L"LIKE", TOKEN_LIKE}, + {L"BEGINSLIKE", TOKEN_BEGINSLIKE}, +}; + +typedef struct +{ + int Op; + int Level; +} OpLevel; + +static int Query_ParseLength(const wchar_t *str) +{ + int i = (str ? _wtoi(str) : 0); + + const wchar_t *p; + if ((p=wcsstr(str,L":"))) + { + i*=60; + i+=_wtoi(++p); + if ((p=wcsstr(p,L":"))) + { + i*=60; + i+=_wtoi(++p); + } + } + return i; +} + +/* + our state machine + + &, | + ----------<-------------------------<----------------------<----------- + | | + v ID (Col) =, >, <... ID / Data / [f] ) | + ---->(0) ----->-----> (1) ------>-----> (2) ------>------> (3) ------>-----> (4) <-- + | |^ \isempty------------->------------/ |^ | | + | !( || ||---- | ) | + --<-- ---------<---------------------------<-------------<-| | -->-- + &, | v [f] | + -->-- +*/ + +//--------------------------------------------------------------------------- +BOOL Scanner::Query_Parse(const wchar_t *query) +{ + const wchar_t *p = query; // pointer on next token to read + int state = 0; + int pcount = 0; + VListEntry<OpLevel> *entry = 0; + + if (pstack.GetNElements() > 0) + Query_CleanUp(); + + while (1) + { + p = Query_EatSpace(p); + int size = 0, t = Query_GetNextToken(p, &size, &token); + if (t == TOKEN_UNKNOWN) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + if (t == TOKEN_EOQ) + break; + switch (state) + { + case 0: + switch (t) + { + case TOKEN_PAROPEN: + state = 0; + // check too many parenthesis open + if (pcount == 255) + { + Query_SyntaxError((int)(p-query)); // should not be _syntax_ error + return FALSE; + } + // up one level + pcount++; + break; + case TOKEN_NOT: + // push not in this level + OpLevel o; + o.Op = FILTER_NOT; + o.Level = pcount; + entry = new VListEntry<OpLevel>; + entry->SetVal(o); + pstack.AddEntry(entry, TRUE); + state = 0; + break; + case TOKEN_IDENTIFIER: + state = 1; + // create filter column + + if (AddFilterByName(token, NULL, FILTER_NONE) == ADDFILTER_FAILED) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + + break; + default: + Query_SyntaxError((int)(p-query)); + return FALSE; + } + break; + case 1: + switch (t) + { + case TOKEN_EQUAL: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_EQUALS); + break; + } + case TOKEN_ABOVE: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_ABOVE); + break; + } + case TOKEN_BELOW: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_BELOW); + break; + } + case TOKEN_CONTAINS: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_CONTAINS); + break; + } + case TOKEN_NOTCONTAINS: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_NOTCONTAINS); + break; + } + case TOKEN_AOREQUAL: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_ABOVEOREQUAL); + break; + } + case TOKEN_BOREQUAL: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_BELOWOREQUAL); + break; + } + case TOKEN_NOTEQUAL: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_NOTEQUALS); + break; + } + case TOKEN_BEGINS: + { + state = 2; + Filter *f = GetLastFilter(); + f->SetOp(FILTER_BEGINS); + } + break; + case TOKEN_ENDS: + { + state = 2; + Filter *f = GetLastFilter(); + f->SetOp(FILTER_ENDS); + } + break; + case TOKEN_LIKE: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_LIKE); + break; + } + case TOKEN_BEGINSLIKE: + { + state = 2; + // set filter op + Filter *f = GetLastFilter(); + f->SetOp(FILTER_BEGINSLIKE); + break; + } + case TOKEN_ISNOTEMPTY: + case TOKEN_ISEMPTY: + { + state = 3; + Filter *f = GetLastFilter(); + f->SetOp(t==TOKEN_ISEMPTY ? FILTER_ISEMPTY : FILTER_ISNOTEMPTY); + // pop all operators in this level beginning by the last inserted + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + } + break; + default: + Query_SyntaxError((int)(p-query)); + return FALSE; + } + break; + case 2: + if (t == TOKEN_SQBRACKETOPEN) + { + state = 3; + const wchar_t *s = wcschr(p, L']'); + if (!s) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + p = Query_EatSpace(p); + if (p && *p == L'[') p++; + + wchar_t *format = ndestring_wcsndup(p, s-p); + + Filter *f = GetLastFilter(); + int id = f->GetId(); + ColumnField *c = GetColumnById(id); + int tt = c ? c->GetDataType() : -1; + if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN)) + { + if (disable_date_resolution) + { + StringField *field = (StringField *)f->Data(); + + if (!field) + { + // format was used without a value, assume value is 0 + f->SetData(new StringField(L"")); + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + field = (StringField*)f->Data(); + } + + field->SetNDEString(format); + ndestring_release(format); + p = s+1; + continue; + } + + ndestring_release(format); + Query_SyntaxError((int)(p-query)); + return FALSE; + } + + IntegerField *field = (IntegerField *)f->Data(); + + if (!field) + { + // format was used without a value, assume value is 0 + f->SetData(new IntegerField(0)); + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + field = (IntegerField *)f->Data(); + } + int r = field->ApplyConversion(format); + ndestring_release(format); + if (!r) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + p = s+1; + continue; + } + // switch (t) { + // case TOKEN_IDENTIFIER: + else // JF> we make this relaxed, so anything is valid as a value + { + state = 3; + // set filter data + Filter *f = GetLastFilter(); + int id = f->GetId(); + ColumnField *c = GetColumnById(id); + switch (c ? c->GetDataType() : -1) + { + case FIELD_DATETIME: + if (disable_date_resolution) + goto field_string_override; + case FIELD_LENGTH: + { + IntegerField *i_f = new IntegerField(); + int i = Query_ParseLength(token); + i_f->SetValue(i); + f->SetData(i_f); + } + break; + case FIELD_BOOLEAN: + case FIELD_INTEGER: + { + IntegerField *i_f = new IntegerField(); + int i = _wtoi(token); + i_f->SetValue(i); + f->SetData(i_f); + } + break; + case FIELD_INT64: + { + Int64Field *i_f = new Int64Field(); + int64_t i = _wtoi64(token); // todo: Replace with own conversion and error checking + i_f->SetValue(i); + f->SetData(i_f); + } + break; + case FIELD_FILENAME: + { + FilenameField *s_f = new FilenameField(); + s_f->SetNDEString(token); + f->SetData(s_f); + } + break; + case FIELD_STRING: +field_string_override: + { + StringField *s_f = new StringField(); + s_f->SetNDEString(token); + f->SetData(s_f); + } + break; + default: + Query_SyntaxError((int)(p-query)); + return FALSE; + break; + } + // pop all operators in this level beginning by the last inserted + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + break; + } + // default: + // Query_SyntaxError(p-query); + // return FALSE; + // } + break; + case 3: + switch (t) + { + case TOKEN_SQBRACKETOPEN: + { + const wchar_t *s = wcschr(p, L']'); + if (!s) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + p = Query_EatSpace(p); + if (p && *p == L'[') p++; + wchar_t *format = ndestring_wcsndup(p, s-p); + Filter *f = GetLastFilter(); + int id = f->GetId(); + ColumnField *c = GetColumnById(id); + int tt = c ? c->GetDataType() : -1; + if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN)) + { + if (disable_date_resolution) + { + StringField *field = (StringField *)f->Data(); + + if (!field) + { + // format was used without a value, assume value is 0 + f->SetData(new StringField(L"")); + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + field = (StringField *)f->Data(); + } + + field->SetNDEString(format); + ndestring_release(format); + p = s+1; + continue; + } + ndestring_release(format); + Query_SyntaxError((int)(p-query)); + return FALSE; + } + + IntegerField *field = (IntegerField *)f->Data(); + + if (!field) + { + // format was used without a value, assume value is 0 + f->SetData(new IntegerField(0)); + entry = (VListEntry<OpLevel> *)pstack.GetFoot(); + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + field = (IntegerField *)f->Data(); + } + int r = field->ApplyConversion(format); + ndestring_release(format); + if (!r) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + p = s+1; + continue; + } + break; + + case TOKEN_PARCLOSE: + state = 4; + // check parenthesis count + if (pcount == 0) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + // down one level + pcount--; + // pop all operators in this level, beginning by the last inserted + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + break; + case TOKEN_AND: + { + state = 0; + // push and + OpLevel o; + o.Op = FILTER_AND; + o.Level = pcount; + entry = new VListEntry<OpLevel>; + entry->SetVal(o); + pstack.AddEntry(entry, TRUE); + break; + } + case TOKEN_OR: + { + state = 0; + // push or + OpLevel o; + o.Op = FILTER_OR; + o.Level = pcount; + entry = new VListEntry<OpLevel>; + entry->SetVal(o); + pstack.AddEntry(entry, TRUE); + break; + } + default: + Query_SyntaxError((int)(p-query)); + return FALSE; + } + break; + case 4: + switch (t) + { + case TOKEN_AND: + { + state = 0; + // push and + OpLevel o; + o.Op = FILTER_AND; + o.Level = pcount; + entry = new VListEntry<OpLevel>; + entry->SetVal(o); + pstack.AddEntry(entry, TRUE); + break; + } + case TOKEN_OR: + { + state = 0; + // push or + OpLevel o; + o.Op = FILTER_OR; + o.Level = pcount; + entry = new VListEntry<OpLevel>; + entry->SetVal(o); + pstack.AddEntry(entry, TRUE); + break; + } + case TOKEN_PARCLOSE: + state = 4; + // check parenthesis count + if (pcount == 0) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + // down one level + pcount--; + // pop all operators in this level, beginning by the last inserted + while (entry) + { + if (entry->GetVal().Level == pcount) + { + AddFilterOp(entry->GetVal().Op); + VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious(); + pstack.RemoveEntry(entry); + entry = _entry; + } + else + break; + } + break; + default: + Query_SyntaxError((int)(p-query)); + return FALSE; + } + break; + default: + // Ahem... :/ + break; + } + p += size; + } + if (pcount > 0) + { + Query_SyntaxError((int)(p-query)); + return FALSE; + } + return TRUE; +} + +//--------------------------------------------------------------------------- + +void Scanner::Query_SyntaxError(int c) +{ +} + +//--------------------------------------------------------------------------- +void Scanner::Query_CleanUp() +{ + while (pstack.GetNElements() > 0) + { + VListEntry<int> *e; + e = (VListEntry<int> *)pstack.GetHead(); + pstack.RemoveEntry(e); + } +} + + +//--------------------------------------------------------------------------- +const wchar_t *Scanner::Query_EatSpace(const wchar_t *p) +{ + while (p && *p && *p == L' ') p++; + return p; +} + +//--------------------------------------------------------------------------- +const wchar_t *Scanner::Query_ProbeNonAlphaNum(const wchar_t *p) +{ + int inquote=0; + while (p && *p && (!Query_isControlChar(*p) || (inquote))) + { + if (*p == L'\"') + { + if (!inquote) + inquote = 1; + else + return p+1; + } + p++; + } + return p; +} + +//--------------------------------------------------------------------------- +int Scanner::Query_isControlChar(wchar_t p) +{ + switch (p) + { + case L'&': + case L'|': + case L'!': + case L'(': + case L'[': + case L')': + case L']': + case L'>': + case L'<': + case L'=': + case L',': + case L' ': + return TRUE; + } + return FALSE; +} + +//--------------------------------------------------------------------------- +wchar_t *Scanner::Query_ProbeAlphaNum(wchar_t *p) +{ + while (p && *p && Query_isControlChar(*p)) p++; + return p; +} + +//--------------------------------------------------------------------------- +wchar_t *Scanner::Query_ProbeSpace(wchar_t *p) +{ + while (p && *p && *p != ' ') p++; + return p; +} + +//--------------------------------------------------------------------------- +int Scanner::Query_LookupToken(const wchar_t *t) +{ + for (int i=0;i<sizeof(Tokens)/sizeof(tokenstruct);i++) + { + if (!_wcsicmp(Tokens[i].token, t)) + return Tokens[i].tid; + } + return TOKEN_IDENTIFIER; +} + +//--------------------------------------------------------------------------- + +int Scanner::Query_GetNextToken(const wchar_t *p, int *size, wchar_t **_token, int tokentable) +{ + int t = TOKEN_EOQ; + const wchar_t *startptr = p; + + if (!p || !*p) return TOKEN_EOQ; + + p = Query_EatSpace(p); + + const wchar_t *e = Query_ProbeNonAlphaNum(p); + + if (e != p) // We have a word + { + size_t token_length = e-p; + if (*_token) ndestring_release(*_token); + *_token = ndestring_wcsndup(p, token_length); + if (*(*_token) == L'\"' && (*_token)[token_length-1] == L'\"') // check for quoted string + { + int l=(int)token_length-2; + if (l>0) + { + memcpy(*_token,(*_token)+1,l*sizeof(wchar_t)); + (*_token)[l]=0; + Query_Unescape(*_token); + } + else + (*_token)[0]=0;// we have an empty string + } + + switch (tokentable) + { + case -1: + t = TOKEN_IDENTIFIER; + break; + case 0: + t = Query_LookupToken(*_token); + break; + case 1: + t = IntegerField::LookupToken(*_token); + } + p = e; + } + else // We have a symbol + { + switch (*p) + { + case L'&': + if (*(p+1) == L'&') p++; + t = TOKEN_AND; + break; + case L'|': + if (*(p+1) == L'|') p++; + t = TOKEN_OR; + break; + case L'!': + if (*(p+1) == L'=') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_NOT; + break; + case L'(': + t = TOKEN_PAROPEN; + break; + case L')': + t = TOKEN_PARCLOSE; + break; + case L'[': + t = TOKEN_SQBRACKETOPEN; + break; + case L']': + t = TOKEN_SQBRACKETCLOSE; + break; + case L',': + t = TOKEN_COMMA; + break; + case L'>': + if (*(p+1) == L'=') + { + p++; + t = TOKEN_AOREQUAL; + break; + } + if (*(p+1) == L'<') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_ABOVE; + break; + case L'<': + if (*(p+1) == L'=') + { + p++; + t = TOKEN_BOREQUAL; + break; + } + if (*(p+1) == L'>') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_BELOW; + break; + case L'=': + if (*(p+1) == L'>') + { + p++; + t = TOKEN_AOREQUAL; + break; + } + if (*(p+1) == L'<') + { + p++; + t = TOKEN_BOREQUAL; + break; + } + if (*(p+1) == L'!') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + if (*(p+1) == L'=') p++; + t = TOKEN_EQUAL; + break; + default: + t = TOKEN_UNKNOWN; + break; + } + p++; + } + + *size = (int)(p - startptr); + return t; +} + +static uint8_t quickhex(wchar_t c) +{ + int hexvalue = c; + if (hexvalue & 0x10) + hexvalue &= ~0x30; + else + { + hexvalue &= 0xF; + hexvalue += 9; + } + return hexvalue; +} + +static uint8_t DecodeEscape(const wchar_t *&str) +{ + uint8_t a = quickhex(*++str); + uint8_t b = quickhex(*++str); + str++; + return a * 16 + b; +} + +static void DecodeEscapedUTF8(wchar_t *&output, const wchar_t *&input) +{ + uint8_t utf8_data[1024] = {0}; // hopefully big enough!! + int num_utf8_words=0; + bool error=false; + + while (input && *input == '%' && num_utf8_words < sizeof(utf8_data)) + { + if (iswxdigit(input[1]) && iswxdigit(input[2])) + { + utf8_data[num_utf8_words++]=DecodeEscape(input); + } + else if (input[1] == '%') + { + input+=2; + utf8_data[num_utf8_words++]='%'; + } + else + { + error = true; + break; + } + } + + int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, 0, 0); + MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, output, len); + output += len; + + if (error) + { + *output++ = *input++; + } +} + +// benski> We have the luxury of knowing that decoding will ALWAYS produce smaller strings +// so we can do it in-place +void Query_Unescape(wchar_t *str) +{ + const wchar_t *itr = str; + while (itr && *itr) + { + switch (*itr) + { + case '%': + DecodeEscapedUTF8(str, itr); + break; + default: + *str++ = *itr++; + break; + } + } + *str = 0; +}
\ No newline at end of file diff --git a/Src/nde/win/Query.h b/Src/nde/win/Query.h new file mode 100644 index 00000000..1c9e04a4 --- /dev/null +++ b/Src/nde/win/Query.h @@ -0,0 +1,30 @@ +#pragma once + +#define TOKEN_UNKNOWN -1 // BLAAAAA!!!!!!!! +#define TOKEN_EOQ 0 // End of Query +#define TOKEN_IDENTIFIER 1 // Column name or data to match against +#define TOKEN_EQUAL 2 // =, ==, IS +#define TOKEN_NOTEQUAL 3 // !=, =!, <>, >< +#define TOKEN_BELOW 4 // < +#define TOKEN_ABOVE 5 // > +#define TOKEN_BOREQUAL 6 // <=, =< +#define TOKEN_AOREQUAL 7 // >=, => +#define TOKEN_NOT 8 // !, NOT +#define TOKEN_AND 9 // &, &&, AND +#define TOKEN_OR 10 // |, ||, OR +#define TOKEN_PAROPEN 11 // ( +#define TOKEN_PARCLOSE 12 // ) +#define TOKEN_CONTAINS 13 // HAS +#define TOKEN_BEGINS 14 // string starts with... +#define TOKEN_ENDS 15 // string ends with... +#define TOKEN_LIKE 16 // string is nearly (excluding "the " and whitespace etc) +#define TOKEN_ISEMPTY 17 // field does not exists +#define TOKEN_SQBRACKETOPEN 18 // [ +#define TOKEN_SQBRACKETCLOSE 19 // ] +#define TOKEN_COMMA 20 // , +#define TOKEN_NOTCONTAINS 21 // NOTHAS +#define TOKEN_ISNOTEMPTY 22 // field does not exists +#define TOKEN_BEGINSLIKE 23 // string is nearly starts with (excluding "the " and whitespace etc) + +// in-place +void Query_Unescape(wchar_t *p);
\ No newline at end of file diff --git a/Src/nde/win/Record.cpp b/Src/nde/win/Record.cpp new file mode 100644 index 00000000..f54d2798 --- /dev/null +++ b/Src/nde/win/Record.cpp @@ -0,0 +1,133 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Record Class + +--------------------------------------------------------------------------- */ + +//#include "record.h" +#include "../nde.h" +#include <stdio.h> + +void RecordBase::Retain() +{ + ref_count++; +} + +void RecordBase::Release() +{ + if (--ref_count == 0) + delete this; +} + +RecordBase::RecordBase() +{ + ref_count = 1; + InsertionPoint = 0; +} + +//--------------------------------------------------------------------------- +Record::Record(int RecordPos, int insertionPoint, VFILE *TableHandle, Table *ParentTable) +{ + InsertionPoint = insertionPoint; + Record *columns = ParentTable->GetColumns(); + int max=columns ? (int)columns->Fields.size() : 128; + if (RecordPos != 0) + { + int n=0; + uint32_t ThisPos = RecordPos; + while (ThisPos) + { + if (n >= max) + break; + Vfseek(TableHandle, ThisPos, SEEK_SET); + Field Entry (ThisPos); + Field *TypedEntry = Entry.ReadField(ParentTable, ThisPos, &ThisPos); + + if (!TypedEntry) break; // some db error? + + AddField(TypedEntry); + n++; + } + } +} + +//--------------------------------------------------------------------------- +RecordBase::~RecordBase() +{ + //Fields.deleteAll(); + for (Field* field : Fields) + { + if (field) + { + delete field; + } + } + Fields.clear(); +} + +ColumnField *Record::GetColumnByName(const wchar_t *name) +{ + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + ColumnField *p = (ColumnField *)*itr; + if (!_wcsicmp(p->GetFieldName(), name)) + return p; + } + return NULL; +} + +//--------------------------------------------------------------------------- +int Record::WriteFields(Table *ParentTable, int RecordIndex) +{ + Field *previous = 0; + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + Field *p = *itr; + + Field* nextField = (itr + 1) != Fields.end() ? *(itr + 1) : nullptr; + + //p->WriteField(ParentTable, previous, (Field*)p->next); + p->WriteField(ParentTable, previous, nextField); + + previous = p; + } + return WriteIndex(ParentTable, RecordIndex); +} + +//--------------------------------------------------------------------------- +int Record::WriteIndex(Table *ParentTable, int RecordIndex) +{ + int P=0; + if (RecordIndex == NEW_RECORD) + RecordIndex = ParentTable->index->Insert(InsertionPoint); + if (!Fields.empty()) + { + Field *f = *Fields.begin(); + P=f->GetFieldPos(); + } + return ParentTable->index->Update(RecordIndex, P, this, FALSE); +} + +//--------------------------------------------------------------------------- +void Record::Delete(Table *ParentTable, int RecordIndex) +{ + ParentTable->index->Delete(RecordIndex, ParentTable->index->Get(RecordIndex), this); +} + +void Record::WalkFields(FieldsWalker callback, void *context) +{ + if (callback) + { + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + if (!callback(this, *itr, context)) + break; + } + } +}
\ No newline at end of file diff --git a/Src/nde/win/Record.h b/Src/nde/win/Record.h new file mode 100644 index 00000000..0c92acef --- /dev/null +++ b/Src/nde/win/Record.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../nde.h" +#include "../Field.h" +#include "Vfs.h" +#include <stdio.h> +#include <deque> + +class ColumnField; + +class RecordBase +{ +public: + RecordBase(); + virtual ~RecordBase(); + Field *GetField(unsigned char ID); + void Retain(); + void Release(); + void RemoveField(Field *field); + void AddField(Field *field); + Field* GetLastField() + { + if (Fields.empty()) + { + return nullptr; + } + return Fields[Fields.size() - 1]; + } +protected: + void Undelete(void); + + int InsertionPoint; // TODO: benski> might be able to pass this into WriteFields/WriteIndex + int ref_count; + typedef std::deque<Field*> FieldList; + FieldList Fields; +}; + +class Record : public RecordBase +{ +public: + Record(int RecordPos, int insertionPoint, VFILE *FileHandle,Table *p); + bool InCache() { return ref_count > 1; } + + ColumnField *GetColumnByName(const wchar_t *name); + int WriteFields(Table *ParentTable, int RecordIndex); + int WriteIndex(Table *ParentTable, int RecordIndex); + void Delete(Table *ParentTable, int RecordIndex); + typedef bool (__cdecl *FieldsWalker)(Record *record, Field *entry, void *context); + NDE_API void WalkFields(FieldsWalker callback, void *context); +};
\ No newline at end of file diff --git a/Src/nde/win/Scanner.cpp b/Src/nde/win/Scanner.cpp new file mode 100644 index 00000000..8b0886ef --- /dev/null +++ b/Src/nde/win/Scanner.cpp @@ -0,0 +1,1238 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Scanner Class + +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "../nu/AutoChar.h" +#include "../nu/AutoWide.h" +#include "BinaryField.h" +#include "Binary32Field.h" +#include <assert.h> +//--------------------------------------------------------------------------- +Scanner::Scanner(Table *parentTable) +{ + disable_date_resolution=0; + index = NULL; + pTable = parentTable; + Edition=FALSE; + + lastLocateIndex = NULL; + lastLocateId = -1; + lastLocateIdx = -1; + lastLocateFrom = -128; + lastLocateFieldClone = NULL; + CurrentRecord=NULL; + CurrentRecordIdx=0; + iModified = FALSE; + FiltersOK = FALSE; + + search_any = FALSE; + ResultPtr = 0; + + /*inMatchJoins = 0; + lastJoinCache = 0; + invalidJoinCache = 1;*/ + last_query = NULL; + last_query_failed = FALSE; + token = NULL; + in_query_parser = 0; +} + +//--------------------------------------------------------------------------- +Scanner::~Scanner() +{ + for ( StringField *l_string_field : search_strings ) + delete l_string_field; + + search_strings.clear(); + + if ( CurrentRecord ) + CurrentRecord->Release(); + + if ( lastLocateFieldClone ) + { + delete lastLocateFieldClone; + } + + Query_CleanUp(); + + if ( token ) + ndestring_release( token ); + + ndestring_release( last_query ); +} + +//--------------------------------------------------------------------------- +Record *Scanner::GetRecord(int Idx) +{ + int Ptr; + Ptr = index->Get(Idx); + + Record *r = pTable->RowCache_Get(Ptr); + if (r) + { + return r; + } + + r = new Record(Ptr, Idx, pTable->Handle, pTable); + pTable->RowCache_Add(r, Ptr); + return r; +} + +//--------------------------------------------------------------------------- +void Scanner::GetCurrentRecord(void) +{ + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = NULL; + + //invalidJoinCache = 1; + if (Eof() || Bof()) return; + CurrentRecord = GetRecord(CurrentRecordIdx); + +} + +//--------------------------------------------------------------------------- +void Scanner::GetRecordById(int Id, BOOL checkFilters) +{ + CurrentRecordIdx=max(min(index->NEntries, Id+NUM_SPECIAL_RECORDS), 0); + GetCurrentRecord(); + if (!checkFilters || MatchFilters()) + return; + Next(); +} + +//--------------------------------------------------------------------------- +void Scanner::First(int *killswitch) +{ + if (last_query_failed) return; + GetRecordById(0); + if (!MatchFilters() && !Eof()) + Next(killswitch); +} + +//--------------------------------------------------------------------------- +int Scanner::Next(int *killswitch) +{ + if (last_query_failed) return 0; + + while (!Eof() && !Bof()) + { + CurrentRecordIdx++; + GetCurrentRecord(); + if (MatchFilters()) + break; + else + { + if ((killswitch && *killswitch)) + return 0; + } + } + return 1; +} + +//--------------------------------------------------------------------------- +int Scanner::Previous(int *killswitch) +{ + if (last_query_failed) return 0; + + while (CurrentRecordIdx >= NUM_SPECIAL_RECORDS) + { + CurrentRecordIdx--; + GetCurrentRecord(); + if (MatchFilters()) + break; + else + { + if ((killswitch && *killswitch)) + return 0; + } + } + return 1; +} + +//--------------------------------------------------------------------------- +void Scanner::Last(int *killswitch) +{ + if (last_query_failed) return; + GetRecordById(index->NEntries-NUM_SPECIAL_RECORDS-1); // -3 here because 1)GetRecordById is public, so -NUM_SPECIAL_RECORDS, and 2)last entry is nentries-1, so -1 + if (!MatchFilters() && !Bof()) + Previous(killswitch); + if (CurrentRecordIdx < NUM_SPECIAL_RECORDS) + { + CurrentRecordIdx = index->NEntries; + GetCurrentRecord(); // will only delete current record if it exists + } +} + +//--------------------------------------------------------------------------- +BOOL Scanner::Eof(void) +{ + if (last_query_failed) return TRUE; + return CurrentRecordIdx >= index->NEntries; +} + +//--------------------------------------------------------------------------- +BOOL Scanner::Bof(void) +{ + if (last_query_failed) return TRUE; + return CurrentRecordIdx < NUM_SPECIAL_RECORDS; +} + +//--------------------------------------------------------------------------- +Field *Scanner::GetFieldByName(const wchar_t *FieldName) +{ + ColumnField *header = pTable->GetColumnByName(FieldName); + if (header) + { + unsigned char Idx = header->ID; + return GetFieldById(Idx); + } + return NULL; +} + +//--------------------------------------------------------------------------- +Field *Scanner::GetFieldById(unsigned char Id) +{ + if (!CurrentRecord) + return NULL; + Field *field = CurrentRecord->GetField(Id); + if (field) + { + int column_type = pTable->GetColumnType(Id); + if (!CompatibleFields(field->GetType(), column_type)) + { + return NULL; + } + } + return field; +} + +//--------------------------------------------------------------------------- +Field *Scanner::NewFieldByName(const wchar_t *fieldName, unsigned char Perm) +{ + ColumnField *header = pTable->GetColumnByName(fieldName); + if (header) + { + unsigned char Id = header->ID; + Field *field = NewFieldById(Id, Perm); + return field; + } + return NULL; +} + +//--------------------------------------------------------------------------- +Field *Scanner::NewFieldById(unsigned char Id, unsigned char Perm) +{ + ColumnField *field = GetColumnById(Id); + if (!field) + return NULL; + Field *O=GetFieldById(Id); + if (O) return O; + switch (field->GetDataType()) + { + case FIELD_STRING: + O = new StringField(); + break; + case FIELD_INTEGER: + O = new IntegerField(); + break; + case FIELD_INT64: + O = new Int64Field(); + break; + case FIELD_INT128: + O = new Int128Field(); + break; + case FIELD_DATETIME: + if (disable_date_resolution) + O= new StringField(); + else + O = new DateTimeField(); + break; + case FIELD_LENGTH: + O = new LengthField(); + break; + case FIELD_FILENAME: + O = new FilenameField(); + break; + case FIELD_BINARY: + O = new BinaryField(); + break; + case FIELD_BINARY32: + O = new Binary32Field(); + break; + default: + MessageBox(NULL, L"unknown field type for id", L"debug", 0); + O = new Field(); + break; + } + O->Type = field->GetDataType(); + O->ID = Id; + CurrentRecord->AddField(O); + return O; +} + +Field *Scanner::NewFieldByType(unsigned char Type, unsigned char Id, unsigned char Perm) +{ + Field *O = NULL; + switch (Type) + { + case FIELD_STRING: + O = new StringField(); + break; + case FIELD_INTEGER: + O = new IntegerField(); + break; + case FIELD_INT64: + O = new Int64Field(); + break; + case FIELD_INT128: + O = new Int128Field(); + break; + case FIELD_DATETIME: + if (disable_date_resolution) + O= new StringField(); + else + O = new DateTimeField(); + break; + case FIELD_LENGTH: + O = new LengthField(); + break; + case FIELD_FILENAME: + O = new FilenameField(); + break; + case FIELD_BINARY: + O = new BinaryField(); + break; + case FIELD_BINARY32: + O = new Binary32Field(); + break; + default: + MessageBox(NULL, L"unknown field type for id", L"debug", 0); + O = new Field(); + break; + } + O->Type = Type; + O->ID = Id; + CurrentRecord->AddField(O); + return O; +} + +//--------------------------------------------------------------------------- +void Scanner::Post(void) +{ + if (!CurrentRecord) return; + /*if (CurrentRecord->RecordIndex == NEW_RECORD) + NEntries++;*/ + if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD) + { + int Ptr = index->Get(CurrentRecordIdx); + pTable->RowCache_Remove(Ptr); + } + CurrentRecordIdx = CurrentRecord->WriteFields(pTable, CurrentRecordIdx); + Edition=FALSE; + if (pTable->use_row_cache) + { + int Ptr = index->Get(CurrentRecordIdx); + pTable->RowCache_Add(CurrentRecord, Ptr); + } +} + +//--------------------------------------------------------------------------- +void Scanner::New(void) +{ + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = NULL; + + CurrentRecord = new Record(0, index->NEntries, pTable->Handle, pTable); + CurrentRecordIdx = NEW_RECORD; + Edition = TRUE; +} + +//--------------------------------------------------------------------------- +void Scanner::Insert(void) +{ + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = new Record(0, CurrentRecordIdx, pTable->Handle, pTable); + CurrentRecordIdx = NEW_RECORD; + Edition=TRUE; +} + +//--------------------------------------------------------------------------- +void Scanner::Delete(void) +{ + if (CurrentRecord) + { + if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD) + { + int Ptr = index->Get(CurrentRecordIdx); + pTable->RowCache_Delete(Ptr); + } + CurrentRecord->Delete(pTable, CurrentRecordIdx); + } + if (Eof()) + Previous(); + GetRecordById(CurrentRecordIdx-NUM_SPECIAL_RECORDS); +} + +//--------------------------------------------------------------------------- +int Scanner::GetRecordId(void) +{ + return CurrentRecordIdx != NEW_RECORD ? CurrentRecordIdx-NUM_SPECIAL_RECORDS : CurrentRecordIdx; +} + +//--------------------------------------------------------------------------- +void Scanner::Edit(void) +{ + if (Edition) return; + if (!CurrentRecord) + return; + /*Field *f = (Field *)CurrentRecord->Fields->GetHead(); + while (f) + { + f->SubtableRecord = INVALID_RECORD; + f = (Field *)f->GetNext(); + }*/ + + if (CurrentRecord->InCache()) // if it's in the cache + { + // benski> make copy of CurrentRecord, outside the cache + int Ptr = index->Get(CurrentRecordIdx); + pTable->RowCache_Remove(Ptr); + Record *r = new Record(Ptr, CurrentRecordIdx, pTable->Handle, pTable); + CurrentRecord->Release(); + CurrentRecord = r; + } + + Edition = TRUE; +} + + +//--------------------------------------------------------------------------- +void Scanner::Cancel(void) +{ + Edition = FALSE; + GetCurrentRecord(); +} + +//--------------------------------------------------------------------------- +BOOL Scanner::LocateByName(const wchar_t *col, int From, Field *field, int *nskip) +{ + ColumnField *f = pTable->GetColumnByName(col); + if (!f) + return NULL; + return LocateById(f->GetFieldId(), From, field, nskip); +} + +//--------------------------------------------------------------------------- +void Scanner::CacheLastLocate(int Id, int From, Field *field, Index *i, int j) +{ + lastLocateId = Id; + lastLocateFrom = From; + if (lastLocateFieldClone) + { + delete lastLocateFieldClone; + lastLocateFieldClone = NULL; + } + lastLocateFieldClone = field->Clone(pTable); + lastLocateIndex = i; + i->locateUpToDate = TRUE; + pTable->SetGlobalLocateUpToDate(TRUE); + lastLocateIdx = j; +} + + +//--------------------------------------------------------------------------- +BOOL Scanner::LocateById(int Id, int From, Field *field, int *nskip) +{ + return LocateByIdEx(Id, From, field, nskip, COMPARE_MODE_EXACT); +} +//--------------------------------------------------------------------------- +BOOL Scanner::LocateByIdEx(int Id, int From, Field *field, int *nskip, int comp_mode) +{ + IndexField *i = pTable->GetIndexById(Id); + Field *compField=NULL; + int j=0; + int n=0; + Field *cfV=NULL; + + if (index->NEntries == NUM_SPECIAL_RECORDS) + return FALSE; + + int success = FALSE; + + if (nskip) *nskip=0; + // I know this is stupid but.... May be do something later + switch (comp_mode) + { + case COMPARE_MODE_CONTAINS: + while (1) + { + success = FALSE; + if (!i) + { + // No index for this column. Using slow locate, enumerates the database, still faster than what the user + // can do since we have access to QuickFindField which only read field headers + // in order to locate the field we have to compare. user could only read the entire record. + if (From == FIRST_RECORD) + From = NUM_SPECIAL_RECORDS; + else + From+=(NUM_SPECIAL_RECORDS+1); + if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_1; + } + for (j=From;j<index->NEntries;j++) + { + compField = index->QuickFindField(Id, index->Get(j)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (!field->Contains(cfV)) + { + if (compField) + { + delete compField; + } + if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, index, j); + success = TRUE; + goto nextiter_1; + } + delete compField; + } + success = FALSE; + goto nextiter_1; + } + else + { + // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria + int p = 0; + if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS; + else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1; + if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_1; + } + if (From >= index->NEntries) + { + return FALSE; + } + compField = i->index->QuickFindField(Id, i->index->Get(From)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (field->Contains(cfV) == 0) + { + delete compField; + compField = NULL; + n = i->index->TranslateIndex(From, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_1; + } + delete compField; + compField = NULL; + p = i->index->QuickFindEx(Id, field, From, comp_mode); + if (p != FIELD_NOT_FOUND) + { + n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_1; + } + } +nextiter_1: // eek + if (success) + { + if (!MatchFilters() && !Eof()) + { + From = GetRecordId(); + if (nskip) (*nskip)++; + } + else break; + } + else break; + } + + break; + case COMPARE_MODE_EXACT: + while (1) + { + success = FALSE; + if (!i) + { + // No index for this column. Using slow locate, enumerates the database, still faster than what the user + // can do since we have access to QuickFindField which only read field headers + // in order to locate the field we have to compare. user could only read the entire record. + if (From == FIRST_RECORD) + From = NUM_SPECIAL_RECORDS; + else + From+=(NUM_SPECIAL_RECORDS+1); + if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_2; + } + for (j=From;j<index->NEntries;j++) + { + compField = index->QuickFindField(Id, index->Get(j)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (!field->Compare(cfV)) + { + delete compField; + compField = NULL; + if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, index, j); + success = TRUE; + goto nextiter_2; + } + delete compField; + compField = NULL; + } + success = FALSE; + goto nextiter_2; + } + else + { + // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria + int p; + if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS; + else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1; + if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_2; + } + if (From >= index->NEntries) + { + return FALSE; + } + compField = i->index->QuickFindField(Id, i->index->Get(From)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (field->Compare(cfV) == 0) + { + if (compField) + { + delete compField; + compField = NULL; + } + n = i->index->TranslateIndex(From, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_2; + } + delete compField; + p = i->index->QuickFindEx(Id, field, From, comp_mode); + if (p != FIELD_NOT_FOUND) + { + n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_2; + } + } +nextiter_2: // eek + if (success) + { + if (!MatchFilters() && !Eof()) + { + From = GetRecordId(); + if (nskip) (*nskip)++; + } + else break; + } + else break; + } + break; + case COMPARE_MODE_STARTS: + while (1) + { + success = FALSE; + if (!i) + { + // No index for this column. Using slow locate, enumerates the database, still faster than what the user + // can do since we have access to QuickFindField which only read field headers + // in order to locate the field we have to compare. user could only read the entire record. + if (From == FIRST_RECORD) + From = NUM_SPECIAL_RECORDS; + else + From+=(NUM_SPECIAL_RECORDS+1); + if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_3; + } + for (j=From;j<index->NEntries;j++) + { + compField = index->QuickFindField(Id, index->Get(j)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (!field->Starts(cfV)) + { + if (compField) + { + delete compField; + compField = NULL; + } + if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, index, j); + success = TRUE; + goto nextiter_3; + } + if (compField) + { + delete compField; + compField = NULL; + } + } + success = FALSE; + goto nextiter_3; + } + else + { + // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria + int p = 0; + if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS; + else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1; + if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate)) + { + if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE); + success = TRUE; + goto nextiter_3; + } + if (From >= index->NEntries) + { + return FALSE; + } + compField = i->index->QuickFindField(Id, i->index->Get(From)); + cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField; + if (field->Starts(cfV) == 0) + { + delete compField; + compField = NULL; + n = i->index->TranslateIndex(From, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_3; + } + delete compField; + compField = NULL; + p = i->index->QuickFindEx(Id, field, From, comp_mode); + if (p != FIELD_NOT_FOUND) + { + n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index); + if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE); + CacheLastLocate(Id, From, field, i->index, n); + success = TRUE; + goto nextiter_3; + } + } +nextiter_3: // eek + if (success) + { + if (!MatchFilters() && !Eof()) + { + From = GetRecordId(); + if (nskip) (*nskip)++; + } + else break; + } + else break; + } + break; + } + + return success; +} + +//--------------------------------------------------------------------------- +void Scanner::DeleteFieldByName(const wchar_t *name) +{ + ColumnField *header = pTable->GetColumnByName(name); + if (header) + { + unsigned char Idx = header->ID; + DeleteFieldById(Idx); + } + return; +} + + +//--------------------------------------------------------------------------- +void Scanner::DeleteFieldById(unsigned char Id) +{ + Field *field = CurrentRecord->GetField(Id); + if (!field) return; + CurrentRecord->RemoveField(field); +} + +//--------------------------------------------------------------------------- +void Scanner::DeleteField(Field *field) +{ + if (!field) return; + CurrentRecord->RemoveField(field); +} + +static bool TotalSizeCalculator(Record *record, Field *f, void *context) +{ + size_t *totalSize = (size_t *)context; + *totalSize += f->GetTotalSize(); + return true; +} + +//--------------------------------------------------------------------------- +float Scanner::FragmentationLevel(void) +{ + int oldP = GetRecordId(); + size_t totalSize=0; + + if (CurrentRecord) + { + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = NULL; + CurrentRecordIdx = 0; + } + + for (int i=0;i<index->NEntries;i++) + { + Record *r = GetRecord(i); + if (r) + { + r->WalkFields(TotalSizeCalculator, &totalSize); + r->Release(); + } + } + GetRecordById(oldP); + Vfseek(pTable->Handle, 0, SEEK_END); + return (((float)(Vftell(pTable->Handle)-strlen(__TABLE_SIGNATURE__)) / (float)totalSize) - 1) * 100; +} + +//--------------------------------------------------------------------------- +int Scanner::GetRecordsCount(void) +{ + if (index) + return index->NEntries-NUM_SPECIAL_RECORDS; + else + return 0; +} + +//--------------------------------------------------------------------------- +BOOL Scanner::SetWorkingIndexById(unsigned char Id) +{ + IndexField *indx = pTable->GetIndexById(Id); + int v = CurrentRecordIdx; + if (indx) + { + if (!Eof() && !Bof()) + { + IndexField *f = index->SecIndex; + v = index->GetCooperative(CurrentRecordIdx); + while (f != indx) + { + v = f->index->GetCooperative(v); + f = f->index->SecIndex; + } + } + index = indx->index; + CurrentRecordIdx = v; + GetCurrentRecord(); + } + return (indx != NULL); +} + +//--------------------------------------------------------------------------- +BOOL Scanner::SetWorkingIndexByName(const wchar_t *desc) +{ + IndexField *indx = pTable->GetIndexByName(desc); + if (indx) + return SetWorkingIndexById(indx->ID); + else + return SetWorkingIndexById(-1); +} + +//--------------------------------------------------------------------------- +void Scanner::IndexModified(void) +{ + iModified = TRUE; +} + +//--------------------------------------------------------------------------- +void Scanner::ClearDirtyBit(void) +{ + iModified = FALSE; +} + +//--------------------------------------------------------------------------- +Table *Scanner::GetTable(void) +{ + return pTable; +} + +//--------------------------------------------------------------------------- +ColumnField *Scanner::GetColumnByName(const wchar_t *FieldName) +{ + return pTable->GetColumnByName(FieldName); +} +//--------------------------------------------------------------------------- +ColumnField *Scanner::GetColumnById(unsigned char Idx) +{ + return pTable->GetColumnById(Idx); +} + +//--------------------------------------------------------------------------- +int Scanner::AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op) +{ + ColumnField *f = pTable->GetColumnByName(name); + if (f) + return AddFilterById(f->GetFieldId(), Data, Op); + return ADDFILTER_FAILED; +} + +//--------------------------------------------------------------------------- +int Scanner::AddFilterById(unsigned char Id, Field *Data, unsigned char Op) +{ + ColumnField *f = pTable->GetColumnById(Id); + if (f) + { + Filter *filter = new Filter(Data, f->GetFieldId(), Op); + FilterList.AddEntry(filter, TRUE); + } + else + return ADDFILTER_FAILED; + + if (in_query_parser) return 1; + return CheckFilters(); +} + +//--------------------------------------------------------------------------- +int Scanner::AddFilterOp(unsigned char Op) +{ + Filter *filter = new Filter(Op); + FilterList.AddEntry(filter, TRUE); + if (in_query_parser) return 1; + return CheckFilters(); +} + +//--------------------------------------------------------------------------- +Filter *Scanner::GetLastFilter(void) +{ + if (FilterList.GetNElements() == 0) return NULL; + return (Filter *)FilterList.GetFoot(); +} + +//--------------------------------------------------------------------------- +void Scanner::RemoveFilters(void) +{ + last_query_failed = FALSE; + while (FilterList.GetNElements() > 0) + FilterList.RemoveEntry(FilterList.GetHead()); + FiltersOK = FALSE; +} + +//--------------------------------------------------------------------------- +BOOL Scanner::CheckFilters(void) +{ + int f=0; + FiltersOK = FALSE; + if (FilterList.GetNElements() == 0) // Should never happen + return FILTERS_INVALID; + Filter *filter = (Filter *)FilterList.GetHead(); + while (filter) + { + if (f == 256) return FILTERS_INVALID; + int op = filter->GetOp(); + if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY) + f++; + else + { + if (op != FILTER_NOT) + f--; + } + if (f == 0) return FILTERS_INVALID; + filter = (Filter *)filter->GetNext(); + } + + if (f == 1) + { + FiltersOK = TRUE; + return FILTERS_COMPLETE; + } + return FILTERS_INCOMPLETE; +} + +//--------------------------------------------------------------------------- +static bool EmptyMeansTrue(int op) +{ + return op == FILTER_ISEMPTY + || op == FILTER_NOTEQUALS + || op == FILTER_NOTCONTAINS; +} + +//--------------------------------------------------------------------------- +bool Scanner::MatchFilter(Filter *filter) +{ + Field *field = GetFieldById(filter->GetId()); + int op = filter->GetOp(); + Field * f = filter->Data(); + /* old behaviour + if (!field) + return EmptyMeansTrue(op); + else if (op == FILTER_ISEMPTY) + return FALSE; + else if (op == FILTER_ISNOTEMPTY) + return TRUE; + */ + + // new behaviour + if (!field) + { + // if field is empty and we're doing an equals op, match if f is also empty + if (op == FILTER_EQUALS && f) return f->ApplyFilter(f,FILTER_ISEMPTY); + if (op == FILTER_NOTEQUALS && f) return f->ApplyFilter(f,FILTER_ISNOTEMPTY); + return EmptyMeansTrue(op); + } + // no need to check for op == FILTER_ISEMPTY, the fields now handle that + + return field->ApplyFilter(f, op); +} + +struct Results +{ + Results(){ + value = false; + calculated = false; + filter = 0; + } + + void operator=(bool _val) + { + calculated=true; + value=_val; + } + + bool Calc(Scanner *scanner) + { + if (!calculated) + { +#if 0 /* if we want to do field-to-field comparisons */ + Field *compare_column = filter->Data(); + if (compare_column && compare_column->Type == FIELD_COLUMN) + { + Field *field = scanner->GetFieldById(filter->GetId()); + Field *compare_field = scanner->GetFieldById(compare_column->ID); + int op = filter->GetOp(); + value = field->ApplyFilter(compare_field, op); + calculated=true; + return value; + } +#endif + value = scanner->MatchFilter(filter); + calculated=true; + } + return value; + } + + void SetFilter(Filter *_filter) + { + calculated=false; + filter=_filter; + } +private: + bool value; + bool calculated; + Filter *filter; +}; + +bool Scanner::MatchSearch(const SearchFields &fields, StringField *search_field) +{ + for (SearchFields::const_iterator itr = fields.begin(); itr != fields.end();itr++) + { + Field *f = GetFieldById(*itr); + if (f && f->Contains(search_field)) + { + return true; + } + } + return false; // if none of the fields matched the search strings, then bail out +} + +bool Scanner::MatchSearches() +{ + // no search means always match + if (search_strings.empty()) + return true; + + Scanner *s = pTable->GetDefaultScanner(); // kind of a hack but gets around private member variables + const SearchFields &fields = s->search_fields; + if (search_any) + { + for ( StringField *l_string_field : search_strings ) + { + if ( MatchSearch( fields, l_string_field ) == true ) + return true; + } + + return false; // we'll only get here if no search strings matched + } + else + { // normal search (subsequent terms do further filtering + for ( StringField *l_string_field : search_strings ) + { + if ( MatchSearch( fields, l_string_field ) == false ) + return false; + } + + return true; // we'll only get here if all search strings have a match + } +} + +//--------------------------------------------------------------------------- +bool Scanner::MatchFilters(void) +{ + if (!FiltersOK || FilterList.GetNElements() == 0) + { + // return MatchJoins(); + return true; + } + + //if (!MatchSearches()) + //return false; + + ResultPtr = 0; + + Results resultTable[256]; + + Filter *filter = (Filter *)FilterList.GetHead(); + while (filter) + { + int op = filter->GetOp(); + if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY) + resultTable[ResultPtr++].SetFilter(filter); + else + switch (op) + { + case FILTER_AND: + if (ResultPtr > 1) + resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) && resultTable[ResultPtr-1].Calc(this); + ResultPtr--; + break; + case FILTER_OR: + if (ResultPtr > 1) + resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) || resultTable[ResultPtr-1].Calc(this); + ResultPtr--; + break; + case FILTER_NOT: + if (ResultPtr > 0) + resultTable[ResultPtr-1] = !resultTable[ResultPtr-1].Calc(this); + break; + } + filter = (Filter *)filter->GetNext(); + } + + if (ResultPtr != 1) // Should never happen, case already discarded by CheckFilters + { + FiltersOK = FALSE; + return TRUE; + } + + if (!resultTable[0].Calc(this)) return 0; + // return MatchJoins(); + //return FALSE; + return MatchSearches(); +} + +void Scanner::WalkFilters(FilterWalker callback, void *context) +{ + if (callback) + { + LinkedListEntry *entry = FilterList.GetHead(); + while (entry) + { + if (!callback(this, (Filter *)entry, context)) + break; + entry = entry->Next; + } + } +} + +void Scanner::Search(const wchar_t *search_string) +{ + // first, clear existing search terms + for ( StringField *l_string_field : search_strings ) + delete l_string_field; + + search_strings.clear(); + + if (search_string && *search_string == L'*' && search_string[1] == L' ') + { + search_any=true; + search_string += 2; + } + else + search_any=false; + + if (search_string) + { + while (search_string && *search_string) + { + while (search_string && (*search_string && (*search_string == ' ' || *search_string == '\t'))) + search_string++; + + const wchar_t *end=search_string; + + wchar_t c = *search_string; + if (c == L'\"') // a quoted string + { + end++; + search_string++; + while (end && *end && *end != '\"') + end++; + + if (search_string && *search_string) // make sure it's not just a quote by itself + { + if (*end == 0) // no terminating quotes + { + wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string); + search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING)); + } + else if (end > (search_string+1)) // at least one character in the quotes + { + wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1); + search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING)); + end++; + } + } + search_string=end; + } + else if (c) + { + while (end && *end && *end != ' ' && *end != '\t') + end++; + + wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1); + search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING)); + } + } + } +} + +void Scanner::WalkFields(Record::FieldsWalker callback, void *context) +{ + if (CurrentRecord) + CurrentRecord->WalkFields(callback, context); +}
\ No newline at end of file diff --git a/Src/nde/win/Scanner.h b/Src/nde/win/Scanner.h new file mode 100644 index 00000000..edd4dc64 --- /dev/null +++ b/Src/nde/win/Scanner.h @@ -0,0 +1,154 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience + --------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Scanner Class Prototypes + + --------------------------------------------------------------------------- */ + +#ifndef __SCANNER_H +#define __SCANNER_H + +#include <vector> +#include "record.h" +#include "Table.h" +#include "index.h" +#include <vector> +#include <set> +#ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> +#endif +class Table; +class Index; +#pragma warning( disable: 4251 ) +class Scanner; +class Record; +class Scanner : public LinkedListEntry +{ +public: + Record *GetRecord(int Idx); + Scanner(Table *parentTable); + void IndexModified(void); + Index *GetIndex() { return index; } + Index *index; // TODO: make protected + +protected: + ~Scanner(); + + Table *pTable; + BOOL iModified; + typedef std::vector<StringField *> SearchStrings; + SearchStrings search_strings; + typedef std::set<unsigned char> SearchFields; + SearchFields search_fields; + bool search_any; + + void GetCurrentRecord(void); + bool MatchFilters(void); + bool MatchSearches(); + bool MatchSearch(const SearchFields &fields, StringField *search_field); + //BOOL MatchJoins(void); + int CheckFilters(void); + void CacheLastLocate(int Id, int From, Field *field, Index *i, int j); + + static int Query_LookupToken(const wchar_t *token); + void Query_CleanUp(void); + void Query_SyntaxError(int c); +public: + static int Query_GetNextToken(const wchar_t *p, int *size, wchar_t **token, int tokentable=0); + static const wchar_t *Query_EatSpace(const wchar_t *p); + static wchar_t *Query_ProbeSpace(wchar_t *p); + static const wchar_t *Query_ProbeNonAlphaNum(const wchar_t *p); + static wchar_t *Query_ProbeAlphaNum(wchar_t *p); + static int Query_isControlChar(wchar_t p); + + BOOL Query(const wchar_t *query); + BOOL Query_Parse(const wchar_t *query); + + const wchar_t *GetLastQuery(); + +public://fucko: protected + LinkedList pstack; + wchar_t *token; + wchar_t *last_query; + int last_query_failed; + +protected: + Record *CurrentRecord; + int CurrentRecordIdx; + LinkedList FilterList; + Index *lastLocateIndex; + int lastLocateIdx; + Field *lastLocateFieldClone; + int lastLocateFrom; + int lastLocateId; + BOOL Edition; + int ResultPtr; + BOOL FiltersOK; + +public: + bool MatchFilter(Filter *filter); + typedef bool (*FilterWalker)(Scanner *scanner, Filter *filter, void *context); + void WalkFilters(FilterWalker walker, void *context); + + ColumnField *GetColumnByName(const wchar_t *FieldName); + ColumnField *GetColumnById(unsigned char id); + + Field *NewFieldByName(const wchar_t *fieldName, unsigned char Perm); + Field *NewFieldByType(unsigned char Type, unsigned char Id, unsigned char Perm); + Field *NewFieldById(unsigned char Id, unsigned char Perm); + void DeleteField(Field *field); + void DeleteFieldByName(const wchar_t *name); + void DeleteFieldById(unsigned char Id); + + void Cancel(void); + void Insert(void); + void Edit(void); + void Post(void); + void Delete(void); + + Field *GetFieldByName(const wchar_t *FieldName); + Field *GetFieldById(unsigned char Id); + + void First(int *killswitch=0); + void Last(int *killswitch=0); + int Next(int *killswitch=0); + int Previous(int *killswitch=0); + BOOL Eof(void); + BOOL Bof(void); + void New(void); + int GetRecordsCount(void); + void GetRecordById(int Id, BOOL checkFilters=TRUE); + int GetRecordId(void); + void Sync(void); + BOOL LocateByName(const wchar_t *column, int From, Field *field, int *nskip=NULL); + BOOL LocateById(int Id, int From, Field *field, int *nskip=NULL); + BOOL LocateByIdEx(int Id, int From, Field *field, int *nskip, int comp_mode); + + // Filters + int AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op); + int AddFilterById(unsigned char Id, Field *Data, unsigned char Op); + int AddFilterOp(unsigned char Op); + void RemoveFilters(void); + Filter *GetLastFilter(void); + + BOOL SetWorkingIndexByName(const wchar_t *desc); + BOOL SetWorkingIndexById(unsigned char Id); + + void Search(const wchar_t *search_string); + BOOL HasIndexChanged(void) { return iModified; } + void ClearDirtyBit(void); + float FragmentationLevel(void); + + void WalkFields(Record::FieldsWalker callback, void *context); + + Table *GetTable(); + int in_query_parser; + int disable_date_resolution; +}; + +#endif diff --git a/Src/nde/win/StringField.cpp b/Src/nde/win/StringField.cpp new file mode 100644 index 00000000..b6ae3eed --- /dev/null +++ b/Src/nde/win/StringField.cpp @@ -0,0 +1,341 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +StringField Class +Windows specific version + +Field data layout: +[2 bytes] string length (bytes) +[length bytes] String data. UTF-16 data will start with a BOM +--------------------------------------------------------------------------- */ + +#include "../nde.h" +#include "StringField.h" +#include "../../nu/AutoChar.h" +#include "../../nu/AutoWide.h" + +static wchar_t CharSwap(wchar_t value) +{ + return (value >> 8) | (value << 8); +} + +//--------------------------------------------------------------------------- +StringField::StringField(const wchar_t *Str, int strkind) +{ + InitField(); + Type = FIELD_STRING; + if (Str) + { + if (strkind == STRING_IS_WCHAR) + StringW = ndestring_wcsdup(Str); + else + { + StringW = const_cast<wchar_t *>(Str); + ndestring_retain(StringW); + } + } +} + +//--------------------------------------------------------------------------- +void StringField::InitField(void) +{ + Type = FIELD_STRING; + StringW = NULL; + optimized_the = 0; +} + +//--------------------------------------------------------------------------- +StringField::StringField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +StringField::~StringField() +{ + ndestring_release(StringW); + StringW=0; +} + +//--------------------------------------------------------------------------- +void StringField::ReadTypedData(const uint8_t *data, size_t len) +{ + unsigned short c; + + CHECK_SHORT(len); + c = (unsigned short)(data[0]|(data[1]<<8)); + data+=2; + if (c) + { + bool unicode=false; + bool reverseEndian=false; + if (c >= 2 // enough room for BOM + && (c % 2) == 0) // can't be unicode if it's not an even multiple of 2 + { + wchar_t BOM=0; + memcpy(&BOM, data, 2); + if (BOM == 0xFEFF) + { + data+=2; + c-=2; + unicode=true; + } + else if (BOM == 0xFFFE) + { + data+=2; + c-=2; + unicode=true; + reverseEndian=true; + } + } + + CHECK_BIN(len, c); + if (unicode) + { + ndestring_release(StringW); + StringW = ndestring_malloc(c+sizeof(wchar_t)); + + memcpy(StringW, data, c); + StringW[c/2]=0; + if (reverseEndian) + { + for (unsigned short i=0;i<c;i++) + StringW[i]=CharSwap(StringW[i]); + } + } + else + { + int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)data, c, 0, 0); + StringW = ndestring_malloc((len+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, (LPCSTR)data, c, StringW, len); + StringW[len]=0; + } + } +} + +//--------------------------------------------------------------------------- +void StringField::WriteTypedData(uint8_t *data, size_t len) +{ + int pos=0; + + if (StringW) + { + unsigned short c = (unsigned short)wcslen(StringW) * sizeof(wchar_t) + 2 /* for BOM */; + // write size + CHECK_SHORT(len); + PUT_SHORT(c); pos+=2; + + // write byte order mark + CHECK_BIN(len, 2); + wchar_t BOM = 0xFEFF; + PUT_BINARY(data, (uint8_t *)&BOM, 2, pos); + pos+=2; + c-=2; + + // write string + CHECK_BIN(len, c); + PUT_BINARY(data, (uint8_t *)StringW, c, pos); + } + else + { + CHECK_SHORT(len); + PUT_SHORT(0); /*pos+=2;*/ + } +} + +//--------------------------------------------------------------------------- +wchar_t *StringField::GetStringW(void) +{ + return StringW; +} + +//--------------------------------------------------------------------------- +void StringField::SetStringW(const wchar_t *Str) +{ + if (!Str) return; + + ndestring_release(StringW); + StringW = NULL; + StringW = ndestring_wcsdup(Str); + optimized_the=0; +} + +//--------------------------------------------------------------------------- +void StringField::SetNDEString(wchar_t *Str) +{ + if (!Str) return; + + // copy and then release, just in case we're copying into ourselves + wchar_t *oldStr = StringW; + StringW = Str; + ndestring_retain(StringW); + ndestring_release(oldStr); + optimized_the=0; +} + +//--------------------------------------------------------------------------- +size_t StringField::GetDataSize(void) +{ + if (StringW) + { + return wcslen(StringW)*2 +2 /*for BOM*/ + 2 /*for byte length*/; + } + else + { + return 2; + } +} + +//--------------------------------------------------------------------------- +int StringField::Compare(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + return mywcsicmp(GetStringW(), ((StringField*)Entry)->GetStringW()); +} + +//--------------------------------------------------------------------------- +int StringField::Starts(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + const wchar_t *p = ((StringField*)Entry)->GetStringW(); + const wchar_t *d = GetStringW(); + if (!d || !p) return 0; + return nde_wcsbegins(d, p); +} + +//--------------------------------------------------------------------------- +int StringField::Contains(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + const wchar_t *p = ((StringField*)Entry)->GetStringW(); + const wchar_t *d = GetStringW(); + if (!d || !p) return 0; + return nde_wcscontains(GetStringW(), ((StringField*)Entry)->GetStringW()); +} + +Field *StringField::Clone(Table *pTable) +{ + StringField *clone = new StringField(StringW, STRING_IS_NDESTRING); + clone->Pos = FIELD_CLONE; + clone->ID = ID; + clone->MaxSizeOnDisk = (uint32_t)GetDataSize(); + return clone; +} + +// todo: make configurable words to skip, as well as trailing whitespace removal +#define IsCharSpaceW(c) (c == L' ' || c == L'\t') +inline bool IsTheW(const wchar_t *str) { if (str && (str[0] == L't' || str[0] == L'T') && (str[1] == L'h' || str[1] == L'H') && (str[2] == L'e' || str[2] == L'E') && (str[3] == L' ')) return true; else return false; } +#define SKIP_THE_AND_WHITESPACEW(x) { wchar_t *save##x=(wchar_t*)x; while (IsCharSpaceW(*x) && *x) x++; if (IsTheW(x)) x+=4; while (IsCharSpaceW(*x)) x++; if (!*x) x=save##x; } + +bool StringField::ApplyFilter(Field *Data, int op) +{ + // TODO: maybe do this? + + if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY) + { + bool r = (op == FILTER_ISEMPTY); + if (!StringW) + return r; + + if (StringW && StringW[0] == 0) + return r; + + return !r; + } + // + bool r; + StringField *compField = (StringField *)Data; + + const wchar_t *p = compField->GetStringW(); + const wchar_t *d = GetStringW(); + if (!p) + p = L""; + if (!d) + d = L""; + + switch (op) + { + case FILTER_EQUALS: + r = !nde_wcsicmp(d, p); + break; + case FILTER_NOTEQUALS: + r = !!nde_wcsicmp(d, p); + break; + case FILTER_CONTAINS: + r = nde_wcscontains(d, p); + break; + case FILTER_NOTCONTAINS: + r = !nde_wcscontains(d, p); + break; + case FILTER_ABOVE: + r = (bool)(nde_wcsicmp(d, p) > 0); + break; + case FILTER_ABOVEOREQUAL: + r = (bool)(nde_wcsicmp(d, p) >= 0); + break; + case FILTER_BELOW: + r = (bool)(nde_wcsicmp(d, p) < 0); + break; + case FILTER_BELOWOREQUAL: + r = (bool)(nde_wcsicmp(d, p) <= 0); + break; + case FILTER_BEGINS: + r = nde_wcsbegins(d, p); + break; + case FILTER_ENDS: + r = nde_wcsends(d, p); + break; + case FILTER_LIKE: + + if (compField->optimized_the) + p = compField->optimized_the; + else + { + SKIP_THE_AND_WHITESPACEW(p); + compField->optimized_the = p; + } + + if (optimized_the) + d = optimized_the; + else + { + SKIP_THE_AND_WHITESPACEW(d); + optimized_the=d; + } + + r = (bool)(nde_wcsicmp(d, p) == 0); + break; + case FILTER_BEGINSLIKE: + + if (compField->optimized_the) + p = compField->optimized_the; + else + { + SKIP_THE_AND_WHITESPACEW(p); + compField->optimized_the = p; + } + + if (optimized_the) + d = optimized_the; + else + { + SKIP_THE_AND_WHITESPACEW(d); + optimized_the=d; + } + + r = nde_wcsbegins(d, p); + break; + default: + r = true; + break; + } + return r; +} diff --git a/Src/nde/win/StringField.h b/Src/nde/win/StringField.h new file mode 100644 index 00000000..722ba0c2 --- /dev/null +++ b/Src/nde/win/StringField.h @@ -0,0 +1,50 @@ +/* +--------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- +*/ + +/* +--------------------------------------------------------------------------- + +StringField Class Prototypes + +--------------------------------------------------------------------------- +*/ + +#ifndef __STRINGFIELD_H +#define __STRINGFIELD_H +#include "../NDEString.h" + +class StringField : public Field +{ +public: + StringField(); + ~StringField(); + + + StringField(const wchar_t *Str, int strkind=STRING_IS_WCHAR); + wchar_t *GetStringW(void); + void SetStringW(const wchar_t *Str); + void SetNDEString(wchar_t *Str); + +protected: + virtual void ReadTypedData(const uint8_t *, size_t len); + virtual void WriteTypedData(uint8_t *, size_t len); + virtual size_t GetDataSize(void); + virtual int Compare(Field *Entry); + virtual int Starts(Field *Entry); + virtual int Contains(Field *Entry); + + virtual bool ApplyFilter(Field *Data, int op); + virtual Field *Clone(Table *pTable); + void InitField(void); + + wchar_t *StringW; + const wchar_t *optimized_the; +}; + +#endif + diff --git a/Src/nde/win/Table.cpp b/Src/nde/win/Table.cpp new file mode 100644 index 00000000..0631ec27 --- /dev/null +++ b/Src/nde/win/Table.cpp @@ -0,0 +1,886 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Table Class + +--------------------------------------------------------------------------- */ +#include "../nde.h" +//#include <direct.h> +#include <stdio.h> +#include <string.h> +#include "../CRC.H" +#include <strsafe.h> + +const char *tSign="NDETABLE"; + +//--------------------------------------------------------------------------- +Table::Table( const wchar_t *TableName, const wchar_t *Idx, BOOL Create, Database *_db, BOOL _Cached ) : Scanner( this ) +{ + memset( column_ids, FIELD_UNKNOWN, 255 ); + + Cached = _Cached; + db = _db; + AutoCreate = Create; + Name = ndestring_wcsdup( TableName ); + IdxName = ndestring_wcsdup( Idx ); + + Init(); +} + +//--------------------------------------------------------------------------- +void Table::Init() +{ + numErrors = 0; + Scanners = new LinkedList(); + // benski> cut: Handle=NULL; + IdxHandle = NULL; + FieldsRecord = NULL; + IndexList = NULL; + GLocateUpToDate = FALSE; +} + +//--------------------------------------------------------------------------- +Table::~Table() +{ + Reset(); + if (Handle) // Reset doesn'l_data_type completely destroy Handle + Vfdestroy(Handle); + Handle = 0; + + ndestring_release(Name); + ndestring_release(IdxName); +} + +//--------------------------------------------------------------------------- +void Table::Reset() +{ + if ( IndexList ) + IndexList->Release(); + + IndexList = 0; + + if ( FieldsRecord ) + FieldsRecord->Release(); + + FieldsRecord = 0; + + delete Scanners; + Scanners = NULL; + + if ( Handle ) + Vfclose( Handle ); // close (but don'l_data_type destroy) to keep mutex open. + if ( IdxHandle ) + Vfdestroy( IdxHandle ); + + IdxHandle = NULL; + + for ( RowCache::iterator itr = rowCache.begin(); itr != rowCache.end(); ++itr ) + { + if ( itr->second ) + itr->second->Release(); + } + + rowCache.clear(); + + memset( column_ids, FIELD_UNKNOWN, 255 ); + columns_cached = false; +} + +struct IndexNewWalkerContext +{ + IndexNewWalkerContext(Table *_table) + { + N = -1; + table = _table; + } + int N; + Table *table; +}; + +bool Table::IndexNewWalker(IndexRecord *record, Field *entry, void *context_in) +{ + IndexNewWalkerContext *context = (IndexNewWalkerContext *)context_in; + IndexField *p = (IndexField *)entry; + p->index = new Index(context->table->IdxHandle, p->ID, context->N++, p->Type, FALSE, 0, context->table); + return true; +} + +//--------------------------------------------------------------------------- +BOOL Table::Open(void) +{ + int justcreated = 0; + + if (!Handle) + Handle = Vfnew(Name, "r+b", Cached); + if (!Handle) return FALSE; + if (!Vflock(Handle)) + { + Vfdestroy(Handle); + Handle = 0; + return FALSE; + } + + Handle = Vfopen(Handle, Name, "r+b", Cached); + IdxHandle = Vfopen(0, IdxName, "r+b", TRUE); + BOOL Valid = (Handle && IdxHandle); + + // unlock + if (Valid || !AutoCreate) + { + //if (Handle) + //Vfunlock(Handle); + } + else + { + if (Handle) + { + Vfclose(Handle); + if (IdxHandle) + Vfdestroy(IdxHandle); + IdxHandle = 0; + } + else + { + if (IdxHandle) + Vfdestroy(IdxHandle); + IdxHandle = 0; + Handle = Vfnew(Name, "w+b", Cached); + if (!Vflock(Handle)) + { + Vfdestroy(Handle); + return FALSE; + } + } + + Handle = Vfopen(Handle, Name, "w+b", Cached); + IdxHandle = Vfopen(0, IdxName, "w+b", TRUE); + Valid = (Handle && IdxHandle); + + if (Valid) + { + Vfwrite(__TABLE_SIGNATURE__, strlen(__TABLE_SIGNATURE__), Handle); + Vfwrite(__INDEX_SIGNATURE__, strlen(__TABLE_SIGNATURE__), IdxHandle); + // TODO bensk> change if NUM_SPECIAL_RECORDS ever increases + int v=NUM_SPECIAL_RECORDS;//strlen(__TABLE_SIGNATURE__); + Vfwrite(&v, sizeof(v), IdxHandle); + // v = 0; fwrite(&v, sizeof(v), 1, IdxHandle); + v = -1; Vfwrite(&v, sizeof(v), IdxHandle); // write ID + v = 0; + for (int i=0;i<NUM_SPECIAL_RECORDS;i++) + { + Vfwrite(&v, sizeof(v), IdxHandle); + Vfwrite(&v, sizeof(v), IdxHandle); + } + Sync(); + justcreated = 1; + } + } + + if (!Valid) + { + if (Handle) Vfdestroy(Handle); + if (IdxHandle) Vfdestroy(IdxHandle); + Handle = NULL; + IdxHandle = NULL; + } + else + { + char test1[9]={0}; + char test2[9]={0}; + + Vfseek(Handle, 0, SEEK_SET); + Vfread(test1, strlen(__TABLE_SIGNATURE__), Handle); + Vfseek(IdxHandle, 0, SEEK_SET); + Vfread(test2, strlen(__INDEX_SIGNATURE__), IdxHandle); + test1[8]=0; + test2[8]=0; + if (strcmp(test1, __TABLE_SIGNATURE__) || strcmp(test2, __INDEX_SIGNATURE__)) + { + if (Handle) Vfdestroy(Handle); + Handle = 0; + if (IdxHandle) Vfdestroy(IdxHandle); + IdxHandle = 0; + return FALSE; + } + + // Load default index + IndexField *l_index_field = new IndexField(PRIMARY_INDEX, -1, -1, L"None"); + l_index_field->index = new Index(IdxHandle, PRIMARY_INDEX, -1, -1, FALSE, 0, this); + + // Get indexes + int Ptr = l_index_field->index->Get(INDEX_RECORD_NUM); + IndexList = new IndexRecord(Ptr, INDEX_RECORD_NUM, Handle, this); + if (!IndexList) + { + delete l_index_field; + if (Handle) Vfdestroy(Handle); + Handle = 0; + if (IdxHandle) Vfdestroy(IdxHandle); + IdxHandle = 0; + return FALSE; + } + + // Init them + IndexNewWalkerContext newContext(this); + IndexList->WalkFields(IndexNewWalker, &newContext); + + // Add default in case its not there (if it is it won'l_data_type be added by addfield) + IndexList->AddField(l_index_field); + + // Get the default index (whether loaded or preloaded) + Scanner::index = ((IndexField*)IndexList->GetField(PRIMARY_INDEX))->index; + + // If it's different from preloaded, delete preloaded + if (l_index_field->index != Scanner::index) + { + delete l_index_field; + l_index_field = NULL; + } + + // Set up colaboration + IndexList->BuildCollaboration(); + + // Get columns + Ptr = Scanner::index->Get(FIELDS_RECORD_NUM); + FieldsRecord = new Record(Ptr, FIELDS_RECORD_NUM, Handle, this); + if (!FieldsRecord) + { + IndexList->Release(); + IndexList=0; + if (Handle) Vfdestroy(Handle); + Handle = 0; + if (IdxHandle) Vfdestroy(IdxHandle); + IdxHandle = 0; + return FALSE; + } + + // update the column cache + FieldsRecord->WalkFields(BuildColumnCache, this); + columns_cached=true; + } + + if (Valid && !justcreated) + { + if (IndexList->NeedFix()) + Compact(); + } + + if (Valid) First(); + if (Handle) + Vfunlock(Handle); + return Valid; +} + +//--------------------------------------------------------------------------- +void Table::Close(void) +{ + int v=0; + + if (Handle && IndexList && Vflock(Handle, 0)) + { + IndexList->WalkFields(IndexWriteWalker, 0); + } + + delete Scanners; + Scanners = NULL; + + Vsync(Handle); + if (IdxHandle) + { + Vfdestroy(IdxHandle); + IdxHandle = NULL; + v |= 2; + } + if (Handle) + { + Vfdestroy(Handle); + Handle = NULL; + v |= 1; + } + + if (v != 3) + return; +} + +bool Table::IndexWriteWalker(IndexRecord *record, Field *entry, void *context) +{ + IndexField *field = (IndexField *)entry; + field->index->WriteIndex(); + return true; +} + +//--------------------------------------------------------------------------- +void Table::Sync(void) +{ + if (!Vflock(Handle)) + return; + + if (IndexList) + IndexList->WalkFields(IndexWriteWalker, 0); + + int err=0; + if (!err && Handle) err|=Vsync(Handle); + if (!err && IdxHandle) Vsync(IdxHandle); + + Vfunlock(Handle); +} + +//--------------------------------------------------------------------------- +ColumnField *Table::NewColumn( unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, BOOL indexUnique ) +{ + columns_cached = false; // if they start writing new columns, kill the columns cache until they PostColumns() + ColumnField *l_column_field = GetColumnById( FieldID ); + if ( l_column_field ) + { + int l_data_type = l_column_field->GetDataType(); + if ( l_data_type != FieldType ) + { + OutputDebugStringW( L"column " ); + OutputDebugStringW( FieldName ); + OutputDebugStringW( L" already exists but is of the wrong type\n" ); + + if ( CompatibleFields( l_data_type, FieldType ) ) + { + OutputDebugStringW( L"going from one equivalent type to another, converting column\n" ); + l_column_field->SetDataType( FieldType ); + goto aok; + } + } + + l_column_field->SetFieldName( (wchar_t *)FieldName ); + return NULL; + } +aok: + if ( GetColumnByName( FieldName ) ) + return NULL; + + ColumnField *l_new_column_field = new ColumnField( FieldID, FieldName, FieldType, this ); + column_ids[ FieldID ] = FieldType; + FieldsRecord->AddField( l_new_column_field ); + + return l_new_column_field; +} + +void Table::SetFieldSearchableById(unsigned char field_id, bool searchable) +{ + ColumnField *column = GetColumnById(field_id); + if (column) + column->SetSearchable(searchable); + if (searchable) + { + search_fields.insert(field_id); + } + else + { + search_fields.erase(field_id); + } +} + +//--------------------------------------------------------------------------- +bool Table::BuildColumnCache(Record *record, Field *entry, void *context) +{ + Table *table = (Table *)context; + ColumnField *column = (ColumnField *)entry; + unsigned char field_id=column->GetFieldId(); + table->column_ids[field_id] = column->GetDataType(); + + if (column->IsSearchableField()) + { + table->search_fields.insert(field_id); + } + else + { + table->search_fields.erase(field_id); + } + return true; +} + +//--------------------------------------------------------------------------- +void Table::PostColumns(void) +{ + FieldsRecord->WriteFields(this, FIELDS_RECORD_NUM); + memset(column_ids, FIELD_UNKNOWN, 255); + FieldsRecord->WalkFields(BuildColumnCache, this); + columns_cached=true; +} + +unsigned char Table::GetColumnType(unsigned char Id) +{ + if (columns_cached) + { + return column_ids[Id]; + } + else + { + return GetColumnById(Id)->GetDataType(); + } +} + +//--------------------------------------------------------------------------- +IndexField *Table::GetIndexByName(const wchar_t *name) +{ + if (!IndexList) + return NULL; + return IndexList->GetIndexByName(name); +} + +//--------------------------------------------------------------------------- +IndexField *Table::GetIndexById(unsigned char Id) +{ + if (!IndexList) + return NULL; + return (IndexField *)IndexList->GetField(Id); +} + +//--------------------------------------------------------------------------- +void Table::AddIndexByName(const wchar_t *name, const wchar_t *desc) +{ + ColumnField *header = GetColumnByName(name); + if (header) + { + unsigned char Idx = header->ID; + AddIndexById(Idx, desc); + } +} + +//--------------------------------------------------------------------------- +void Table::AddIndexById( unsigned char Id, const wchar_t *desc ) +{ + if ( GetIndexById( Id ) ) + return; + + ColumnField *l_column_field = GetColumnById( Id ); + if ( !l_column_field ) + return; + + IndexField *l_new_index_field = new IndexField( Id, IndexList->GetColumnCount(), l_column_field->GetDataType(), desc ); + l_new_index_field->index = new Index( IdxHandle, Id, IndexList->GetColumnCount(), l_column_field->GetDataType(), TRUE, Scanner::index->NEntries, this ); + + IndexField* l_previous_index_field = dynamic_cast<IndexField*>(IndexList->GetLastField()); + + IndexList->AddField( l_new_index_field ); + + //IndexField* l_previous_index_field = (IndexField*)l_new_index_field->prev; + if (l_previous_index_field) + { + l_previous_index_field->index->Colaborate(l_new_index_field); + } + + IndexField *l_primary_index_field = (IndexField *)IndexList->GetField( PRIMARY_INDEX ); + if (l_primary_index_field) + { + l_new_index_field->index->Colaborate(l_primary_index_field); + } + + if (l_previous_index_field) + { + l_previous_index_field->index->Propagate(); + } + + IndexList->WriteFields( this ); +} + +//--------------------------------------------------------------------------- +BOOL Table::CheckIndexing(void) +{ + if (IndexList->GetColumnCount() == 0) return TRUE; + + for (int i=0;i<Scanner::index->NEntries;i++) + { + if (!IndexList->CheckIndexing(i)) + return FALSE; + } + return TRUE; +} + +struct IndexWalkerThunkContext +{ + void *context; + Table *_this; + Table::IndexWalker callback; +}; + +bool Table::IndexWalkerThunk(IndexRecord *record, Field *entry, void *context_in) +{ + IndexWalkerThunkContext *context = (IndexWalkerThunkContext *)context_in; + return context->callback(context->_this, (IndexField *)entry, context->context); +} + +//--------------------------------------------------------------------------- +void Table::WalkIndices(IndexWalker callback, void *context) +{ + if (IndexList && callback) + { + IndexWalkerThunkContext walkerContext = { context, this, callback }; + IndexList->WalkFields(IndexWalkerThunk, &walkerContext); + } +} + +//--------------------------------------------------------------------------- +void Table::DropIndex(IndexField *Ptr) +{ + if (!Ptr || Ptr->Type != FIELD_INDEX) return; + if (Scanner::index == Ptr->index) + { + Scanner::index = ((IndexField*)IndexList->GetField(PRIMARY_INDEX))->index; + IndexList->BuildCollaboration(); + } + IndexList->RemoveField(Ptr); + if (Scanner::index->SecIndex == Ptr) + Scanner::index->SecIndex = 0; + IndexList->WriteFields(this); +} + +//--------------------------------------------------------------------------- +void Table::DropIndexByName(const wchar_t *desc) +{ + IndexField *indx = GetIndexByName(desc); + if (!_wcsicmp(desc, L"None")) return; + + if (indx) + DropIndex(indx); +} + +//--------------------------------------------------------------------------- +void Table::DropIndexById(unsigned char Id) +{ + if (!IndexList) + return; + if (Id == (unsigned char)PRIMARY_INDEX) return; + IndexField *indx=(IndexField *)IndexList->GetField(Id); + if (indx) + DropIndex(indx); +} + +//--------------------------------------------------------------------------- +BOOL Table::LocateByIdEx(int Id, int From, Field *field, int comp_mode) +{ + return Scanner::LocateByIdEx(Id, From, field, NULL, comp_mode); +} + +//--------------------------------------------------------------------------- +Record *Table::GetColumns(void) +{ + if (!FieldsRecord) + return NULL; + return FieldsRecord; +} + +//--------------------------------------------------------------------------- +Scanner *Table::NewScanner() +{ + Scanner *s = new Scanner(this); + /*if (Scanners->GetNElements() > 0)*/ + s->index = Scanner::index; + Scanners->AddEntry(s, TRUE); + return s; +} + +//--------------------------------------------------------------------------- +Scanner *Table::GetDefaultScanner() +{ + return this; +} + +//--------------------------------------------------------------------------- +void Table::DeleteScanner(Scanner *scan) +{ + if (!scan) return; + Scanners->RemoveEntry(scan); +} + +//--------------------------------------------------------------------------- +void Table::IndexModified(void) +{ + Scanner *s = (Scanner *)Scanners->GetHead(); + while (s) + { + s->IndexModified(); + s = (Scanner *)s->GetNext(); + } +} + +//--------------------------------------------------------------------------- +void Table::SetGlobalLocateUpToDate(BOOL is) { + GLocateUpToDate = is; +} + +struct ColumnWalkContext +{ + Table *ctable; +}; + +bool Table::Compact_ColumnWalk(Record *record, Field *entry, void *context_in) +{ + ColumnField *field = static_cast<ColumnField *>(entry); + ColumnWalkContext *context = (ColumnWalkContext *)context_in; + Table *ctable = context->ctable; + + ctable->NewColumn(field->GetFieldId(), field->GetFieldName(), field->GetDataType(), FALSE); + return true; +} + +struct ColumnWalk2Context +{ + Table *ctable; + Table *thisTable; + uint8_t *data; + size_t data_size; + int gotstuff; +}; + +bool Table::Compact_ColumnWalk2(Record *record, Field *entry, void *context_in) +{ + ColumnField *colfield = (ColumnField *)entry; + ColumnWalk2Context *context = (ColumnWalk2Context *)context_in; + + unsigned char fieldid = colfield->GetFieldId(); + //char *fieldname = colfield->GetFieldName(); + Field *mfield = context->thisTable->GetFieldById(fieldid); + //Field *mfield = GetFieldByName(fieldname); + if (mfield != NULL) { + if (!context->gotstuff) { + context->ctable->New(); + context->gotstuff = 1; + } + Field *cfield = context->ctable->NewFieldById(fieldid, 0); + //Field *cfield = ctable->NewFieldByName(fieldname, mfield->GetPerm()); + size_t len = mfield->GetDataSize(); + if (len > context->data_size) + { + size_t old_data_size = context->data_size; + context->data_size = len; + uint8_t *new_data = (uint8_t *)realloc(context->data, context->data_size); + if (new_data) + { + context->data = new_data; + } + else + { + new_data = (uint8_t *)malloc(context->data_size); + if (new_data) + { + memcpy(new_data, context->data, old_data_size); + free(context->data); + context->data = new_data; + } + else + { + context->data_size = old_data_size; + return false; + } + } + } + mfield->WriteTypedData(context->data, len); + cfield->ReadTypedData(context->data, len); + } + + return true; +} + +bool Table::Compact_IndexWalk(Table *table, IndexField *field, void *context) +{ + Table *ctable = (Table *)context; + + if (_wcsicmp(field->GetIndexName(), L"None")) + ctable->AddIndexById(field->GetFieldId(), field->GetIndexName()); + return true; +} +//--------------------------------------------------------------------------- +void Table::Compact(int *progress) { + // ok so we're gonna be cheating a bit, instead of figuring out how to safely modify all those + // nifty indexes that cross reference themselves and blablabla, we're just gonna duplicate the + // whole table from scratch, overwrite ourselves, and reopen the table. duh. + + if (!Vflock(Handle)) + { + if (progress != NULL) *progress = 100; + return; + } + // create a temporary table in windows temp dir + wchar_t temp_table[MAX_PATH+12] = {0}; + wchar_t temp_index[MAX_PATH+12] = {0}; + wchar_t old_table[MAX_PATH+12] = {0}; + wchar_t old_index[MAX_PATH+12] = {0}; + DWORD pid=GetCurrentProcessId(); + + StringCbPrintfW(temp_table, sizeof(temp_table), L"%s.new%08X", Name,pid); + StringCbPrintfW(temp_index, sizeof(temp_index),L"%s.new%08X", IdxName,pid); + StringCbPrintfW(old_table, sizeof(old_table),L"%s.old%08X", Name,pid); + StringCbPrintfW(old_index, sizeof(old_index),L"%s.old%08X", IdxName,pid); + + // delete them, in case we crashed while packing + + DeleteFileW(temp_table); + DeleteFileW(temp_index); + DeleteFileW(old_table); + DeleteFileW(old_index); + + // create a brand new db and a brand new table + Table *ctable = db->OpenTable(temp_table, temp_index, NDE_OPEN_ALWAYS, Cached); + + // duplicate the columns + Record *record = GetColumns(); + if (record != NULL) + { + ColumnWalkContext context; + context.ctable = ctable; + record->WalkFields(Compact_ColumnWalk, &context); + } + ctable->PostColumns(); + + // duplicate the indexes + WalkIndices(Compact_IndexWalk, (void *)ctable); + + // duplicate the data + int reccount = GetRecordsCount(); + + int count = 0; + First(); + ColumnWalk2Context context; + context.data_size = 65536; + context.data = (uint8_t *)calloc(65536, sizeof(uint8_t)); + context.ctable = ctable; + context.thisTable = this; + + while (1) { + int lasterr = NumErrors(); + GetDefaultScanner()->GetRecordById(count, FALSE); + count++; + + if (Eof() || count > reccount) break; + + if (NumErrors() > lasterr) + continue; + + Index *idx = GetDefaultScanner()->GetIndex(); + int pos = idx->Get(GetDefaultScanner()->GetRecordId()); + + if (pos == 0) continue; + + int pr = (int)((float)GetRecordId()/(float)reccount*100.0f); + if (progress != NULL) *progress = pr; + context.gotstuff = 0; + + if (record != NULL) + record->WalkFields(Compact_ColumnWalk2, &context); + + if (context.gotstuff) + ctable->Post(); + } + free(context.data); + + // done creating temp table + db->CloseTable(ctable); + + // reset the data structures and close underlying file handles + Reset(); + + if (MoveFileW(Name,old_table)) + { + if (MoveFileW(IdxName,old_index)) + { + if (!MoveFileW(temp_table,Name) || !MoveFileW(temp_index,IdxName)) + { + // failed, try to copy back + DeleteFileW(Name); + DeleteFileW(IdxName); + MoveFileW(old_table,Name); // restore old file + MoveFileW(old_index,IdxName); // restore old file + } + } + else + { + MoveFileW(old_table,Name); // restore old file + } + } + + // clean up our temp files + DeleteFileW(temp_table); + DeleteFileW(temp_index); + DeleteFileW(old_table); + DeleteFileW(old_index); + + if (progress != NULL) *progress = 100; + + // reopen our table + Init(); + Open(); +} + +ColumnField *Table::GetColumnById(unsigned char Idx) +{ + if (!FieldsRecord) + return NULL; + return (ColumnField *)FieldsRecord->GetField(Idx); +} + +ColumnField *Table::GetColumnByName(const wchar_t *FieldName) +{ + return FieldsRecord->GetColumnByName(FieldName); +} + +void Table::RowCache_Delete(int position) +{ + if (use_row_cache) + { + RowCache::iterator found = rowCache.find(position); + if (found != rowCache.end()) + { + if (found->second) + found->second->Release(); + rowCache.erase(found); + } + } +} + +void Table::RowCache_Remove(int position) +{ + if (use_row_cache) + { + Record *&row = rowCache[position]; + if (row) + { + row->Release(); + } + + rowCache[position] = 0; + } +} + +void Table::RowCache_Add(Record *record, int position) +{ + if (use_row_cache) + { + record->Retain(); + + Record *&row = rowCache[position]; + if (row) + { + row->Release(); + } + + rowCache[position] = record; + } +} + +Record *Table::RowCache_Get(int position) +{ + if (!use_row_cache || 0 == rowCache.count(position)) + return 0; + + Record *row = rowCache[position]; + if (row) + row->Retain(); + return row; +} + +void Table::EnableRowCache() +{ + use_row_cache=true; +}
\ No newline at end of file diff --git a/Src/nde/win/Table.h b/Src/nde/win/Table.h new file mode 100644 index 00000000..02dc6990 --- /dev/null +++ b/Src/nde/win/Table.h @@ -0,0 +1,170 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Table Class Prototypes + Windows implementation +--------------------------------------------------------------------------- */ + +#ifndef __TABLE_H +#define __TABLE_H + +#include <stdio.h> +//#include <io.h> +#include "../Scanner.h" +#include <map> +#include "../IndexRecord.h" +#include <assert.h> + +class Table : private Scanner +{ +public: + // TODO: move these back to protected + VFILE *Handle = NULL; + using Scanner::index; + bool use_row_cache = false; + BOOL GLocateUpToDate = FALSE; + +private: + void Init(); + void Reset(); + +private: + LinkedList *Scanners; + +protected: + wchar_t *Name; + wchar_t *IdxName; + + VFILE *IdxHandle = NULL; + BOOL AutoCreate; + Record *FieldsRecord = NULL; + IndexRecord *IndexList = NULL; + Database *db; + BOOL Cached; + int numErrors = 0; + using Scanner::Edition; + bool columns_cached = false; + unsigned char column_ids[256]; + typedef std::map<int, Record*> RowCache; + RowCache rowCache; + + // Tables + static bool Compact_ColumnWalk(Record *record, Field *entry, void *context_in); + static bool Compact_ColumnWalk2(Record *record, Field *entry, void *context_in); + static bool Compact_IndexWalk(Table *table, IndexField *entry, void *context); + static bool IndexWriteWalker(IndexRecord *record, Field *entry, void *context); + static bool IndexWalkerThunk(IndexRecord *record, Field *entry, void *context); + static bool IndexNewWalker(IndexRecord *record, Field *entry, void *context); + static bool BuildColumnCache(Record *record, Field *entry, void *context); + +public: + typedef bool (*IndexWalker)(Table *table, IndexField *entry, void *context); + Table(const wchar_t *TableName, const wchar_t *IdxName, BOOL Create, Database *db, BOOL Cached); + ~Table(); + BOOL Open(void); + void Close(void); + + // Columns + ColumnField *NewColumn(unsigned char Id, const wchar_t *name, unsigned char type, BOOL indexUniques); + + void DeleteColumn(ColumnField *field); // todo + void DeleteColumnByName(const wchar_t *name); // todo + void DeleteColumnById(unsigned char Id); // todo + void PostColumns(void); + NDE_API Record *GetColumns(void); + ColumnField *GetColumnByName(const wchar_t *FieldName); + ColumnField *GetColumnById(unsigned char Idx); + unsigned char GetColumnType(unsigned char Id); + + // Fields + using Scanner::NewFieldByName; + using Scanner::NewFieldById; + using Scanner::GetFieldByName; + using Scanner::GetFieldById; + using Scanner::DeleteField; + using Scanner::DeleteFieldByName; + using Scanner::DeleteFieldById; + + // Records + using Scanner::First; + using Scanner::Last; + using Scanner::Next; + using Scanner::Previous; + using Scanner::Eof; + using Scanner::Bof; + using Scanner::New; + using Scanner::Insert; + using Scanner::Edit; + using Scanner::Cancel; + using Scanner::Post; + using Scanner::Delete; + using Scanner::GetRecordsCount; + using Scanner::GetRecordById; + using Scanner::GetRecordId; + void Sync(void); + using Scanner::LocateByName; + using Scanner::LocateById; + BOOL LocateByIdEx(int Id, int From, Field *field, int comp_mode); + + // Indexes + void AddIndexByName(const wchar_t *FieldName, const wchar_t *KeyName); + void AddIndexById(unsigned char Id, const wchar_t *KeyName); + + void WalkIndices(IndexWalker callback, void *context); + + IndexField *GetIndexByName(const wchar_t *name); + IndexField *GetIndexById(unsigned char Id); + using Scanner::SetWorkingIndexByName; + using Scanner::SetWorkingIndexById; + NDE_API BOOL CheckIndexing(void); + void DropIndexByName(const wchar_t *desc); + void DropIndexById(unsigned char Id); + void DropIndex(IndexField *Ptr); + void IndexModified(void); + + // Filters + using Scanner::AddFilterByName; + using Scanner::AddFilterById; + using Scanner::AddFilterOp; + using Scanner::RemoveFilters; + + // Scanners + Scanner *NewScanner(); + Scanner *GetDefaultScanner(); + void DeleteScanner(Scanner *scan); + + // Misc + using Scanner::FragmentationLevel; + void Compact(int *progress = NULL); + void SetGlobalLocateUpToDate(BOOL is); + + // Row Cache + void RowCache_Delete(int position); + void RowCache_Remove(int position); + void RowCache_Add(Record *record, int position); + Record *RowCache_Get(int position); + NDE_API void EnableRowCache(); + + // Searching + void SetFieldSearchableById(unsigned char field_id, bool searchable); + + int HasErrors() + { + return numErrors > 0; + } + int NumErrors() + { + return numErrors; + } + void IncErrorCount() + { + numErrors++; + } +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/win/Vfs.cpp b/Src/nde/win/Vfs.cpp new file mode 100644 index 00000000..3c5daf81 --- /dev/null +++ b/Src/nde/win/Vfs.cpp @@ -0,0 +1,599 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Virtual File System + +--------------------------------------------------------------------------- */ +#include "../nde.h" + +#include "vfs.h" +#include <malloc.h> +#ifndef EOF +#define EOF -1 +#endif +#include <Sddl.h> +#include <strsafe.h> + +#if defined(NDE_ALLOW_NONCACHED) +size_t ReadFileN(void *buffer, size_t size, VFILE *f) +{ + uint8_t *b = (uint8_t *) buffer; + size_t total_size=0; + while (size) + { + DWORD bytesRead = 0; + DWORD toRead = min(0xffffffffUL, size); + ReadFile(f->hfile, b, toRead, &bytesRead, NULL); + if (bytesRead != toRead) + { + f->endoffile=true; + // TODO: rewind + return total_size+bytesRead; + } + size-=toRead; + b+=toRead; + total_size+=toRead; + } + return total_size; +} + +size_t WriteFileN(void *buffer, size_t size, VFILE *f) +{ + uint8_t *b = (uint8_t *) buffer; + size_t total_size=0; + while (size) + { + DWORD bytesRead = 0; + DWORD toRead = min(0xffffffffUL, size); + WriteFile(f->hfile, b, toRead, &bytesRead, NULL); + if (bytesRead != toRead) + { + f->endoffile=true; + // TODO: rewind + return total_size+bytesRead; + } + size-=toRead; + b+=toRead; + total_size+=toRead; + } + + return total_size; +} +#endif + +// benski> i havn't the slightest fucking clue why this works, it's copypasta code from the internets +static LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)"; +static bool GetLowIntegrity(SECURITY_ATTRIBUTES *attributes) +{ + PSECURITY_DESCRIPTOR pSD = NULL; + + if ( ConvertStringSecurityDescriptorToSecurityDescriptorW ( + LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) ) + { + attributes->nLength = sizeof(SECURITY_ATTRIBUTES); + attributes->lpSecurityDescriptor = pSD; + attributes->bInheritHandle = FALSE; + + //LocalFree ( pSD ); + return true; + } + + return false; +} + +VFILE *Vfnew(const wchar_t *fl, const char *mode, BOOL Cached) +{ + if (!fl) return NULL; + VFILE *f = (VFILE *)calloc(1, sizeof(VFILE)); + if (!f) return NULL; + +#ifdef NDE_ALLOW_NONCACHED + if (!Cached) + { + f->r.reserve(256); // heuristically determined + } + f->hfile = INVALID_HANDLE_VALUE; +#endif + +#ifndef NO_TABLE_WIN32_LOCKING + // TODO: should we retrieve a better filename, e.g. GetLongPathName, or GetFinalPathNameByHandle on vista+ + wchar_t mutex_name[1024] = {0}; + StringCbPrintfW(mutex_name, sizeof(mutex_name), L"Global\\nde-%s", fl); + + CharLowerW(mutex_name+7); + wchar_t *sw = mutex_name+7; + wchar_t *has_extension=0; + while (sw && *sw) + { + if (*sw == L'\\') + { + has_extension=0; + *sw = L'/'; + } + else if (*sw == L'.') + has_extension=sw; + sw++; + } + if (has_extension) + *has_extension = 0; + + SECURITY_ATTRIBUTES attr = {0}; + if (GetLowIntegrity(&attr)) + { + f->mutex = CreateMutexW(&attr, FALSE, mutex_name); + LocalFree(attr.lpSecurityDescriptor); + } + else + f->mutex = CreateMutexW(0, FALSE, mutex_name); + +#endif + return f; +} + +//---------------------------------------------------------------------------- +VFILE *Vfopen(VFILE *f, wchar_t *fl, const char *mode, BOOL Cached) +{ + if (!fl) return NULL; + if (!f) + { + f = Vfnew(fl, mode, Cached); + if (!f) + return NULL; + } + +#ifdef NDE_ALLOW_NONCACHED + f->cached = Cached; +#else + f->cached = TRUE; +#endif + + if (!strchr(mode, '+')) + { + if (strchr(mode, 'r')) + f->mode = VFS_READ | VFS_MUSTEXIST; + if (strchr(mode, 'w')) + f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT; + if (strchr(mode, 'a')) + f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF; + } + else + { + if (strstr(mode, "r+")) + f->mode = VFS_WRITE | VFS_MUSTEXIST; + if (strstr(mode, "w+")) + f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT; + if (strstr(mode, "a+")) + f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF; + } + + if (f->mode == 0 || ((f->mode & VFS_READ) && (f->mode & VFS_WRITE))) + { + Vfdestroy(f); + return NULL; + } + +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + f->endoffile=false; + int readFlags=GENERIC_READ, openFlags=0; + if (f->mode & VFS_WRITE) readFlags|=GENERIC_WRITE; + if (f->mode & VFS_MUSTEXIST) openFlags=OPEN_EXISTING; + if (f->mode & VFS_CREATE) openFlags = OPEN_ALWAYS; + if (f->mode & VFS_NEWCONTENT) openFlags = CREATE_ALWAYS; + f->hfile=CreateFile(fl,readFlags,FILE_SHARE_READ,0,openFlags,0,0); + if (f->hfile!=INVALID_HANDLE_VALUE) + f->filename = _strdup(fl); + else + { + Vfdestroy(f); + return NULL; + } + return f; + } +#endif + + if (f->mode & VFS_MUSTEXIST) + { + if (GetFileAttributesW(fl) == INVALID_FILE_ATTRIBUTES) + { + Vfdestroy(f); + return NULL; + } + } + + if (!(f->mode & VFS_NEWCONTENT)) + { + int attempts=0; + HANDLE hFile=INVALID_HANDLE_VALUE; +again: + if (attempts<100) // we'll try for 10 seconds + { + hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ/*|FILE_SHARE_WRITE*/,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0); + if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION) + { + Sleep(100); // let's try again + goto again; + } + } + else if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION) + { + // screwed up STILL? eeergh I bet it's another program locking it, let's try with more sharing flags + hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0); + } + + if (hFile==INVALID_HANDLE_VALUE) + { + f->data = (uint8_t *)calloc(VFILE_INC, 1); + if (f->data == NULL) + { + Vfdestroy(f); + return NULL; + } + f->filesize = 0; + f->maxsize = VFILE_INC; + } + else + { + size_t fsize_ret_value=GetFileSize(hFile,NULL); + if (fsize_ret_value==INVALID_FILE_SIZE) + { + Vfdestroy(f); + return NULL; + } + f->filesize = (uint32_t)fsize_ret_value; + f->data = (uint8_t *)calloc(f->filesize, 1); + if (f->data == NULL) + { + CloseHandle(hFile); + Vfdestroy(f); + return NULL; + } + f->maxsize = f->filesize; + DWORD r = 0; + // TODO: benski> I think we should switch this to overlapped I/O (to allow I/O to happen as we're parsing) + // or switch to a memory mapped file... but we'll need to check with the profiler + if (!ReadFile(hFile,f->data,f->filesize,&r,NULL) || r != f->filesize) + { + CloseHandle(hFile); + Vfdestroy(f); + return NULL; + } + CloseHandle(hFile); + } + } + + if (f->mode & VFS_SEEKEOF) + f->ptr = f->filesize; + + f->filename = fl; + ndestring_retain(f->filename); + return f; +} + +//---------------------------------------------------------------------------- +void Vfclose(VFILE *f) +{ + if (!f) return; + +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + if (f->hfile!=INVALID_HANDLE_VALUE) + CloseHandle(f->hfile); + f->hfile=INVALID_HANDLE_VALUE; + } + else +#endif + { + if (f->mode & VFS_WRITE) + { + Vsync(f); + } + } + + ndestring_release(f->filename); + f->filename=0; + free(f->data); + f->data = 0; + f->ptr=0; + f->filesize=0; + f->maxsize=0; + f->dirty=0; +} + +void Vfdestroy(VFILE *f) +{ + // benski> TODO: + if (f) + { + Vfclose(f); + + while (f->locks) + Vfunlock(f, 1); + + if (f->mutex) CloseHandle(f->mutex); + free(f); + } +} + +//---------------------------------------------------------------------------- +size_t Vfread( void *ptr, size_t size, VFILE *f ) +{ + assert( ptr && f ); +#ifdef NDE_ALLOW_NONCACHED + if ( !f->cached ) + { + size_t read = f->r.read( ptr, size ); + ptr = (uint8_t *)ptr + read; + size -= read; + if ( size == 0 ) return 1; // yay fully buffered read + // if we got here, the ring buffer is empty + f->r.clear(); // reset back to normal + if ( size > f->r.avail() ) + { + return ReadFileN( ptr, size, f ) == size; + } + void *data = f->r.LockBuffer(); + size_t bytes_read = ReadFileN( data, f->r.avail(), f ); + f->r.UnlockBuffer( bytes_read ); + read = f->r.read( ptr, size ); + return read == size; + } +#endif + //if (!size) return 0; + if ( size + f->ptr > f->filesize ) + { + //FUCKO: remove this + if ( !( f->ptr < f->filesize ) ) + { +#ifdef _DEBUG + char buf[ 128 ] = { 0 }; + StringCbPrintfA( buf, sizeof( buf ), "NDE/VFS: VFS read at %d/%d (%d bytes) is bad\n", f->ptr, f->filesize, size ); + OutputDebugStringA( buf ); +#endif + + // if (!f->flushtable) // this would be ideal, if we could figure out f->flushtable + // f->flushtable=MessageBox(g_hwnd,"DB read failed, DB may be corrupted.\r\n\r\n" + // "Hit Retry to continue, or Cancel to clear the DB and start over","Winamp Library Error",MB_RETRYCANCEL) == IDCANCEL; //fucko + //MessageBox(g_hwnd,"DB read failed, DB may be corrupted. If this error persists, remove all files from the library.", + // "Winamp Library Error",MB_OK); + return 0; + } + + size = f->filesize - f->ptr; + } + + memcpy( ptr, f->data + f->ptr, size ); + f->ptr += (uint32_t)size; + + return 1; +} + +//---------------------------------------------------------------------------- +void Vfwrite(const void *ptr, size_t size, VFILE *f) +{ + if (!ptr || !f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + // TODO: with some cleverness we might be able to make this write to the read cache + + // if we're cached, then our file position is off and we need to adjust + if (!f->r.empty()) + Vfseek(f, -(f->r.size()), SEEK_CUR); + + f->r.clear(); + WriteFileN(ptr, size, f); + return; + } +#endif + f->dirty=1; + size_t s = (size); + if (s + f->ptr > f->maxsize) + { + // grow f->data,f->maxsize to be (s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1) + // instead of calling Vgrow again which gets kinda slow + size_t newsize=(s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1); + uint8_t *newdata=(uint8_t *)realloc(f->data,newsize); + if (newdata == NULL) return; + f->data = newdata; + memset(f->data+f->maxsize,0,newsize-f->maxsize); + f->maxsize=(uint32_t)newsize; + } + memcpy(f->data + f->ptr, ptr, s); + f->ptr += (uint32_t)s; + if (f->ptr > f->filesize) + f->filesize = f->ptr; +} + +//---------------------------------------------------------------------------- +void Vgrow(VFILE *f) +{ + if (!f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) return; +#endif + uint8_t *newdata=(uint8_t *)realloc(f->data, f->maxsize + VFILE_INC); + if (newdata == NULL) return; + f->data = newdata; + f->maxsize += VFILE_INC; +} + +//---------------------------------------------------------------------------- +uint32_t Vftell(VFILE *f) +{ + if (!f) return (unsigned)-1; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + return SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT); + } +#endif + return f->ptr; +} + +//---------------------------------------------------------------------------- +void Vfseek(VFILE *f, uint32_t i, int whence) +{ + if (!f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + if (whence == SEEK_CUR && i > 0 && i <f->r.size()) + { + f->r.advance(i); + } + else + { + f->r.clear(); + SetFilePointer(f->hfile, i, NULL, whence); + f->endoffile = false; + } + return; + } +#endif + switch (whence) + { + case SEEK_SET: + f->ptr = i; + break; + case SEEK_CUR: + f->ptr += i; + break; + case SEEK_END: + f->ptr = f->filesize+i; + break; + } +} + +//---------------------------------------------------------------------------- +int Vfeof(VFILE *f) +{ + if (!f) return -1; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + return !!f->endoffile; + } +#endif + return (f->ptr >= f->filesize); +} + +//---------------------------------------------------------------------------- +int Vsync(VFILE *f) +{ + if (!f) return 0; + if (!f->dirty) return 0; + + if (f->mode & VFS_WRITE) + { +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + LONG p = SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT); + CloseHandle(f->hfile); + f->hfile = CreateFileW(f->filename,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL); + if (f->hfile == INVALID_HANDLE_VALUE) + return 1; + SetFilePointer(f->hfile, p, NULL, SEEK_SET); + f->endoffile=false; + + return 0; + } +#endif + + wchar_t newfn[MAX_PATH] = {0}; + wchar_t oldfn[MAX_PATH] = {0}; + DWORD mypid=GetCurrentProcessId(); + + StringCchPrintfW(newfn, MAX_PATH, L"%s.n3w%08X",f->filename,mypid); + StringCchPrintfW(oldfn, MAX_PATH, L"%s.o1d%08X",f->filename,mypid); + + DeleteFileW(newfn); + DeleteFileW(oldfn); + + HANDLE hFile = CreateFileW(newfn,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,NULL); + int success=0; + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD o = 0; + if (WriteFile(hFile,f->data,f->filesize,&o,NULL) && o == f->filesize) success++; + CloseHandle(hFile); + } + if (!success) + { + DeleteFileW(newfn); + return 1; + } + + // TODO use this to keep a backup of the database file for edit fails, etc + if (MoveFileW(f->filename,oldfn) == 0) // if the function fails + { + CopyFileW(f->filename,oldfn, FALSE); + DeleteFileW(f->filename); + } + + int rv=0; + if (MoveFileW(newfn,f->filename) == 0 && CopyFileW(newfn,f->filename, FALSE) == 0) + { + MoveFileW(oldfn,f->filename); // restore old file + rv=1; + } + else + { + f->dirty=0; + } + + // clean up our temp files + DeleteFileW(oldfn); + DeleteFileW(newfn); + + return rv; + } + f->dirty=0; + return 0; +} + +// returns 0 on failure +int Vflock(VFILE *fl, BOOL is_sync) +{ +#ifndef NO_TABLE_WIN32_LOCKING + if (!fl) return 0; + if (!is_sync && fl->cached) + return 1; + // try for 10 seconds + if (fl->locks++ == 0) + { + if (WaitForSingleObject(fl->mutex, 10000) != WAIT_OBJECT_0) + { + fl->locks--; + return 0; + } + } +#endif + return 1; +} + +void Vfunlock(VFILE *fl, BOOL is_sync) +{ +#ifndef NO_TABLE_WIN32_LOCKING + if (!is_sync && fl->cached) + return; + + if (fl && fl->locks == 0) + DebugBreak(); + if (--fl->locks == 0) + { + if (fl && fl->mutex) + { + ReleaseMutex(fl->mutex); + } + } +#endif +}
\ No newline at end of file diff --git a/Src/nde/win/Vfs.h b/Src/nde/win/Vfs.h new file mode 100644 index 00000000..50490b83 --- /dev/null +++ b/Src/nde/win/Vfs.h @@ -0,0 +1,98 @@ +#ifndef __NDE_VFS_H +#define __NDE_VFS_H + +#include <bfc/platform/types.h> +#include <windows.h> + +//#define NDE_ALLOW_NONCACHED + +/* +#ifdef NDE_ALLOW_NONCACHED + #ifndef NDE_NOWIN32FILEIO + #error NDE_ALLOW_NONCACHED at least for now requires NDE_NOWIN32FILEIO + #endif +#endif +*/ + +#define VFILE_INC 65536 + +#define VFS_READ 1 +#define VFS_WRITE 2 +#define VFS_SEEKEOF 4 +#define VFS_CREATE 8 +#define VFS_NEWCONTENT 16 +#define VFS_MUSTEXIST 32 +#if defined(NDE_ALLOW_NONCACHED) +#include "../nu/RingBuffer.h" +#endif +typedef struct VFILEStruct +{ + void VFILE() + { + data = 0; + ptr = 0; + filesize = 0; + maxsize = 0; + filename = 0; + mode = 0; + cached = FALSE; + dirty = 0; + + #ifdef NDE_ALLOW_NONCACHED + #ifdef NDE_NOWIN32FILEIO + rfile = 0; + #else + hfile = NULL; + endoffile = false; + r = 0; + #endif + #endif + + mutex = NULL; + locks = 0; + } + + uint8_t *data; + uint32_t ptr; + uint32_t filesize; + uint32_t maxsize; + wchar_t *filename; + char mode; + BOOL cached; + int dirty; + +#ifdef NDE_ALLOW_NONCACHED + #ifdef NDE_NOWIN32FILEIO + FILE *rfile; + #else + HANDLE hfile; + bool endoffile; + RingBuffer r; + #endif +#endif + HANDLE mutex; + int locks; +} VFILE; + +#ifdef __cplusplus +extern "C" { +#endif + +VFILE *Vfnew(const wchar_t *filename, const char *mode, BOOL Cached); +VFILE *Vfopen(VFILE *f, wchar_t *filename, const char *mode, BOOL Cached); // filename must be an NDE string +size_t Vfread(void *ptr, size_t size, VFILE *buf); +void Vfseek(VFILE *fl, uint32_t i, int whence); +uint32_t Vftell(VFILE *fl); +void Vfclose(VFILE *fl); +void Vfdestroy(VFILE *fl); // benski> TODO: +void Vfwrite(const void *ptr, size_t size, VFILE *f); +int Vfeof(VFILE *fl); +int Vsync(VFILE *fl); // 1 on error updating +int Vflock(VFILE *fl, BOOL is_sync=TRUE); // returns 0 on failure +void Vfunlock(VFILE *fl, BOOL is_sync=TRUE); + +#ifdef __cplusplus +} +#endif + +#endif
\ No newline at end of file diff --git a/Src/nde/win/nde_c.cpp b/Src/nde/win/nde_c.cpp new file mode 100644 index 00000000..aee577c5 --- /dev/null +++ b/Src/nde/win/nde_c.cpp @@ -0,0 +1,561 @@ +#include "nde_c.h" +#include "../nde.h" +#include "../../nu/AutoCharFn.h" +#include "../../nu/AutoWide.h" + +/* Database */ +nde_database_t NDE_CreateDatabase(HINSTANCE hInstance) +{ + return (nde_database_t)new Database(hInstance); +} + +void NDE_DestroyDatabase(nde_database_t db) +{ + delete (Database *)db; +} + +nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *indexname, int create, int cache) +{ + Database *database = (Database *)db; + if (database && filename) + return (nde_table_t)database->OpenTable(filename, indexname, (BOOL)create, (BOOL)cache); + else + return 0; +} + +void NDE_Database_CloseTable(nde_database_t db, nde_table_t t) +{ + Database *database = (Database *)db; + Table *table = (Table *)t; + if (database && table) + { + database->CloseTable(table); + } +} +/* Table */ + +nde_field_t NDE_Table_NewColumn(nde_table_t t, unsigned char id, const char *name, unsigned char type) +{ + Table *table = (Table *)t; + if (table) + return (nde_field_t)table->NewColumn(id, AutoWideDup(name), type, FALSE); + else + return 0; +} + +nde_field_t NDE_Table_NewColumnW(nde_table_t t, unsigned char id, const wchar_t *name, unsigned char type) +{ + Table *table = (Table *)t; + if (table) + return (nde_field_t)table->NewColumn(id, name, type, FALSE); + else + return 0; +} + +void NDE_Table_PostColumns(nde_table_t t) +{ + Table *table = (Table *)t; + if (table) + table->PostColumns(); +} + +void NDE_Table_AddIndexByID(nde_table_t t, unsigned char id, const char *name) +{ + Table *table = (Table *)t; + if (table) + table->AddIndexById(id, AutoWide(name)); +} + +void NDE_Table_AddIndexByIDW(nde_table_t t, unsigned char id, const wchar_t *name) +{ + Table *table = (Table *)t; + if (table) + table->AddIndexById(id, name); +} + +nde_scanner_t NDE_Table_CreateScanner(nde_table_t t) +{ + Table *table = (Table *)t; + if (table) + return (nde_scanner_t)table->NewScanner(); + else + return 0; +} + +void NDE_Table_DestroyScanner(nde_table_t t, nde_scanner_t s) +{ + Table *table = (Table *)t; + Scanner *scanner = (Scanner *)s; + if (table && scanner) + table->DeleteScanner(scanner); +} + +void NDE_Table_Sync(nde_table_t t) +{ + Table *table = (Table *)t; + if (table) + table->Sync(); +} + +void NDE_Table_Compact(nde_table_t t, int *progress) +{ + Table *table = (Table *)t; + if (table) + table->Compact(progress); +} + +int NDE_Table_GetRecordsCount(nde_table_t t) +{ + Table *table = (Table *)t; + if (table) + return table->GetRecordsCount(); + else + return 0; +} + +nde_field_t NDE_Table_GetColumnByID(nde_table_t t, unsigned char id) +{ + Table *table = (Table *)t; + if (table) + return (nde_field_t)table->GetColumnById(id); + else + return 0; +} + +nde_field_t NDE_Table_GetColumnByName(nde_table_t t, const char *name) +{ + Table *table = (Table *)t; + if (table && name) + return (nde_field_t)table->GetColumnByName(AutoWide(name)); + else + return 0; +} + +void NDE_Table_SetColumnSearchableByID(nde_table_t t, unsigned char id, int searchable) +{ + Table *table = (Table *)t; + if (table) + table->SetFieldSearchableById(id, !!searchable); +} + +/* Scanner */ +int NDE_Scanner_Query(nde_scanner_t s, const wchar_t *query) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->Query(query); + else + return 0; +} + +void NDE_Scanner_Search(nde_scanner_t s, const wchar_t *search_term) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->Search(search_term); +} + +const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->GetLastQuery(); + else + return 0; +} + +int NDE_Scanner_GetRecordsCount(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->GetRecordsCount(); + else + return 0; +} + +void NDE_Scanner_New(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->New(); +} + +void NDE_Scanner_Post(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->Post(); +} + +void NDE_Scanner_First(nde_scanner_t s, int *killswitch) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->First(killswitch); +} + +void NDE_Scanner_Delete(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->Delete(); +} + +void NDE_Scanner_Edit(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->Edit(); +} + +int NDE_Scanner_EOF(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->Eof(); + else + return 1; +} + +int NDE_Scanner_BOF(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->Bof(); + else + return 1; +} + +nde_field_t NDE_Scanner_NewFieldByID(nde_scanner_t s, unsigned char id) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return (nde_field_t)scanner->NewFieldById(id, PERM_READWRITE); + else + return 0; +} + +nde_field_t NDE_Scanner_NewFieldByType(nde_scanner_t s, unsigned char type, unsigned char id) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return (nde_field_t)scanner->NewFieldByType(type, id, PERM_READWRITE); + else + return 0; +} + +nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t s, const char *name) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return (nde_field_t)scanner->NewFieldByName(AutoWide(name), PERM_READWRITE); + else + return 0; +} + +nde_field_t NDE_Scanner_GetFieldByID(nde_scanner_t s, unsigned char id) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return (nde_field_t)scanner->GetFieldById(id); + else + return 0; +} + +nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t s, const char *name) +{ + Scanner *scanner = (Scanner *)s; + if (scanner && name) + return (nde_field_t)scanner->GetFieldByName(AutoWide(name)); + else + return 0; +} + +void NDE_Scanner_AddFilterByID(nde_scanner_t s, unsigned char id, nde_field_t f, unsigned char filter_operation) +{ + Scanner *scanner = (Scanner *)s; + Field *field = (Field *)f; + if (scanner && field) + scanner->AddFilterById(id, field, filter_operation); +} + +int NDE_Scanner_LocateInteger(nde_scanner_t s, unsigned char id, int from, int value, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + { + IntegerField field(value); + return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode); + } + else + return 0; +} + +int NDE_Scanner_LocateString(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + { + StringField field(value); + return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode); + } + else + return 0; +} + + +int NDE_Scanner_LocateNDEString(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + { + StringField field(value, STRING_IS_NDESTRING); + return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode); + } + else + return 0; +} + +int NDE_Scanner_LocateFilename(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + { + FilenameField field(value); + return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode); + } + else + return 0; +} + +int NDE_Scanner_LocateNDEFilename(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + { + FilenameField field(value, STRING_IS_NDESTRING); + return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode); + } + else + return 0; +} + +int NDE_Scanner_LocateField(nde_scanner_t s, unsigned char id, int from, nde_field_t f, int *nskip, int compare_mode) +{ + Scanner *scanner = (Scanner *)s; + Field *field = (Field *)f; + if (scanner && field) + { + return scanner->LocateByIdEx(id, from, field, nskip, compare_mode); + } + else + return 0; +} + +void NDE_Scanner_DeleteField(nde_scanner_t s, nde_field_t f) +{ + Scanner *scanner = (Scanner *)s; + Field *field = (Field *)f; + if (scanner && field) + { + scanner->DeleteField(field); + } +} + +void NDE_Scanner_RemoveFilters(nde_scanner_t s) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->RemoveFilters(); +} + +int NDE_Scanner_Next(nde_scanner_t s, int *killswitch) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->Next(killswitch); + else + return 0; +} + +void NDE_Scanner_WalkFields(nde_scanner_t s, FieldEnumerator enumerator, void *context) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->WalkFields((Record::FieldsWalker)enumerator, context); +} + +/* Filter functions */ +unsigned char NDE_Filter_GetID(nde_filter_t f) +{ + Filter *filter = (Filter *)f; + if (filter) + return filter->GetId(); + else + return FILTERS_INVALID; // right value but I'm not sure if it's the best constant name to use +} + +NDE_API unsigned char NDE_Filter_GetOp(nde_filter_t f) +{ + Filter *filter = (Filter *)f; + if (filter) + return filter->GetOp(); + else + return FILTERS_INVALID; // right value but I'm not sure if it's the best constant name to use +} + +NDE_API nde_field_t NDE_Filter_GetData(nde_filter_t f) +{ + Filter *filter = (Filter *)f; + if (filter) + return (nde_field_t)filter->Data(); + else + return 0; +} + +/* Field functions */ +unsigned char NDE_Field_GetType(nde_field_t f) +{ + Field *field = (Field *)f; + if (field) + return field->GetType(); + else + return FIELD_UNKNOWN; +} + +unsigned char NDE_Field_GetID(nde_field_t f) +{ + Field *field = (Field *)f; + if (field) + return field->GetFieldId(); + else + return FIELD_UNKNOWN; +} + +/* StringField functions */ + +void NDE_StringField_SetNDEString(nde_field_t f, wchar_t *nde_string) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + field->SetNDEString(nde_string); +} + +wchar_t *NDE_StringField_GetString(nde_field_t f) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + return field->GetStringW(); + else + return 0; +} + +void NDE_StringField_SetString(nde_field_t f, const wchar_t *str) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + field->SetStringW(str); +} + +/* IntegerField functions */ +void NDE_IntegerField_SetValue(nde_field_t f, int value) +{ + IntegerField *field = (IntegerField *)(Field *)f; + if (field) + field->SetValue(value); +} + +int NDE_IntegerField_GetValue(nde_field_t f) +{ + IntegerField *field = (IntegerField *)(Field *)f; + if (field) + return field->GetValue(); + else + return 0; +} + +nde_field_t NDE_IntegerField_Create(int value) +{ + return (nde_field_t)new IntegerField(value); +} + +/* Int64Field functions */ +void NDE_Int64Field_SetValue(nde_field_t f, __int64 value) +{ + Int64Field *field = (Int64Field *)(Field *)f; + if (field) + field->SetValue(value); +} + +__int64 NDE_Int64Field_GetValue(nde_field_t f) +{ + Int64Field *field = (Int64Field *)(Field *)f; + if (field) + return field->GetValue(); + else + return 0; +} + +nde_field_t NDE_Int64Field_Create(__int64 value) +{ + return (nde_field_t)new Int64Field(value); +} + +/* BinaryField */ +void *NDE_BinaryField_GetData(nde_field_t f, size_t *length) +{ + BinaryField *field = (BinaryField *)(Field *)f; + if (field) + return (void *)field->GetData(length); + else + return 0; +} +void NDE_BinaryField_SetData(nde_field_t f, const void *data, size_t length) +{ + BinaryField *field = (BinaryField *)(Field *)f; + if (field) + field->SetData((const uint8_t *)data, length); +} + +/* Int128Field */ +void NDE_Int128Field_SetValue(nde_field_t f, const void *value) +{ + Int128Field *field = (Int128Field *)(Field *)f; + if (field && value) + field->SetValue(value); +} + +/* ColumnField */ +const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t f) +{ + ColumnField *field = (ColumnField *)(Field *)f; + if (field) + return field->GetFieldName(); + else + return 0; +} + +unsigned char NDE_ColumnField_GetDataType(nde_field_t f) +{ + ColumnField *field = (ColumnField *)(Field *)f; + if (field) + return field->GetDataType(); + else + return FIELD_UNKNOWN; +} + +unsigned char NDE_ColumnField_GetFieldID(nde_field_t f) +{ + ColumnField *field = (ColumnField *)(Field *)f; + if (field) + return field->GetFieldId(); + else + return FIELD_UNKNOWN; +} + +__time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp) +{ + IntegerField f((int)value); + f.ApplyConversion(str, tp); + return f.GetValue(); +}
\ No newline at end of file diff --git a/Src/nde/win/nde_c.h b/Src/nde/win/nde_c.h new file mode 100644 index 00000000..a7faae66 --- /dev/null +++ b/Src/nde/win/nde_c.h @@ -0,0 +1,131 @@ +#pragma once +/* C style API. + +We'll eventually deprecate the C++ API as it presents a lot of linking challenges +*/ +#include "../nde_defines.h" +#include "../NDEString.h" +#include <bfc/platform/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct nde_database_struct_t { } *nde_database_t; + typedef struct nde_table_struct_t { } *nde_table_t; + typedef struct nde_scanner_t_struct_t { } *nde_scanner_t; + typedef struct nde_field_t_struct_t { } *nde_field_t; + typedef struct nde_filter_struct_t { } *nde_filter_t; + + /* Database functions */ + + // Windows API +#ifdef __cplusplus +#define NDE_DEFAULT_PARAMETER(x) =x +#else +#define NDE_DEFAULT_PARAMETER(x) +#endif + +NDE_API void NDE_Init(); +NDE_API void NDE_Quit(); +NDE_API nde_database_t NDE_CreateDatabase(HINSTANCE hInstance NDE_DEFAULT_PARAMETER(0)); +NDE_API void NDE_DestroyDatabase(nde_database_t db); +NDE_API nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *filename_index, int create, int cache); + +NDE_API void NDE_Database_CloseTable(nde_database_t db, nde_table_t table); + +/* Table functions */ +NDE_API nde_field_t NDE_Table_NewColumn(nde_table_t table, unsigned char id, const char *name, unsigned char type); +NDE_API nde_field_t NDE_Table_NewColumnW(nde_table_t table, unsigned char id, const wchar_t *name, unsigned char type); +NDE_API void NDE_Table_PostColumns(nde_table_t table); +NDE_API void NDE_Table_AddIndexByID(nde_table_t table, unsigned char id, const char *name); +NDE_API void NDE_Table_AddIndexByIDW(nde_table_t table, unsigned char id, const wchar_t *name); +NDE_API nde_scanner_t NDE_Table_CreateScanner(nde_table_t table); +NDE_API void NDE_Table_DestroyScanner(nde_table_t table, nde_scanner_t scanner); +NDE_API void NDE_Table_Sync(nde_table_t table); +NDE_API void NDE_Table_Compact(nde_table_t table, int *progress NDE_DEFAULT_PARAMETER(0)); +NDE_API int NDE_Table_GetRecordsCount(nde_table_t table); +NDE_API nde_field_t NDE_Table_GetColumnByID(nde_table_t table, unsigned char id); + +NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, const char *name); + +NDE_API void NDE_Table_SetColumnSearchableByID(nde_table_t table, unsigned char id, int searchable); + +/* Scanner functions */ +NDE_API int NDE_Scanner_Query(nde_scanner_t scanner, const wchar_t *query); +NDE_API void NDE_Scanner_Search(nde_scanner_t scanner, const wchar_t *search_term); +NDE_API const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t scanner); +NDE_API int NDE_Scanner_GetRecordsCount(nde_scanner_t scanner); +NDE_API void NDE_Scanner_New(nde_scanner_t scanner); +NDE_API void NDE_Scanner_Post(nde_scanner_t scanner); +NDE_API void NDE_Scanner_First(nde_scanner_t scanner, int *killswitch NDE_DEFAULT_PARAMETER(0)); +NDE_API int NDE_Scanner_Next(nde_scanner_t scanner, int *killswitch NDE_DEFAULT_PARAMETER(0)); +NDE_API void NDE_Scanner_Delete(nde_scanner_t scanner); +NDE_API void NDE_Scanner_Edit(nde_scanner_t scanner); +NDE_API int NDE_Scanner_EOF(nde_scanner_t scanner); +NDE_API int NDE_Scanner_BOF(nde_scanner_t scanner); +NDE_API nde_field_t NDE_Scanner_NewFieldByID(nde_scanner_t scanner, unsigned char id); +NDE_API nde_field_t NDE_Scanner_NewFieldByType(nde_scanner_t s, unsigned char type, unsigned char id); +NDE_API nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t scanner, const char *name); +NDE_API nde_field_t NDE_Scanner_GetFieldByID(nde_scanner_t scanner, unsigned char id); +NDE_API nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t scanner, const char *name); +NDE_API void NDE_Scanner_AddFilterByID(nde_scanner_t scanner, unsigned char id, nde_field_t field, unsigned char filter_operation); + +NDE_API int NDE_Scanner_LocateInteger(nde_scanner_t scanner, unsigned char id, int from, int value, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); +NDE_API int NDE_Scanner_LocateString(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); +NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); +NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); +NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); +NDE_API int NDE_Scanner_LocateField(nde_scanner_t scanner, unsigned char id, int from, nde_field_t field, + int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT)); + +NDE_API void NDE_Scanner_DeleteField(nde_scanner_t scanner, nde_field_t field); +NDE_API void NDE_Scanner_RemoveFilters(nde_scanner_t scanner); +typedef int (__cdecl *FieldEnumerator)(void *record, nde_field_t field, void *context); +NDE_API void NDE_Scanner_WalkFields(nde_scanner_t scanner, FieldEnumerator enumerator, void *context); +/* Filter functions */ +NDE_API unsigned char NDE_Filter_GetID(nde_filter_t filter); +NDE_API unsigned char NDE_Filter_GetOp(nde_filter_t filter); +NDE_API nde_field_t NDE_Filter_GetData(nde_filter_t filter); + +/* Field functions */ +NDE_API unsigned char NDE_Field_GetType(nde_field_t field); +NDE_API unsigned char NDE_Field_GetID(nde_field_t field); + +/* String Field functions */ +NDE_API wchar_t *NDE_StringField_GetString(nde_field_t field); /* returns non-const because it's an NDE string (reference counted, see ndestring.h) */ +NDE_API void NDE_StringField_SetNDEString(nde_field_t field, wchar_t *nde_string); +NDE_API void NDE_StringField_SetString(nde_field_t field, const wchar_t *str); + +/* IntegerField functions */ +NDE_API void NDE_IntegerField_SetValue(nde_field_t field, int value); +NDE_API int NDE_IntegerField_GetValue(nde_field_t field); +NDE_API nde_field_t NDE_IntegerField_Create(int value); /* mainly used for NDE_Scanner_AddFilterByID */ + +/* Int64Field functions */ +NDE_API void NDE_Int64Field_SetValue(nde_field_t field, __int64 value); +NDE_API __int64 NDE_Int64Field_GetValue(nde_field_t field); +NDE_API nde_field_t NDE_Int64Field_Create(__int64 value); + +/* BinaryField functions */ +// on windows, the data pointer is optionally reference counted via ndestring (ndestring_retain if you plan on keeping it) +NDE_API void *NDE_BinaryField_GetData(nde_field_t field, size_t *length); +NDE_API void NDE_BinaryField_SetData(nde_field_t field, const void *data, size_t length); + +/* Int128Field functions */ +NDE_API void NDE_Int128Field_SetValue(nde_field_t field, const void *value); + +/* ColumnField functions */ +NDE_API const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t field); +NDE_API unsigned char NDE_ColumnField_GetDataType(nde_field_t field); +NDE_API unsigned char NDE_ColumnField_GetFieldID(nde_field_t); + +NDE_API __time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp); + +#ifdef __cplusplus +} +#endif
\ No newline at end of file diff --git a/Src/nde/win/nde_init.cpp b/Src/nde/win/nde_init.cpp new file mode 100644 index 00000000..9dfc31ee --- /dev/null +++ b/Src/nde/win/nde_init.cpp @@ -0,0 +1,36 @@ +#include "../nde_c.h" +#include "../DBUtils.h" + +#include <atomic> + +extern "C" void NDE_HeapInit(); +extern "C" void NDE_HeapQuit(); +static volatile std::atomic<std::size_t> _init_count = 0; + +/* NDE_Init isn't thread safe, be aware +best to call on the main thread during initialization +*/ +void NDE_Init() +{ + if ( _init_count.load() == 0 ) + { + NDE_HeapInit(); + HMODULE klib = LoadLibraryW( L"Kernel32.dll" ); + if ( klib ) + { + void *nls = GetProcAddress( klib, "FindNLSString" ); + if ( nls ) + *( (void **)&findNLSString ) = nls; + } + + FreeModule( klib ); + } + + _init_count.fetch_add( 1 ); +} + +void NDE_Quit() +{ + if ( _init_count.fetch_sub( 1 ) == 0 ) + NDE_HeapQuit(); +}
\ No newline at end of file |