diff options
Diffstat (limited to 'Src/nde/android')
29 files changed, 7250 insertions, 0 deletions
diff --git a/Src/nde/android/Binary32Field.cpp b/Src/nde/android/Binary32Field.cpp new file mode 100644 index 00000000..d633e170 --- /dev/null +++ b/Src/nde/android/Binary32Field.cpp @@ -0,0 +1,79 @@ +/* --------------------------------------------------------------------------- + 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, len) +{ + InitField(); +} + +//--------------------------------------------------------------------------- +void Binary32Field::InitField(void) +{ + Type = FIELD_BINARY32; +} + +//--------------------------------------------------------------------------- +Binary32Field::Binary32Field() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +void Binary32Field::ReadTypedData(const uint8_t *data, size_t len) +{ + uint32_t c; + size_t pos = 0; + + CHECK_INT(len); //len-=4; + c = GET_INT(); pos += 4; + if (c && c<=len) + { + Size = c; + ndestring_release((ndestring_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; +} diff --git a/Src/nde/android/Binary32Field.h b/Src/nde/android/Binary32Field.h new file mode 100644 index 00000000..88b72277 --- /dev/null +++ b/Src/nde/android/Binary32Field.h @@ -0,0 +1,33 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Binary32Field Class Prototypes + + Android (linux) implementation +--------------------------------------------------------------------------- */ + +#ifndef __NDE_BINARY32FIELD_H +#define __NDE_BINARY32FIELD_H + +#include "BinaryField.h" +#include <foundation/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 diff --git a/Src/nde/android/BinaryField.cpp b/Src/nde/android/BinaryField.cpp new file mode 100644 index 00000000..5199cac7 --- /dev/null +++ b/Src/nde/android/BinaryField.cpp @@ -0,0 +1,173 @@ +/* --------------------------------------------------------------------------- + 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((ndestring_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((ndestring_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((ndestring_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, s, p, 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; +} + diff --git a/Src/nde/android/BinaryField.h b/Src/nde/android/BinaryField.h new file mode 100644 index 00000000..dd720755 --- /dev/null +++ b/Src/nde/android/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 <foundation/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 diff --git a/Src/nde/android/ColumnField.cpp b/Src/nde/android/ColumnField.cpp new file mode 100644 index 00000000..0bd47b7b --- /dev/null +++ b/Src/nde/android/ColumnField.cpp @@ -0,0 +1,175 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +ColumnField Class +Android (linux) 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 "ColumnField.h" +#include "../NDEString.h" +#include "../nde.h" + +//--------------------------------------------------------------------------- +ColumnField::ColumnField(unsigned char FieldID, const char *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() +{ + if (Name) free(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); + Name = ndestring_wcsndup((const char *)(data+pos), c); + } +} + +//--------------------------------------------------------------------------- +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) + { + int string_length = strlen(Name); + if (string_length) + { + PUT_CHAR(string_length); + pos++; + + CHECK_BIN(len, string_length); + PUT_BINARY(data, (const uint8_t *)Name, string_length, pos); + } + 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) && + (type == FIELD_INTEGER || type == FIELD_BOOLEAN || type == FIELD_DATETIME || type == FIELD_LENGTH)) { + 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; + } +} + +//--------------------------------------------------------------------------- +char *ColumnField::GetFieldName(void) +{ + return Name; +} + +//--------------------------------------------------------------------------- +size_t ColumnField::GetDataSize(void) +{ + size_t s=3; + if (Name) + { + s+=strlen(Name); + } + return s; +} + +//--------------------------------------------------------------------------- +int ColumnField::Compare(Field * /*Entry*/) +{ + return 0; +} + + diff --git a/Src/nde/android/ColumnField.h b/Src/nde/android/ColumnField.h new file mode 100644 index 00000000..65158e03 --- /dev/null +++ b/Src/nde/android/ColumnField.h @@ -0,0 +1,47 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +ColumnField Class Prototypes + +Android (linux) implementation +--------------------------------------------------------------------------- */ + +#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 char *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); + void SetDataType(unsigned char type); + bool IsSearchableField() const; + void SetSearchable(bool val); +public: + unsigned char GetDataType(void); + char *GetFieldName(void); // not const because it's an NDE string + +protected: + bool searchable; + char *Name; + unsigned char MyType; +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/android/FilenameField.cpp b/Src/nde/android/FilenameField.cpp new file mode 100644 index 00000000..cae2e4c2 --- /dev/null +++ b/Src/nde/android/FilenameField.cpp @@ -0,0 +1,128 @@ +#include "FilenameField.h" +#include "../nde.h" + + +//--------------------------------------------------------------------------- +FilenameField::FilenameField(const char *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 mystricmp_fn(GetString(), ((FilenameField*)Entry)->GetString()); +} + +//--------------------------------------------------------------------------- +int FilenameField::Starts(Field *Entry) +{ + if (!Entry) return -1; + if (!CompatibleFields(Entry->GetType(), GetType())) + return 0; + return (mystristr_fn(GetString(), ((FilenameField*)Entry)->GetString()) == GetString()); +} + +//--------------------------------------------------------------------------- +int FilenameField::Contains(Field *Entry) +{ + if (!Entry) return -1; + if (!CompatibleFields(Entry->GetType(), GetType())) + return 0; + return (mystristr_fn(GetString(), ((FilenameField*)Entry)->GetString()) != NULL); +} + + +Field *FilenameField::Clone(Table *pTable) +{ + FilenameField *clone = new FilenameField(String, STRING_IS_NDESTRING); + clone->Pos = FIELD_CLONE; + clone->ID = ID; + clone->MaxSizeOnDisk = 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 (!String) + return r; + + if (String && String[0] == 0) + return r; + + return !r; + } + // + bool r; + const char *p=0; + if (Data->GetType() == FIELD_STRING) + p = ((StringField *)Data)->GetString(); + else + p = ((FilenameField *)Data)->GetString(); + const char *d = GetString(); + if (!p) + p = ""; + if (!d) + d = ""; + + switch (op) + { + case FILTER_EQUALS: + r = !nde_stricmp_fn(d, p); + break; + case FILTER_NOTEQUALS: + r = !!nde_stricmp_fn(d, p); + break; + case FILTER_CONTAINS: + r = (NULL != stristr_fn(d, p)); + break; + case FILTER_NOTCONTAINS: + r = (NULL == stristr_fn(d, p)); + break; + case FILTER_ABOVE: + r = (bool)(nde_stricmp_fn(d, p) > 0); + break; + case FILTER_ABOVEOREQUAL: + r = (bool)(nde_stricmp_fn(d, p) >= 0); + break; + case FILTER_BELOW: + r = (bool)(nde_stricmp_fn(d, p) < 0); + break; + case FILTER_BELOWOREQUAL: + r = (bool)(nde_stricmp_fn(d, p) <= 0); + break; + case FILTER_BEGINS: + r = (bool)(nde_strnicmp_fn(d, p, strlen(p)) == 0); + break; + case FILTER_ENDS: + { + int lenp = (int)strlen(p), lend = (int)strlen(d); + if (lend < lenp) return 0; // too short + r = (bool)(nde_stricmp_fn((d + lend) - lenp, p) == 0); + } + break; + case FILTER_LIKE: + r = (bool)(nde_stricmp_fn(d, p) == 0); + break; + default: + r = true; + break; + } + return r; +} diff --git a/Src/nde/android/FilenameField.h b/Src/nde/android/FilenameField.h new file mode 100644 index 00000000..cef6f9f6 --- /dev/null +++ b/Src/nde/android/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 char *Str, int strkind=STRING_IS_WCHAR); + FilenameField(); +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/android/IndexField.cpp b/Src/nde/android/IndexField.cpp new file mode 100644 index 00000000..faf3cb49 --- /dev/null +++ b/Src/nde/android/IndexField.cpp @@ -0,0 +1,142 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +IndexField Class +Android (linux) 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 char *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); + Name = ndestring_wcsndup((const char *)(data+pos), c); + } +} + +//--------------------------------------------------------------------------- +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) + { + int string_length = strlen(Name); + if (string_length) + { + PUT_CHAR(string_length); + pos++; + + CHECK_BIN(len, string_length); + PUT_BINARY(data, (const uint8_t *)Name, string_length, pos); + } + else + { + PUT_CHAR(0); + } + } + else + { + PUT_CHAR(0); + } +} + +//--------------------------------------------------------------------------- + +char *IndexField::GetIndexName(void) +{ + return Name; +} + +//--------------------------------------------------------------------------- +size_t IndexField::GetDataSize(void) +{ + size_t s=9; + if (Name) + { + s+=strlen(Name); + } + 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; +} + + diff --git a/Src/nde/android/IndexField.h b/Src/nde/android/IndexField.h new file mode 100644 index 00000000..8b66d315 --- /dev/null +++ b/Src/nde/android/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 char *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); + + char *GetIndexName(void); + int TranslateToIndex(int Id, IndexField *index); + Index *index; // TODO: make protected +protected: + int Position; + int DataType; + char *Name; +}; + +#endif
\ No newline at end of file diff --git a/Src/nde/android/IndexRecord.cpp b/Src/nde/android/IndexRecord.cpp new file mode 100644 index 00000000..5c6f9431 --- /dev/null +++ b/Src/nde/android/IndexRecord.cpp @@ -0,0 +1,148 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +IndexRecord Class + +--------------------------------------------------------------------------- */ + +#include "IndexRecord.h" +#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 (p->next) + p->index->Colaborate((IndexField *)p->next); + 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 char *name) +{ + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + IndexField *p = (IndexField *)*itr; + if (!strcasecmp(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 *previous = 0; + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + IndexField *p = (IndexField *)*itr; + p->WriteField(ParentTable, previous, (Field *)p->next); + previous = p; + } + 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; +} + +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; + + } + } +} + diff --git a/Src/nde/android/IndexRecord.h b/Src/nde/android/IndexRecord.h new file mode 100644 index 00000000..f3be2bde --- /dev/null +++ b/Src/nde/android/IndexRecord.h @@ -0,0 +1,23 @@ +#pragma once +/* +Android (linux) implementation +*/ +#include <stdio.h> +#include "Record.h" +#include <nu/PtrDeque2.h> +class IndexField; +class IndexRecord : public RecordBase +{ +public: + IndexRecord(int RecordPos, int insertionPoint, VFILE *FileHandle, Table *p); + void BuildCollaboration(); + bool NeedFix(); + IndexField *GetIndexByName(const char *name); + int GetColumnCount() { return 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); +}; + diff --git a/Src/nde/android/IntegerField.cpp b/Src/nde/android/IntegerField.cpp new file mode 100644 index 00000000..42ed5e06 --- /dev/null +++ b/Src/nde/android/IntegerField.cpp @@ -0,0 +1,1015 @@ +/* --------------------------------------------------------------------------- + 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> // for alloca + +//--------------------------------------------------------------------------- +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 char *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... + {"ago", TOKEN_AGO}, + {"now", TOKEN_NOW}, + {"am", TOKEN_AM}, + {"pm", TOKEN_PM}, + {"this", TOKEN_THIS}, + {"date", TOKEN_DATE}, + {"time", TOKEN_TIME}, + {"of", TOKEN_OF}, + {"at", TOKEN_AT}, + {"the", TOKEN_THE}, + {"yesterday", TOKEN_YESTERDAY}, + {"tomorrow", TOKEN_TOMORROW}, + {"today", TOKEN_TODAY}, + {"from", TOKEN_FROM}, + {"before", TOKEN_BEFORE}, + {"after", TOKEN_AFTER}, + {"past", TOKEN_AFTER}, + {"monday", TOKEN_MONDAY}, + {"mon", TOKEN_MONDAY}, + {"tuesday", TOKEN_TUESDAY}, + {"tue", TOKEN_TUESDAY}, + {"wednesday", TOKEN_WEDNESDAY}, + {"wed", TOKEN_WEDNESDAY}, + {"thursday", TOKEN_THURSDAY}, + {"thu", TOKEN_THURSDAY}, + {"friday", TOKEN_FRIDAY}, + {"fri", TOKEN_FRIDAY}, + {"saturday", TOKEN_SATURDAY}, + {"sat", TOKEN_SATURDAY}, + {"sunday", TOKEN_SUNDAY}, + {"sun", TOKEN_SUNDAY}, + {"midnight", TOKEN_MIDNIGHT}, + {"noon", TOKEN_NOON}, + {"second", TOKEN_SECOND}, + {"seconds", TOKEN_SECOND}, + {"sec", TOKEN_SECOND}, + {"s", TOKEN_SECOND}, + {"minute", TOKEN_MINUTE}, + {"minutes", TOKEN_MINUTE}, + {"min", TOKEN_MINUTE}, + {"mn", TOKEN_MINUTE}, + {"m", TOKEN_MINUTE}, + {"hour", TOKEN_HOUR}, + {"hours", TOKEN_HOUR}, + {"h", TOKEN_HOUR}, + {"day", TOKEN_DAY}, + {"days", TOKEN_DAY}, + {"d", TOKEN_DAY}, + {"week", TOKEN_WEEK}, + {"weeks", TOKEN_WEEK}, + {"w", TOKEN_WEEK}, + {"month", TOKEN_MONTH}, + {"months", TOKEN_MONTH}, + {"year", TOKEN_YEAR}, + {"years", TOKEN_YEAR}, + {"y", TOKEN_YEAR}, + {"january", TOKEN_JANUARY}, + {"jan", TOKEN_JANUARY}, + {"february", TOKEN_FEBRUARY}, + {"feb", TOKEN_FEBRUARY}, + {"march", TOKEN_MARCH}, + {"mar", TOKEN_MARCH}, + {"april", TOKEN_APRIL}, + {"apr", TOKEN_APRIL}, + {"may", TOKEN_MAY}, + {"june", TOKEN_JUNE}, + {"jun", TOKEN_JUNE}, + {"july", TOKEN_JULY}, + {"jul", TOKEN_JULY}, + {"august", TOKEN_AUGUST}, + {"aug", TOKEN_AUGUST}, + {"september", TOKEN_SEPTEMBER}, + {"sep", TOKEN_SEPTEMBER}, + {"october", TOKEN_OCTOBER}, + {"oct", TOKEN_OCTOBER}, + {"november", TOKEN_NOVEMBER}, + {"nov", TOKEN_NOVEMBER}, + {"december", TOKEN_DECEMBER}, + {"dec", TOKEN_DECEMBER}, +}; + + +//--------------------------------------------------------------------------- +int IntegerField::LookupToken(const char *t) { + for (int i=0;i<sizeof(Int_Tokens)/sizeof(tokenstruct);i++) { + if (!strcasecmp(Int_Tokens[i].token, t)) + return Int_Tokens[i].tid; + } + return TOKEN_IDENTIFIER; +} + +static int myatoi(const char *p, int len) { + char *w = (char *)alloca((len+1)*sizeof(char)); + strncpy(w, p, len); + w[len] = 0; + int a = strtol(w,0, 10); + //free(w); + return a; +} + +static int isallnum(const char *p) +{ + while (p && *p) { + if (*p < '0' || *p > '9') return 0; + p++; + } + return 1; +} + +//--------------------------------------------------------------------------- +int IntegerField::ApplyConversion(const char *format, TimeParse *tp) { + int size; + + int value = GetValue(); + char *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 char *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 = strtol(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. + char *z; + size_t tokenLen=strlen(token); + if (tokenLen>=2) + { + z = token+tokenLen-2; + if (!_stricmp(z, "st") || !_stricmp(z, "nd") || !_stricmp(z, "rd") || !_stricmp(z, "th")) { + int j = myatoi(token, 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 = strchr(token, ':'); + if (z) + { + if (tp) tp->absolute_hastime = 1; + char *zz = strchr(z+1, ':'); + int a, b, 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 = strtol(zz+1,0,10); } + else b = strtol(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 = strchr(token, '/'); + if (z) { + if (tp) tp->absolute_hasdate = 1; + char *zz = strchr(z+1, '/'); + int a, b, 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 = strtol(zz+1,0,10); } + else b = atoi(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(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(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(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/android/IntegerField.h b/Src/nde/android/IntegerField.h new file mode 100644 index 00000000..d5729bb8 --- /dev/null +++ b/Src/nde/android/IntegerField.h @@ -0,0 +1,93 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + IntegerField Class Prototypes + + Android (linux) implementation + +--------------------------------------------------------------------------- */ + +#ifndef __INTEGERFIELD_H +#define __INTEGERFIELD_H + +#include <foundation/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 char *format, TimeParse *tp=NULL); + static int LookupToken(const char *t); + }; + + +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/android/Query.cpp b/Src/nde/android/Query.cpp new file mode 100644 index 00000000..46a9ad1a --- /dev/null +++ b/Src/nde/android/Query.cpp @@ -0,0 +1,973 @@ +#include "../nde.h" +#include "../NDEString.h" +#include "Query.h" + +//--------------------------------------------------------------------------- + +bool Scanner::Query(const char *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 char *Scanner::GetLastQuery() +{ + return last_query; +} + + +typedef struct +{ + const char *token; + int tid; +} tokenstruct; + +tokenstruct Tokens[] = // Feel free to add more... +{ + {"AND", TOKEN_AND }, + {"OR", TOKEN_OR }, + {"HAS", TOKEN_CONTAINS }, + {"NOTHAS",TOKEN_NOTCONTAINS}, + {"BEGINS", TOKEN_BEGINS }, + {"ENDS", TOKEN_ENDS }, + {"ISEMPTY", TOKEN_ISEMPTY}, + {"ISNOTEMPTY",TOKEN_ISNOTEMPTY}, + {"LIKE", TOKEN_LIKE}, + {"BEGINSLIKE", TOKEN_BEGINSLIKE}, +}; + + +typedef struct +{ + int Op; + int Level; +} OpLevel; + +static int Query_ParseLength(const char *str) +{ + int i = atoi(str); + + const char *p; + if ((p=strstr(str,":"))) + { + i*=60; + i+=atoi(++p); + if ((p=strstr(p,":"))) + { + i*=60; + i+=atoi(++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 char *query) +{ + const char *p = query; // pointer on next token to read + int size; + int state = 0; + int pcount = 0; + VListEntry<OpLevel> *entry; + + if (pstack.GetNElements() > 0) + Query_CleanUp(); + + while (1) + { + p = Query_EatSpace(p); + int 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 char *s = strchr(p, ']'); + if (!s) + { + Query_SyntaxError((int)(p-query)); + return false; + } + p = Query_EatSpace(p); + if (*p == '[') p++; + + char *format = ndestring_malloc((s-p+1)*sizeof(char)); + strncpy(format, p, s-p); + format[s-p] = 0; + + 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("")); + 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: + { + int i; + IntegerField *i_f = new IntegerField(); + i = Query_ParseLength(token); + i_f->SetValue(i); + f->SetData(i_f); + } + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + { + int i; + IntegerField *i_f = new IntegerField(); + i = atoi(token); + i_f->SetValue(i); + f->SetData(i_f); + } + break; + case FIELD_INT64: + { + int64_t i; + Int64Field *i_f = new Int64Field(); + i = strtoull(token, 0, 10); // 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 char *s = strchr(p, ']'); + if (!s) + { + Query_SyntaxError((int)(p-query)); + return false; + } + p = Query_EatSpace(p); + if (*p == '[') p++; + char *format = ndestring_malloc((s-p+1)*sizeof(char)); + strncpy(format, p, s-p); + format[s-p] = 0; + 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("")); + 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 char *Scanner::Query_EatSpace(const char *p) +{ + while (*p && *p == ' ') p++; + return p; +} + +//--------------------------------------------------------------------------- +const char *Scanner::Query_ProbeNonAlphaNum(const char *p) +{ + int inquote=0; + while (*p && (!Query_isControlChar(*p) || (inquote))) + { + if (*p == '\"') + { + if (!inquote) + inquote = 1; + else + return p+1; + } + p++; + } + return p; +} + +//--------------------------------------------------------------------------- +int Scanner::Query_isControlChar(char p) +{ + switch (p) + { + case '&': + case '|': + case '!': + case '(': + case '[': + case ')': + case ']': + case '>': + case '<': + case '=': + case ',': + case ' ': + return true; + } + return false; +} + +//--------------------------------------------------------------------------- +char *Scanner::Query_ProbeAlphaNum(char *p) +{ + while (*p && Query_isControlChar(*p)) p++; + return p; +} + +//--------------------------------------------------------------------------- +char *Scanner::Query_ProbeSpace(char *p) +{ + while (*p && *p != ' ') p++; + return p; +} + +//--------------------------------------------------------------------------- +int Scanner::Query_LookupToken(const char *t) +{ + for (int i=0;i<sizeof(Tokens)/sizeof(tokenstruct);i++) + { + if (!_stricmp(Tokens[i].token, t)) + return Tokens[i].tid; + } + return TOKEN_IDENTIFIER; +} + +//--------------------------------------------------------------------------- + +int Scanner::Query_GetNextToken(const char *p, int *size, char **_token, int tokentable) +{ + + int t = TOKEN_EOQ; + const char *startptr = p; + + if (!*p) return TOKEN_EOQ; + + p = Query_EatSpace(p); + + const char *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) == '\"' && (*_token)[token_length-1] == '\"') // check for quoted string + { + size_t l=token_length-2; + if (l>0) + { + memcpy(*_token,(*_token)+1,l*sizeof(char)); + (*_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 '&': + if (*(p+1) == '&') p++; + t = TOKEN_AND; + break; + case '|': + if (*(p+1) == '|') p++; + t = TOKEN_OR; + break; + case '!': + if (*(p+1) == '=') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_NOT; + break; + case '(': + t = TOKEN_PAROPEN; + break; + case ')': + t = TOKEN_PARCLOSE; + break; + case '[': + t = TOKEN_SQBRACKETOPEN; + break; + case ']': + t = TOKEN_SQBRACKETCLOSE; + break; + case ',': + t = TOKEN_COMMA; + break; + case '>': + if (*(p+1) == '=') + { + p++; + t = TOKEN_AOREQUAL; + break; + } + if (*(p+1) == '<') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_ABOVE; + break; + case '<': + if (*(p+1) == '=') + { + p++; + t = TOKEN_BOREQUAL; + break; + } + if (*(p+1) == '>') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + t = TOKEN_BELOW; + break; + case '=': + if (*(p+1) == '>') + { + p++; + t = TOKEN_AOREQUAL; + break; + } + if (*(p+1) == '<') + { + p++; + t = TOKEN_BOREQUAL; + break; + } + if (*(p+1) == '!') + { + p++; + t = TOKEN_NOTEQUAL; + break; + } + if (*(p+1) == '=') p++; + t = TOKEN_EQUAL; + break; + default: + t = TOKEN_UNKNOWN; + break; + } + p++; + } + + *size = (int)(p - startptr); + return t; +} + +static uint8_t quickhex(char c) +{ + int hexvalue = c; + if (hexvalue & 0x10) + hexvalue &= ~0x30; + else + { + hexvalue &= 0xF; + hexvalue += 9; + } + return hexvalue; +} + +static uint8_t DecodeEscape(const char *&str) +{ + uint8_t a = quickhex(*++str); + uint8_t b = quickhex(*++str); + str++; + return a * 16 + b; +} + +static void DecodeEscapedUTF8(char *&output, const char *&input) +{ + bool error=false; + + while (*input == '%') + { + if (isxdigit(input[1]) && isxdigit(input[2])) + { + *output++=DecodeEscape(input); + } + else if (input[1] == '%') + { + input+=2; + *output++='%'; + } + else + { + error = true; + break; + } + } + + 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(char *str) +{ + const char *itr = str; + while (*itr) + { + switch (*itr) + { + case '%': + DecodeEscapedUTF8(str, itr); + break; + default: + *str++ = *itr++; + break; + } + } + *str = 0; +} + diff --git a/Src/nde/android/Query.h b/Src/nde/android/Query.h new file mode 100644 index 00000000..a9dd90dd --- /dev/null +++ b/Src/nde/android/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(char *p);
\ No newline at end of file diff --git a/Src/nde/android/Record.cpp b/Src/nde/android/Record.cpp new file mode 100644 index 00000000..f43013cb --- /dev/null +++ b/Src/nde/android/Record.cpp @@ -0,0 +1,124 @@ +/* --------------------------------------------------------------------------- +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 ? 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(); +} + +ColumnField *Record::GetColumnByName(const char *name) +{ + for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++) + { + ColumnField *p = (ColumnField *)*itr; + if (!strcasecmp(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; + p->WriteField(ParentTable, previous, (Field *)p->next); + 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; + } + } +} + + + diff --git a/Src/nde/android/Scanner.cpp b/Src/nde/android/Scanner.cpp new file mode 100644 index 00000000..001cdc8f --- /dev/null +++ b/Src/nde/android/Scanner.cpp @@ -0,0 +1,1196 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Scanner Class + +--------------------------------------------------------------------------- */ + +#include "../nde.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() +{ + 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 (Eof() || Bof()) + { + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = NULL; + return; + } + + Record *old_record = CurrentRecord; + CurrentRecord = NULL; + + Record *new_record = GetRecord(CurrentRecordIdx); + if (old_record) old_record->Release(); + CurrentRecord = new_record; + */ + 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 char *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 char *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, "unknown field type for id", "debug", 0); + O = new Field(); + break; + } + O->Type = field->GetDataType(); + 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; + Ptr = index->Get(CurrentRecordIdx); + pTable->RowCache_Remove(Ptr); + } + CurrentRecordIdx = CurrentRecord->WriteFields(pTable, CurrentRecordIdx); + Edition=false; + if (pTable->use_row_cache) + { + int Ptr; + 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 = NULL; + + 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; + 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; + 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 char *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; + int j; + int n; + Field *cfV; + + if (index->NEntries == NUM_SPECIAL_RECORDS) + return false; + + int success; + + 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; + 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; + 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; + 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; + if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, false); + CacheLastLocate(Id, From, field, index, j); + success = true; + goto nextiter_2; + } + delete compField; + } + 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; + } + 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; + } + if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, false); + CacheLastLocate(Id, From, field, index, j); + success = true; + goto nextiter_3; + } + if (compField) + { + delete compField; + } + } + 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; + 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; + 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; + 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 char *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(); + int i; + size_t totalSize=0; + + if (CurrentRecord) + { + if (CurrentRecord) CurrentRecord->Release(); + CurrentRecord = NULL; + CurrentRecordIdx = 0; + } + + for (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 char *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 char *FieldName) +{ + return pTable->GetColumnByName(FieldName); +} +//--------------------------------------------------------------------------- +ColumnField *Scanner::GetColumnById(unsigned char Idx) +{ + return pTable->GetColumnById(Idx); +} + +//--------------------------------------------------------------------------- +int Scanner::AddFilterByName(const char *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 +{ + 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 (SearchStrings::const_iterator field_itr=search_strings.begin();field_itr!=search_strings.end();field_itr++) + { + StringField *search_field = *field_itr; + if (MatchSearch(fields, search_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 (SearchStrings::const_iterator field_itr=search_strings.begin();field_itr!=search_strings.end();field_itr++) + { + StringField *search_field = *field_itr; + if (MatchSearch(fields, search_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) + { + if (ResultPtr == 256) + { + FiltersOK = false; // Should never happen, case already discarded by CheckFilters + return true; + } + 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 char *search_string) +{ + // first, clear existing search terms + for (SearchStrings::iterator itr=search_strings.begin();itr!=search_strings.end();itr++) + { + delete *itr; + } + search_strings.clear(); + + if (*search_string == '*' && search_string[1] == ' ') + { + search_any=true; + search_string += 2; + } + else + search_any=false; + + if (search_string) + { + + while (*search_string) + { + while (*search_string && (*search_string == ' ' || *search_string == '\t')) + search_string++; + + const char *end=search_string; + + char c = *search_string; + if (c == '\"') // a quoted string + { + end++; + search_string++; + while (*end && *end != '\"') + end++; + + if (*search_string) // make sure it's not just a quote by itself + { + if (*end == 0) // no terminating quotes + { + char *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 + { + char *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 != '\t') + end++; + + char *search_term = ndestring_wcsndup(search_string, end-search_string-1); + search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING)); + } + } + } +}
\ No newline at end of file diff --git a/Src/nde/android/Scanner.h b/Src/nde/android/Scanner.h new file mode 100644 index 00000000..33a3b6d5 --- /dev/null +++ b/Src/nde/android/Scanner.h @@ -0,0 +1,148 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience + --------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Scanner Class Prototypes + + Android (linux) implementation + + --------------------------------------------------------------------------- */ + +#ifndef __SCANNER_H +#define __SCANNER_H + +#include "../nde.h" +#include "record.h" +#include "../index.h" +#include <nu/Vector.h> +#include <nu/ValueSet.h> +#include "../LinkedList.h" +class Table; +class Index; + +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 Vector<StringField *> SearchStrings; + SearchStrings search_strings; + typedef ValueSet<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); + bool CheckFilters(void); + void CacheLastLocate(int Id, int From, Field *field, Index *i, int j); + + static int Query_LookupToken(const char *token); + void Query_CleanUp(void); + void Query_SyntaxError(int c); +public: + static int Query_GetNextToken(const char *p, int *size, char **token, int tokentable=0); + static const char *Query_EatSpace(const char *p); + static char *Query_ProbeSpace(char *p); + static const char *Query_ProbeNonAlphaNum(const char *p); + static char *Query_ProbeAlphaNum(char *p); + static int Query_isControlChar(char p); + + bool Query(const char *query); + bool Query_Parse(const char *query); + + const char *GetLastQuery(); + +public://fucko: protected + LinkedList pstack; + char *token; + char *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 char *FieldName); + ColumnField *GetColumnById(unsigned char id); + + Field *NewFieldByName(const char *fieldName, unsigned char Perm); + Field *NewFieldById(unsigned char Id, unsigned char Perm); + void DeleteField(Field *field); + void DeleteFieldByName(const char *name); + void DeleteFieldById(unsigned char Id); + + void Cancel(void); + void Insert(void); + void Edit(void); + void Post(void); + void Delete(void); + + Field *GetFieldByName(const char *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 char *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 char *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 char *desc); + bool SetWorkingIndexById(unsigned char Id); + + void Search(const char *search_string); + bool HasIndexChanged(void) { return iModified; } + void ClearDirtyBit(void); + float FragmentationLevel(void); + + Table *GetTable(); + int in_query_parser; + int disable_date_resolution; +}; + +#endif diff --git a/Src/nde/android/StringField.cpp b/Src/nde/android/StringField.cpp new file mode 100644 index 00000000..dd5c7101 --- /dev/null +++ b/Src/nde/android/StringField.cpp @@ -0,0 +1,292 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +StringField Class +Android (linux) 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" + +//--------------------------------------------------------------------------- +StringField::StringField(const char *Str, int strkind) +{ + InitField(); + Type = FIELD_STRING; + if (Str) + { + if (strkind == STRING_IS_WCHAR) + String = ndestring_wcsdup(Str); + else + { + String = const_cast<char *>(Str); + ndestring_retain(String); + } + } +} + +//--------------------------------------------------------------------------- +void StringField::InitField(void) +{ + Type = FIELD_STRING; + // String = NULL; + String = NULL; +} + +//--------------------------------------------------------------------------- +StringField::StringField() +{ + InitField(); +} + +//--------------------------------------------------------------------------- +StringField::~StringField() +{ + ndestring_release(String); + String=0; +} + + +//--------------------------------------------------------------------------- +void StringField::ReadTypedData(const uint8_t *data, size_t len) +{ + size_t pos=0; + unsigned short c; + + CHECK_SHORT(len); + c = GET_SHORT(); + pos+=2; + if (c) + { + bool unicode=false; + bool utf16BE=false; + if (c >= 2 // enough room for BOM + && (c & 1) == 0) // can't be unicode if it's not an even multiple of 2 + { + uint16_t BOM=GET_SHORT(); + if (BOM == 0xFEFF) + { + pos+=2; + c-=2; + unicode=true; + } + else if (BOM == 0xFFFE) + { + pos+=2; + c-=2; + unicode=true; + utf16BE=true; + } + } + + CHECK_BIN(len, c); + if (unicode) + { + ndestring_release(String); + if (utf16BE) + { + size_t bytes = utf16BE_to_utf8((uint16_t *)(data+pos), c>>1, 0, 0); + String = ndestring_malloc(bytes+1); + utf16BE_to_utf8((uint16_t *)(data+pos), c>>1, String, bytes); + String[bytes]=0; + } + else + { + size_t bytes = utf16LE_to_utf8((uint16_t *)(data+pos), c>>1, 0, 0); + String = ndestring_malloc(bytes+1); + utf16LE_to_utf8((uint16_t *)(data+pos), c>>1, String, bytes); + String[bytes]=0; + } + } + else + { + // TODO: check for utf-8 byte marker + String = ndestring_malloc(c+1); + GET_BINARY((uint8_t *)String, data, c, pos); + String[c]=0; + } + } +} + +//--------------------------------------------------------------------------- +void StringField::WriteTypedData(uint8_t *data, size_t len) +{ + int pos=0; + + if (String) + { + unsigned short c = (unsigned short)strlen(String); + // write size + CHECK_SHORT(len); + PUT_SHORT(c); pos+=2; + + // write string + CHECK_BIN(len, c); + PUT_BINARY(data, (uint8_t *)String, c, pos); + } + else + { + CHECK_SHORT(len); + PUT_SHORT(0); pos+=2; + } +} + + +//--------------------------------------------------------------------------- +char *StringField::GetString(void) +{ + return String; +} + +//--------------------------------------------------------------------------- +void StringField::SetString(const char *Str) +{ + if (!Str) return; + + ndestring_release(String); + String = NULL; + String = ndestring_wcsdup(Str); +} + +//--------------------------------------------------------------------------- +void StringField::SetNDEString(char *Str) +{ + if (!Str) return; + + // copy and then release, just in case we're copying into ourselves + char *oldStr = String; + String = Str; + ndestring_retain(String); + ndestring_release(oldStr); +} +//--------------------------------------------------------------------------- +size_t StringField::GetDataSize(void) +{ + if (String) + { + return strlen(String) + 2; + } + else + { + return 2; + } +} + +//--------------------------------------------------------------------------- +int StringField::Compare(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + return mystricmp(GetString(), ((StringField*)Entry)->GetString()); +} + +//--------------------------------------------------------------------------- +int StringField::Starts(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + return (mystristr(GetString(), ((StringField*)Entry)->GetString()) == GetString()); +} + +//--------------------------------------------------------------------------- +int StringField::Contains(Field *Entry) +{ + if (!Entry) return -1; + if (Entry->GetType() != GetType()) return 0; + return (mystristr(GetString(), ((StringField*)Entry)->GetString()) != NULL); +} + +Field *StringField::Clone(Table *pTable) +{ + StringField *clone = new StringField(String, STRING_IS_NDESTRING); + clone->Pos = FIELD_CLONE; + clone->ID = ID; + clone->MaxSizeOnDisk = GetDataSize(); + return clone; +} + +bool StringField::ApplyFilter(Field *Data, int op) +{ + // TODO: maybe do this? + + if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY) + { + bool r = (op == FILTER_ISEMPTY); + if (!String) + return r; + + if (String && String[0] == 0) + return r; + + return !r; + } + // + bool r; + StringField *compField = (StringField *)Data; + + const char *p = compField->GetString(); + const char *d = GetString(); + if (!p) + p = ""; + if (!d) + d = ""; + + switch (op) + { + case FILTER_EQUALS: + r = !nde_stricmp(d, p); + break; + case FILTER_NOTEQUALS: + r = !!nde_stricmp(d, p); + break; + case FILTER_CONTAINS: + r = (NULL != stristr_ignore(d, p)); + break; + case FILTER_NOTCONTAINS: + r = (NULL == stristr_ignore(d, p)); + break; + case FILTER_ABOVE: + r = (bool)(nde_stricmp(d, p) > 0); + break; + case FILTER_ABOVEOREQUAL: + r = (bool)(nde_stricmp(d, p) >= 0); + break; + case FILTER_BELOW: + r = (bool)(nde_stricmp(d, p) < 0); + break; + case FILTER_BELOWOREQUAL: + r = (bool)(nde_stricmp(d, p) <= 0); + break; + case FILTER_BEGINS: + r = (bool)(nde_strnicmp(d, p, strlen(p)) == 0); + break; + case FILTER_ENDS: + { + size_t lenp = strlen(p), lend = strlen(d); + if (lend < lenp) return 0; // too short + r = (bool)(nde_stricmp((d + lend) - lenp, p) == 0); + } + break; + case FILTER_LIKE: + r = (bool)(nde_stricmp(d, p) == 0); + break; + case FILTER_BEGINSLIKE: + r = (bool)(nde_strnicmp_ignore(d, p, strlen(p)) == 0); + break; + default: + r = true; + break; + } + return r; +} + diff --git a/Src/nde/android/StringField.h b/Src/nde/android/StringField.h new file mode 100644 index 00000000..95d300db --- /dev/null +++ b/Src/nde/android/StringField.h @@ -0,0 +1,50 @@ +/* +--------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- +*/ + +/* +--------------------------------------------------------------------------- + +StringField Class Prototypes + +Android (linux) implementation +--------------------------------------------------------------------------- +*/ + +#ifndef __STRINGFIELD_H +#define __STRINGFIELD_H +#include "../NDEString.h" + +class StringField : public Field +{ +public: + StringField(); + ~StringField(); + + + StringField(const char *Str, int strkind=STRING_IS_WCHAR); + char *GetString(void); + void SetString(const char *Str); + void SetNDEString(char *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); + + char *String; +}; + +#endif + diff --git a/Src/nde/android/Table.cpp b/Src/nde/android/Table.cpp new file mode 100644 index 00000000..315d62cf --- /dev/null +++ b/Src/nde/android/Table.cpp @@ -0,0 +1,846 @@ +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Table Class + +Android (linux) implementation +--------------------------------------------------------------------------- */ +#include "Table.h" +#include "../nde.h" +#include <stdio.h> +#include <string.h> +#include "../CRC.H" +#include "../NDEString.h" +#include "IndexField.h" +#include "ColumnField.h" +#include "../DBUtils.h" +const char *tSign="NDETABLE"; + +//--------------------------------------------------------------------------- +Table::Table(const char *TableName, const char *Idx, int Create, Database *_db, int _Cached) +: Scanner(this), use_row_cache(false), columns_cached(false) +{ + Handle = 0; + 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't 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=0; + if (Handle) + Vfclose(Handle); // close (but don't destroy) to keep mutex open. + if (IdxHandle) + Vfdestroy(IdxHandle); + IdxHandle = 0; + + 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() +{ + bool Valid; + 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", 1); + 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", 1); + 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 + { + int Ptr; + + 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 *field; + field = new IndexField(PRIMARY_INDEX, -1, -1, "None"); + field->index = new Index(IdxHandle, PRIMARY_INDEX, -1, -1, FALSE, 0, this); + + // Get indexes + Ptr = field->index->Get(INDEX_RECORD_NUM); + IndexList = new IndexRecord(Ptr, INDEX_RECORD_NUM, Handle, this); + if (!IndexList) + { + delete 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't be added by addfield) + IndexList->AddField(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 (field->index != Scanner::index) + { + delete field; + field=0; + } + + // 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 0 // TODO + if (Valid && !justcreated) + { + if (IndexList->NeedFix()) + Compact(); + } +#endif + + 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) err|=Vsync(IdxHandle); + + Vfunlock(Handle); +} + +//--------------------------------------------------------------------------- +ColumnField *Table::NewColumn(unsigned char FieldID, const char *FieldName, unsigned char FieldType, bool indexUnique) +{ + columns_cached=false; // if they start writing new columns, kill the columns cache until they PostColumns() + ColumnField *f = GetColumnById(FieldID); + if (f) { + int t = f->GetDataType(); + if (t != FieldType) { + if (CompatibleFields(t, FieldType)) + { + f->SetDataType(FieldType); + goto aok; + } + } + return NULL; + } +aok: + if (GetColumnByName(FieldName)) + return NULL; + ColumnField *field = new ColumnField(FieldID, FieldName, FieldType, this); + column_ids[FieldID]=FieldType; + FieldsRecord->AddField(field); + return 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 char *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 char *name, const char *desc) +{ + ColumnField *header = GetColumnByName(name); + if (header) + { + unsigned char Idx = header->ID; + AddIndexById(Idx, desc); + } +} + +//--------------------------------------------------------------------------- +void Table::AddIndexById(unsigned char Id, const char *desc) +{ + if (GetIndexById(Id)) return; + ColumnField *col = GetColumnById(Id); + if (!col) + return; + IndexField *newindex = new IndexField(Id, IndexList->GetColumnCount(), col->GetDataType(), desc); + newindex->index = new Index(IdxHandle, Id, IndexList->GetColumnCount(), col->GetDataType(), true, Scanner::index->NEntries, this); + IndexList->AddField(newindex); + + IndexField *previous = (IndexField *)newindex->prev; + previous->index->Colaborate(newindex); + IndexField *primary_index = (IndexField *)IndexList->GetField(PRIMARY_INDEX); + newindex->index->Colaborate(primary_index); + + previous->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 char *desc) +{ + IndexField *indx = GetIndexByName(desc); + if (!strcasecmp(desc, "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) + { + context->data_size = len; + context->data = (uint8_t *)realloc(context->data, context->data_size); + } + 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 (strcasecmp(field->GetIndexName(), "None")) + ctable->AddIndexById(field->GetFieldId(), field->GetIndexName()); + return true; +} +#if 0 // TODO +//--------------------------------------------------------------------------- +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]; + wchar_t temp_index[MAX_PATH+12]; + wchar_t old_table[MAX_PATH+12]; + wchar_t old_index[MAX_PATH+12]; + 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 *)malloc(65536); + 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(); +} +#endif +ColumnField *Table::GetColumnById(unsigned char Idx) +{ + if (!FieldsRecord) + return NULL; + return (ColumnField *)FieldsRecord->GetField(Idx); +} + +ColumnField *Table::GetColumnByName(const char *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(); + } + + row = 0; + } +} + +void Table::RowCache_Add(Record *record, int position) +{ + if (use_row_cache) + { + record->Retain(); + + Record *&row = rowCache[position]; + if (row) + { + row->Release(); + } + + row = record; + } +} + +Record *Table::RowCache_Get(int position) +{ + if (!use_row_cache) + return 0; + Record *row = rowCache[position]; + if (row) + row->Retain(); + return row; +} + +void Table::EnableRowCache() +{ + use_row_cache=true; +} diff --git a/Src/nde/android/Table.h b/Src/nde/android/Table.h new file mode 100644 index 00000000..2004e786 --- /dev/null +++ b/Src/nde/android/Table.h @@ -0,0 +1,166 @@ +/* --------------------------------------------------------------------------- + Nullsoft Database Engine + -------------------- + codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + + Table Class Prototypes + Android (linux) implementation +--------------------------------------------------------------------------- */ + +#ifndef __TABLE_H +#define __TABLE_H + +#include <stdio.h> +#include "Scanner.h" +#include <map> +#include "IndexRecord.h" + +class Table : private Scanner +{ +public: + // TODO: move these back to protected + VFILE *Handle; + using Scanner::index; + bool use_row_cache; + bool GLocateUpToDate; +private: + void Init(); + void Reset(); + +private: + LinkedList *Scanners; + +protected: + char *Name; + char *IdxName; + + VFILE *IdxHandle; + int AutoCreate; + Record *FieldsRecord; + IndexRecord *IndexList; + Database *db; + int Cached; + int numErrors; + using Scanner::Edition; + bool columns_cached; + 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 char *TableName, const char *IdxName, int Create, Database *db, int Cached); + ~Table(); + bool Open(); + void Close(); + + // Columns + ColumnField *NewColumn(unsigned char Id, const char *name, unsigned char type, bool indexUniques); + + void DeleteColumn(ColumnField *field); // todo + void DeleteColumnByName(const char *name); // todo + void DeleteColumnById(unsigned char Id); // todo + void PostColumns(void); + NDE_API Record *GetColumns(void); + ColumnField *GetColumnByName(const char *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 char *FieldName, const char *KeyName); + void AddIndexById(unsigned char Id, const char *KeyName); + + void WalkIndices(IndexWalker callback, void *context); + + IndexField *GetIndexByName(const char *name); + IndexField *GetIndexById(unsigned char Id); + using Scanner::SetWorkingIndexByName; + using Scanner::SetWorkingIndexById; + bool CheckIndexing(void); + void DropIndexByName(const char *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/android/Vfs.cpp b/Src/nde/android/Vfs.cpp new file mode 100644 index 00000000..7391576a --- /dev/null +++ b/Src/nde/android/Vfs.cpp @@ -0,0 +1,539 @@ + +/* --------------------------------------------------------------------------- +Nullsoft Database Engine +-------------------- +codename: Near Death Experience +--------------------------------------------------------------------------- */ + +/* --------------------------------------------------------------------------- + +Virtual File System + +--------------------------------------------------------------------------- */ +#include "../nde.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "Vfs.h" + +#ifndef EOF +#define EOF -1 +#endif + +/* the FILE (fopen/fwrite/etc) implementation for Mac and Linux */ + + + +VFILE *Vfnew(const char *fl, const char *mode, int Cached) +{ + if (!fl) return NULL; + VFILE *f = (VFILE *)malloc(sizeof(VFILE)); + if (!f) + return NULL; + memset(f, 0, sizeof(VFILE)); +#ifdef NDE_ALLOW_NONCACHED + f->cached = Cached; +#else + f->cached = 1; +#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))) + { + free(f); + return NULL; + } + + snprintf(f->lockname, sizeof(f->lockname), "%s.lock", fl); + + return f; +} + +//---------------------------------------------------------------------------- +VFILE *Vfopen(VFILE *f, const char *fl, const char *mode, int Cached) +{ + if (!f) + { + f = Vfnew(fl, mode, Cached); + if (!f) + return 0; + } + if (!f && !fl) return NULL; + +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + f->rfile = fopen(fl, mode); + if (f->rfile) + + f->filename = _strdup(fl); + } + else + { + free(f); + return NULL; + } + return f; + } +#endif + + if (f->mode & VFS_MUSTEXIST) + { + if (access(fl, 0) != 0) + { + free(f); + return NULL; + } + } + + if (!(f->mode & VFS_NEWCONTENT)) + { + FILE *pf; + pf = fopen(fl, "rb"); + if (!pf) + { + f->data = (unsigned char *)calloc(VFILE_INC, 1); + if (f->data == NULL) + { + free(f); + return NULL; + } + f->filesize = 0; + f->maxsize = VFILE_INC; + } + else + { + fseek(pf, 0, SEEK_END); + f->filesize = ftell(pf); + fseek(pf, 0, SEEK_SET); + f->data = (unsigned char *)calloc(f->filesize, 1); + if (f->data == NULL) + { + free(f); + return NULL; + } + f->maxsize = f->filesize; + fread(f->data, f->filesize, 1, pf); + fclose(pf); + } + } + + if (f->mode & VFS_SEEKEOF) + f->ptr = f->filesize; + + f->filename = strdup(fl); + return f; +} + +//---------------------------------------------------------------------------- +void Vfclose(VFILE *f) +{ + if (!f) return; + +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + free(f->filename); + if (f->rfile) + fclose(f->rfile); + f->rfile=0; +// free(f); + return; + } +#endif + + if (!(f->mode & VFS_WRITE)) + { + free(f->filename); + free(f->data); + //free(f); + return; + } + + + Vsync(f); + while (f->locks) + Vfunlock(f, 1); + + free(f->filename); + free(f->data); + //free(f); +} + +//---------------------------------------------------------------------------- +size_t Vfread(void *ptr, size_t size, VFILE *f) +{ + if (!ptr || !f) return 0; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + return fread(ptr, 1, size, f->rfile); + } +#endif + size_t s = size; + if (!s) return 0; + if (s + f->ptr > f->filesize) + { + //FUCKO: remove this + if (!(f->ptr < f->filesize)) + { + // 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; + } + s = f->filesize - f->ptr; + } + memcpy(ptr, f->data+f->ptr, s); + f->ptr += s; + return (s/size); +} + +//---------------------------------------------------------------------------- +void Vfwrite(const void *ptr, size_t size, VFILE *f) +{ + if (!ptr || !f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + fwrite(ptr, size*n, f->rfile); + return; + } +#endif + f->dirty=1; + if (size + f->ptr > f->maxsize) + { + // grow f->data,f->maxsize to be (size + f->ptr + VFILE_INC-1)&~(VFILE_INC-1) + // instead of calling Vgrow again which gets kinda slow + size_t newsize=(size + f->ptr + VFILE_INC-1)&~(VFILE_INC-1); + f->data=(unsigned char *)realloc(f->data,newsize); + if (f->data == NULL) return; + memset(f->data+f->maxsize,0,newsize-f->maxsize); + f->maxsize=newsize; + } + memcpy(f->data + f->ptr, ptr, size); + f->ptr += size; + 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 + f->data = (unsigned char *)realloc(f->data, f->maxsize + VFILE_INC); + f->maxsize += VFILE_INC; +} + +//---------------------------------------------------------------------------- +void Vfputs(char *str, VFILE *f) +{ + if (!f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + fputs(str, f->rfile); + return; + } +#endif + Vfwrite(str, strlen(str), f); +} + +//---------------------------------------------------------------------------- +void Vfputc(char c, VFILE *f) +{ + if (!f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + fputc(c, f->rfile); + return; + } +#endif + Vfwrite(&c, 1, f); +} + +/* benski> unused: +// not mac compliant +//---------------------------------------------------------------------------- +char *Vfgets(char *dest, int n, VFILE *f) { +if (!f) return NULL; +#ifdef NDE_ALLOW_NONCACHED +if (!f->cached) +{ +#ifdef NDE_NOWIN32FILEIO +return fgets(dest, n, f->rfile); +#else +#error port me! +#endif +} +#endif + +unsigned char c=0; +char *p; +int l=0; + +p = dest; +while (l < n && !Vfeof(f)) { +c = f->data[f->ptr]; +f->ptr++; +*p = c; +p++; +l++; +if (c == '\n') { +if (!Vfeof(f) && f->data[f->ptr] == '\r') { +f->ptr++; +} +break; +} +} +*p=0; +return dest; +} +*/ + +/* benski> unused: +//---------------------------------------------------------------------------- +char Vfgetc(VFILE *f) { +if (!f) return EOF; +#ifdef NDE_ALLOW_NONCACHED +if (!f->cached) +#ifdef NDE_NOWIN32FILEIO +return fgetc(f->rfile); +#else +#error port me# +#endif +#endif +if (!Vfeof(f)) +return f->data[f->ptr++]; +return EOF; +} +*/ + +//---------------------------------------------------------------------------- +unsigned long Vftell(VFILE *f) +{ + if (!f) return (unsigned)-1; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + return ftell(f->rfile); + } +#endif + return f->ptr; +} + +//---------------------------------------------------------------------------- +void Vfseek(VFILE *f, long i, int whence) +{ + if (!f) return; +#ifdef NDE_ALLOW_NONCACHED + if (!f->cached) + { + fseek(f->rfile, i, whence); + 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 feof(f->rfile); + } +#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) + { + int p=ftell(f->rfile); + fclose(f->rfile); + f->rfile = fopen(f->filename, "r+b"); + if (!f->rfile) + return 1; + fseek(f->rfile, p, SEEK_SET); + + return 0; + } +#endif + + char newfn[1024], oldfn[1024]; + DWORD mypid=getpid(); + + snprintf(newfn,sizeof(newfn), "%s.n3w%08X",f->filename,mypid); + snprintf(oldfn,sizeof(oldfn), "%s.o1d%08X",f->filename,mypid); + + unlink(newfn); + unlink(oldfn); + + +tryagain: + FILE *pf = fopen(newfn, "wb"); + if (!pf || fwrite(f->data, f->filesize, 1, pf) != 1) + { + if (pf) fclose(pf); + + + printf("Error flushing DB to disk (is your drive full?)\n\nHit (R)etry to try again (recommended), or (C)ancel to abort (NOT recommended, and may leave the DB in an unuseable state)"); + fflush(stdout); + char c; + while (1) + { + scanf("%c", &c); +// clear_stdin(); + c = toupper(c); + if (c == 'R') goto tryagain; + else if (c == 'C') break; + } + unlink(newfn); + return 1; + } + fclose(pf); + rename(f->filename,oldfn); // save old file + + + + int rv=0; + +tryagain2: + if (rename(newfn,f->filename)) + { + + printf("Error updating DB file on disk. This should never really happen\n\nHit (R)etry to try again (recommended), or (C)ancel to abort (NOT recommended, and may leave the DB in an unuseable state)"); + fflush(stdout); + char c; + while (1) + { + scanf("%c", &c); +// clear_stdin(); + c = toupper(c); + if (c == 'R') goto tryagain2; + else if (c == 'C') break; + } + + rename(oldfn,f->filename); // restore old file + rv=1; + } + + // clean up our temp files + unlink(oldfn); + unlink(newfn); + + + //free(newfn); + //free(oldfn); + return rv; + } + f->dirty=0; + return 0; +} + +void Vfdestroy(VFILE *f) +{ + // benski> TODO: + if (f) + { + Vfclose(f); + + while (f->locks) + Vfunlock(f, 1); + +// TODO if (f->mutex) CloseHandle(f->mutex); + free(f); + } +} + +// benski> TODO implement these with fopen + +// 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; + if (fl->locks++ == 0) + { + int retry_cnt=0; + FILE *fFile; + do + { + fFile = fopen(fl->lockname, "wb"); + if (fFile == 0) Sleep(100); + } + while (fFile == 0 && retry_cnt++ < 100); // try for 10 seconds + + if (fFile == INVALID_HANDLE_VALUE) return 0; // db already locked, fail gracefully + fl->lockFile = fFile; + } +#endif + return 1; + +} + +void Vfunlock(VFILE *fl, bool is_sync) +{ +#ifndef NO_TABLE_WIN32_LOCKING + if (!is_sync && fl->cached) + return; + + if (--fl->locks == 0) + { + if (fl && fl->lockFile && fl->lockFile != INVALID_HANDLE_VALUE) + { + fclose(fl->lockFile); + unlink(fl->lockname); + } + } +#endif +} diff --git a/Src/nde/android/nde_c.cpp b/Src/nde/android/nde_c.cpp new file mode 100644 index 00000000..58adf2e2 --- /dev/null +++ b/Src/nde/android/nde_c.cpp @@ -0,0 +1,480 @@ +#include "nde_c.h" +#include "../nde.h" + +/* Database */ +nde_database_t NDE_CreateDatabase() +{ + return (nde_database_t)new Database(); +} + +void NDE_DestroyDatabase(nde_database_t db) +{ + delete (Database *)db; +} + +nde_table_t NDE_Database_OpenTable(nde_database_t db, const char *filename, const char *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, 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, 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(); +} + +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(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 char *query) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return scanner->Query(query); + else + return 0; +} + +void NDE_Scanner_Search(nde_scanner_t s, const char *search_term) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + scanner->Search(search_term); +} + +const char *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_NewFieldByName(nde_scanner_t s, const char *name) +{ + Scanner *scanner = (Scanner *)s; + if (scanner) + return (nde_field_t)scanner->NewFieldByName(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(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 char *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, char *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 char *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, char *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; +} +/* 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, char *nde_string) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + field->SetNDEString(nde_string); +} + +char *NDE_StringField_GetString(nde_field_t f) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + return field->GetString(); + else + return 0; +} + +void NDE_StringField_SetString(nde_field_t f, const char *str) +{ + StringField *field = (StringField *)(Field *)f; + if (field) + field->SetString(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); +} + +/* 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 char *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; +} diff --git a/Src/nde/android/nde_c.h b/Src/nde/android/nde_c.h new file mode 100644 index 00000000..c7554262 --- /dev/null +++ b/Src/nde/android/nde_c.h @@ -0,0 +1,122 @@ +#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 <foundation/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 */ + +NDE_API void NDE_Init(); +NDE_API void NDE_Quit(); +NDE_API nde_database_t NDE_CreateDatabase(); +NDE_API void NDE_DestroyDatabase(nde_database_t db); +NDE_API nde_table_t NDE_Database_OpenTable(nde_database_t db, const char *filename, const char *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 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 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); +#ifdef __cplusplus +NDE_API void NDE_Table_Compact(nde_table_t table, int *progress=0); +#else +NDE_API void NDE_Table_Compact(nde_table_t table, int *progress); +#endif +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 char *query); +NDE_API void NDE_Scanner_Search(nde_scanner_t scanner, const char *search_term); +NDE_API const char *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); +#ifdef __cplusplus +NDE_API void NDE_Scanner_First(nde_scanner_t scanner, int *killswitch=0); +NDE_API int NDE_Scanner_Next(nde_scanner_t scanner, int *killswitch=0); +#else +NDE_API void NDE_Scanner_First(nde_scanner_t scanner, int *killswitch); +NDE_API int NDE_Scanner_Next(nde_scanner_t scanner, int *killswitch); +#endif +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_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); +#ifdef __cplusplus +NDE_API int NDE_Scanner_LocateInteger(nde_scanner_t scanner, unsigned char id, int from, int value, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +NDE_API int NDE_Scanner_LocateString(nde_scanner_t scanner, unsigned char id, int from, const char *value, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, char *value, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const char *value, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, char *value, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +NDE_API int NDE_Scanner_LocateField(nde_scanner_t scanner, unsigned char id, int from, nde_field_t field, int *nskip=0, int compare_mode=COMPARE_MODE_EXACT); +#else +NDE_API int NDE_Scanner_LocateInteger(nde_scanner_t scanner, unsigned char id, int from, int value, int *nskip, int compare_mode); +NDE_API int NDE_Scanner_LocateString(nde_scanner_t scanner, unsigned char id, int from, const char *value, int *nskip, int compare_mode); +NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, char *value, int *nskip, int compare_mode); +NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const char *value, int *nskip, int compare_mode); +NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, char *value, int *nskip, int compare_mode); +NDE_API int NDE_Scanner_LocateField(nde_scanner_t scanner, unsigned char id, int from, nde_field_t field, int *nskip, int compare_mode); +#endif +NDE_API void NDE_Scanner_DeleteField(nde_scanner_t scanner, nde_field_t field); +NDE_API void NDE_Scanner_RemoveFilters(nde_scanner_t scanner); + +/* 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 char *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, char *nde_string); +NDE_API void NDE_StringField_SetString(nde_field_t field, const char *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 */ + +/* 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 char *NDE_ColumnField_GetFieldName(nde_field_t field); +NDE_API unsigned char NDE_ColumnField_GetDataType(nde_field_t field); + +#ifdef __cplusplus +} +#endif
\ No newline at end of file diff --git a/Src/nde/android/nde_init.cpp b/Src/nde/android/nde_init.cpp new file mode 100644 index 00000000..5096db4f --- /dev/null +++ b/Src/nde/android/nde_init.cpp @@ -0,0 +1,12 @@ +#include "../nde_c.h" + +/* NDE_Init isn't thread safe, be aware +best to call on the main thread during initialization +*/ +void NDE_Init() +{ +} + +void NDE_Quit() +{ +}
\ No newline at end of file diff --git a/Src/nde/android/record.h b/Src/nde/android/record.h new file mode 100644 index 00000000..accb2d8a --- /dev/null +++ b/Src/nde/android/record.h @@ -0,0 +1,48 @@ +#pragma once + +/* + +android (linux) implementation + +*/ +#include "../field.h" +#include "Vfs.h" +#include <stdio.h> + +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); + +protected: + void Undelete(void); + + int InsertionPoint; // TODO: benski> might be able to pass this into WriteFields/WriteIndex + int ref_count; + typedef nu::PtrDeque2<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 char *name); + int WriteFields(Table *ParentTable, int RecordIndex); + int WriteIndex(Table *ParentTable, int RecordIndex); + void Delete(Table *ParentTable, int RecordIndex); + typedef bool (*FieldsWalker)(Record *record, Field *entry, void *context); + void WalkFields(FieldsWalker callback, void *context); +}; + diff --git a/Src/nde/android/vfs.h b/Src/nde/android/vfs.h new file mode 100644 index 00000000..25d06d8f --- /dev/null +++ b/Src/nde/android/vfs.h @@ -0,0 +1,67 @@ +#ifndef __NDE_VFS_H +#define __NDE_VFS_H + +#include <foundation/types.h> +#include <stdio.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 + +typedef struct { + uint8_t *data; + unsigned long ptr; + unsigned long filesize; + unsigned long maxsize; + char *filename; + char mode; + int cached; + int dirty; + +#ifdef NDE_ALLOW_NONCACHED + FILE *rfile; +#endif + FILE *lockFile; + char lockname[1024]; + + int locks; +} VFILE; + + +#ifdef __cplusplus +extern "C" { +#endif + +VFILE *Vfnew(const char *fl, const char *mode, int Cached); +VFILE *Vfopen(VFILE *f, const char *fl, const char *mode, int Cached); +size_t Vfread(void *ptr, size_t size, VFILE *buf); +void Vfseek(VFILE *fl, long i, int whence); +unsigned long 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 + |