aboutsummaryrefslogtreecommitdiff
path: root/Src/nde
diff options
context:
space:
mode:
Diffstat (limited to 'Src/nde')
-rw-r--r--Src/nde/Android.mk15
-rw-r--r--Src/nde/Binary32Field.h8
-rw-r--r--Src/nde/BinaryField.h8
-rw-r--r--Src/nde/CRC.H7
-rw-r--r--Src/nde/ColumnField.h8
-rw-r--r--Src/nde/Crc.cpp88
-rw-r--r--Src/nde/DBUtils.cpp573
-rw-r--r--Src/nde/DBUtils.h73
-rw-r--r--Src/nde/Database.cpp76
-rw-r--r--Src/nde/Database.h48
-rw-r--r--Src/nde/Field.cpp281
-rw-r--r--Src/nde/Field.h110
-rw-r--r--Src/nde/FilenameField.h8
-rw-r--r--Src/nde/Filter.cpp69
-rw-r--r--Src/nde/Filter.h37
-rw-r--r--Src/nde/Index.cpp787
-rw-r--r--Src/nde/Index.h83
-rw-r--r--Src/nde/IndexField.h8
-rw-r--r--Src/nde/IndexRecord.h8
-rw-r--r--Src/nde/Int128Field.cpp126
-rw-r--r--Src/nde/Int128Field.h38
-rw-r--r--Src/nde/Int64Field.cpp124
-rw-r--r--Src/nde/Int64Field.h39
-rw-r--r--Src/nde/IntegerField.h8
-rw-r--r--Src/nde/LinkedList.cpp126
-rw-r--r--Src/nde/LinkedList.h82
-rw-r--r--Src/nde/NDEString.cpp84
-rw-r--r--Src/nde/NDEString.h31
-rw-r--r--Src/nde/Query.h8
-rw-r--r--Src/nde/Record.h8
-rw-r--r--Src/nde/Scanner.h8
-rw-r--r--Src/nde/StringField.h8
-rw-r--r--Src/nde/Table.h8
-rw-r--r--Src/nde/Vfs.h8
-rw-r--r--Src/nde/android/Binary32Field.cpp79
-rw-r--r--Src/nde/android/Binary32Field.h33
-rw-r--r--Src/nde/android/BinaryField.cpp173
-rw-r--r--Src/nde/android/BinaryField.h38
-rw-r--r--Src/nde/android/ColumnField.cpp175
-rw-r--r--Src/nde/android/ColumnField.h47
-rw-r--r--Src/nde/android/FilenameField.cpp128
-rw-r--r--Src/nde/android/FilenameField.h26
-rw-r--r--Src/nde/android/IndexField.cpp142
-rw-r--r--Src/nde/android/IndexField.h37
-rw-r--r--Src/nde/android/IndexRecord.cpp148
-rw-r--r--Src/nde/android/IndexRecord.h23
-rw-r--r--Src/nde/android/IntegerField.cpp1015
-rw-r--r--Src/nde/android/IntegerField.h93
-rw-r--r--Src/nde/android/Query.cpp973
-rw-r--r--Src/nde/android/Query.h30
-rw-r--r--Src/nde/android/Record.cpp124
-rw-r--r--Src/nde/android/Scanner.cpp1196
-rw-r--r--Src/nde/android/Scanner.h148
-rw-r--r--Src/nde/android/StringField.cpp292
-rw-r--r--Src/nde/android/StringField.h50
-rw-r--r--Src/nde/android/Table.cpp846
-rw-r--r--Src/nde/android/Table.h166
-rw-r--r--Src/nde/android/Vfs.cpp539
-rw-r--r--Src/nde/android/nde_c.cpp480
-rw-r--r--Src/nde/android/nde_c.h122
-rw-r--r--Src/nde/android/nde_init.cpp12
-rw-r--r--Src/nde/android/record.h48
-rw-r--r--Src/nde/android/vfs.h67
-rw-r--r--Src/nde/nde.def33
-rw-r--r--Src/nde/nde.h168
-rw-r--r--Src/nde/nde.rc76
-rw-r--r--Src/nde/nde.sln77
-rw-r--r--Src/nde/nde.vcxproj321
-rw-r--r--Src/nde/nde.vcxproj.filters176
-rw-r--r--Src/nde/nde.xcodeproj/project.pbxproj427
-rw-r--r--Src/nde/nde_c.h8
-rw-r--r--Src/nde/nde_defines.h69
-rw-r--r--Src/nde/osx/Binary32Field.cpp78
-rw-r--r--Src/nde/osx/Binary32Field.h32
-rw-r--r--Src/nde/osx/BinaryField.cpp172
-rw-r--r--Src/nde/osx/BinaryField.h38
-rw-r--r--Src/nde/osx/ColumnField.cpp160
-rw-r--r--Src/nde/osx/ColumnField.h49
-rw-r--r--Src/nde/osx/FilenameField.cpp45
-rw-r--r--Src/nde/osx/FilenameField.h23
-rw-r--r--Src/nde/osx/IndexField.cpp126
-rw-r--r--Src/nde/osx/IndexField.h37
-rw-r--r--Src/nde/osx/IndexRecord.cpp170
-rw-r--r--Src/nde/osx/IndexRecord.h19
-rw-r--r--Src/nde/osx/IntegerField.cpp1018
-rw-r--r--Src/nde/osx/IntegerField.h93
-rw-r--r--Src/nde/osx/Query.cpp936
-rw-r--r--Src/nde/osx/Query.h27
-rw-r--r--Src/nde/osx/Record.cpp138
-rw-r--r--Src/nde/osx/Record.h50
-rw-r--r--Src/nde/osx/Scanner.cpp1283
-rw-r--r--Src/nde/osx/Scanner.h210
-rw-r--r--Src/nde/osx/StringField.cpp297
-rw-r--r--Src/nde/osx/StringField.h50
-rw-r--r--Src/nde/osx/Table.cpp659
-rw-r--r--Src/nde/osx/Table.h165
-rw-r--r--Src/nde/osx/Vfs.cpp542
-rw-r--r--Src/nde/osx/Vfs.h66
-rw-r--r--Src/nde/osx/nde_c.cpp640
-rw-r--r--Src/nde/osx/nde_c.h246
-rw-r--r--Src/nde/resource.h14
-rw-r--r--Src/nde/version.rc239
-rw-r--r--Src/nde/win/Binary32Field.cpp77
-rw-r--r--Src/nde/win/Binary32Field.h32
-rw-r--r--Src/nde/win/BinaryField.cpp172
-rw-r--r--Src/nde/win/BinaryField.h38
-rw-r--r--Src/nde/win/ColumnField.cpp184
-rw-r--r--Src/nde/win/ColumnField.h49
-rw-r--r--Src/nde/win/FilenameField.cpp128
-rw-r--r--Src/nde/win/FilenameField.h26
-rw-r--r--Src/nde/win/IndexField.cpp145
-rw-r--r--Src/nde/win/IndexField.h37
-rw-r--r--Src/nde/win/IndexRecord.cpp169
-rw-r--r--Src/nde/win/IndexRecord.h18
-rw-r--r--Src/nde/win/IntegerField.cpp1011
-rw-r--r--Src/nde/win/IntegerField.h95
-rw-r--r--Src/nde/win/Query.cpp961
-rw-r--r--Src/nde/win/Query.h30
-rw-r--r--Src/nde/win/Record.cpp133
-rw-r--r--Src/nde/win/Record.h50
-rw-r--r--Src/nde/win/Scanner.cpp1238
-rw-r--r--Src/nde/win/Scanner.h154
-rw-r--r--Src/nde/win/StringField.cpp341
-rw-r--r--Src/nde/win/StringField.h50
-rw-r--r--Src/nde/win/Table.cpp886
-rw-r--r--Src/nde/win/Table.h170
-rw-r--r--Src/nde/win/Vfs.cpp599
-rw-r--r--Src/nde/win/Vfs.h98
-rw-r--r--Src/nde/win/nde_c.cpp561
-rw-r--r--Src/nde/win/nde_c.h131
-rw-r--r--Src/nde/win/nde_init.cpp36
131 files changed, 26647 insertions, 0 deletions
diff --git a/Src/nde/Android.mk b/Src/nde/Android.mk
new file mode 100644
index 00000000..7a394aef
--- /dev/null
+++ b/Src/nde/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := nde
+LOCAL_CFLAGS += -D__ANDROID__
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
+# make base stuff
+LOCAL_SRC_FILES := Crc.cpp Database.cpp DBUtils.cpp Field.cpp Filter.cpp Index.cpp Int64Field.cpp Int128Field.cpp LinkedList.cpp NDEString.cpp
+# make platform-specific field classes
+LOCAL_SRC_FILES += android/Binary32Field.cpp android/BinaryField.cpp android/ColumnField.cpp android/FilenameField.cpp android/IndexField.cpp
+LOCAL_SRC_FILES += android/IntegerField.cpp android/StringField.cpp
+# make rest of platform-specific classes
+LOCAL_SRC_FILES += android/IndexRecord.cpp android/nde_c.cpp android/nde_init.cpp android/Query.cpp android/Record.cpp android/Scanner.cpp android/Table.cpp android/Vfs.cpp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/Src/nde/Binary32Field.h b/Src/nde/Binary32Field.h
new file mode 100644
index 00000000..518c21ad
--- /dev/null
+++ b/Src/nde/Binary32Field.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Binary32Field.h"
+#elif defined(_WIN32)
+#include "win/Binary32Field.h"
+#elif defined(__ANDROID__)
+#include "android/Binary32Field.h"
+#endif
diff --git a/Src/nde/BinaryField.h b/Src/nde/BinaryField.h
new file mode 100644
index 00000000..3a7034f3
--- /dev/null
+++ b/Src/nde/BinaryField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/BinaryField.h"
+#elif defined(_WIN32)
+#include "win/BinaryField.h"
+#elif defined(__ANDROID__)
+#include "android/BinaryField.h"
+#endif
diff --git a/Src/nde/CRC.H b/Src/nde/CRC.H
new file mode 100644
index 00000000..6cd8ec4f
--- /dev/null
+++ b/Src/nde/CRC.H
@@ -0,0 +1,7 @@
+#ifndef __CRC_H
+#define __CRC_H
+
+ unsigned long int crc32file(char *name);
+ unsigned long int crc32str(char *name);
+
+#endif
diff --git a/Src/nde/ColumnField.h b/Src/nde/ColumnField.h
new file mode 100644
index 00000000..02f9b1f1
--- /dev/null
+++ b/Src/nde/ColumnField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/ColumnField.h"
+#elif defined(_WIN32)
+#include "win/ColumnField.h"
+#elif defined(__ANDROID__)
+#include "android/ColumnField.h"
+#endif
diff --git a/Src/nde/Crc.cpp b/Src/nde/Crc.cpp
new file mode 100644
index 00000000..426dabc6
--- /dev/null
+++ b/Src/nde/Crc.cpp
@@ -0,0 +1,88 @@
+#include "nde.h"
+#include <stdio.h>
+
+static unsigned int crc_32_tab[] = {
+ 0xD202Ef8dL, 0xA505DF1BL, 0x3C0C8EA1L, 0x4B0BBE37L, 0xD56F2B94L, 0xA2681B02L, 0x3B614AB8L, 0x4C667A2EL,
+ 0xDCD967BFL, 0xABDE5729L, 0x32D70693L, 0x45D03605L, 0xDBB4A3A6L, 0xACB39330L, 0x35BAC28AL, 0x42BDF21CL,
+ 0xCFB5FFE9L, 0xB8B2CF7FL, 0x21BB9EC5L, 0x56BCAE53L, 0xC8D83BF0L, 0xBFDF0B66L, 0x26D65ADCL, 0x51D16A4AL,
+ 0xC16E77DBL, 0xB669474DL, 0x2F6016F7L, 0x58672661L, 0xC603B3C2L, 0xB1048354L, 0x280DD2EEL, 0x5F0AE278L,
+ 0xE96CCF45L, 0x9E6BFFD3L, 0x0762AE69L, 0x70659EFFL, 0xEE010B5CL, 0x99063BCAL, 0x000F6A70L, 0x77085AE6L,
+ 0xE7B74777L, 0x90B077E1L, 0x09B9265BL, 0x7EBE16CDL, 0xE0DA836EL, 0x97DDB3F8L, 0x0ED4E242L, 0x79D3D2D4L,
+ 0xF4DBDF21L, 0x83DCEFB7L, 0x1AD5BE0DL, 0x6DD28E9BL, 0xF3B61B38L, 0x84B12BAEL, 0x1DB87A14L, 0x6ABF4A82L,
+ 0xFA005713L, 0x8D076785L, 0x140E363FL, 0x630906A9L, 0xFD6D930AL, 0x8A6AA39CL, 0x1363F226L, 0x6464C2B0L,
+ 0xA4DEAE1DL, 0xD3D99E8BL, 0x4AD0CF31L, 0x3DD7FFA7L, 0xA3B36A04L, 0xD4B45A92L, 0x4DBD0B28L, 0x3ABA3BBEL,
+ 0xAA05262FL, 0xDD0216B9L, 0x440B4703L, 0x330C7795L, 0xAD68E236L, 0xDA6FD2A0L, 0x4366831AL, 0x3461B38CL,
+ 0xB969BE79L, 0xCE6E8EEFL, 0x5767DF55L, 0x2060EFC3L, 0xBE047A60L, 0xC9034AF6L, 0x500A1B4CL, 0x270D2BDAL,
+ 0xB7B2364BL, 0xC0B506DDL, 0x59BC5767L, 0x2EBB67F1L, 0xB0DFF252L, 0xC7D8C2C4L, 0x5ED1937EL, 0x29D6A3E8L,
+ 0x9FB08ED5L, 0xE8B7BE43L, 0x71BEEFF9L, 0x06B9DF6FL, 0x98DD4ACCL, 0xEFDA7A5AL, 0x76D32BE0L, 0x01D41B76L,
+ 0x916B06E7L, 0xE66C3671L, 0x7F6567CBL, 0x0862575DL, 0x9606C2FEL, 0xE101F268L, 0x7808A3D2L, 0x0F0F9344L,
+ 0x82079EB1L, 0xF500AE27L, 0x6C09FF9DL, 0x1B0ECF0BL, 0x856A5AA8L, 0xF26D6A3EL, 0x6B643B84L, 0x1C630B12L,
+ 0x8CDC1683L, 0xFBDB2615L, 0x62D277AFL, 0x15D54739L, 0x8BB1D29AL, 0xFCB6E20CL, 0x65BFB3B6L, 0x12B88320L,
+ 0x3FBA6CADL, 0x48BD5C3BL, 0xD1B40D81L, 0xA6B33D17L, 0x38D7A8B4L, 0x4FD09822L, 0xD6D9C998L, 0xA1DEF90EL,
+ 0x3161E49FL, 0x4666D409L, 0xDF6F85B3L, 0xA868B525L, 0x360C2086L, 0x410B1010L, 0xD80241AAL, 0xAF05713CL,
+ 0x220D7CC9L, 0x550A4C5FL, 0xCC031DE5L, 0xBB042D73L, 0x2560B8D0L, 0x52678846L, 0xCB6ED9FCL, 0xBC69E96AL,
+ 0x2CD6F4FBL, 0x5BD1C46DL, 0xC2D895D7L, 0xB5DFA541L, 0x2BBB30E2L, 0x5CBC0074L, 0xC5B551CEL, 0xB2B26158L,
+ 0x04D44C65L, 0x73D37CF3L, 0xEADA2D49L, 0x9DDD1DDFL, 0x03B9887CL, 0x74BEB8EAL, 0xEDB7E950L, 0x9AB0D9C6L,
+ 0x0A0FC457L, 0x7D08F4C1L, 0xE401A57BL, 0x930695EDL, 0x0D62004EL, 0x7A6530D8L, 0xE36C6162L, 0x946B51F4L,
+ 0x19635C01L, 0x6E646C97L, 0xF76D3D2DL, 0x806A0DBBL, 0x1E0E9818L, 0x6909A88EL, 0xF000F934L, 0x8707C9A2L,
+ 0x17B8D433L, 0x60BFE4A5L, 0xF9B6B51FL, 0x8EB18589L, 0x10D5102AL, 0x67D220BCL, 0xFEDB7106L, 0x89DC4190L,
+ 0x49662D3DL, 0x3E611DABL, 0xA7684C11L, 0xD06F7C87L, 0x4E0BE924L, 0x390CD9B2L, 0xA0058808L, 0xD702B89EL,
+ 0x47BDA50FL, 0x30BA9599L, 0xA9B3C423L, 0xDEB4F4B5L, 0x40D06116L, 0x37D75180L, 0xAEDE003AL, 0xD9D930ACL,
+ 0x54D13D59L, 0x23D60DCFL, 0xBADF5C75L, 0xCDD86CE3L, 0x53BCF940L, 0x24BBC9D6L, 0xBDB2986CL, 0xCAB5A8FAL,
+ 0x5A0AB56BL, 0x2D0D85FDL, 0xB404D447L, 0xC303E4D1L, 0x5D677172L, 0x2A6041E4L, 0xB369105EL, 0xC46E20C8L,
+ 0x72080DF5L, 0x050F3D63L, 0x9C066CD9L, 0xEB015C4FL, 0x7565C9ECL, 0x0262F97AL, 0x9B6BA8C0L, 0xEC6C9856L,
+ 0x7CD385C7L, 0x0BD4B551L, 0x92DDE4EBL, 0xE5DAD47DL, 0x7BBE41DEL, 0x0CB97148L, 0x95B020F2L, 0xE2B71064L,
+ 0x6FBF1D91L, 0x18B82D07L, 0x81B17CBDL, 0xF6B64C2BL, 0x68D2D988L, 0x1FD5E91EL, 0x86DCB8A4L, 0xF1DB8832L,
+ 0x616495A3L, 0x1663A535L, 0x8F6AF48FL, 0xF86DC419L, 0x660951BAL, 0x110E612CL, 0x88073096L, 0xFF000000L
+};
+
+#define UPDC32(octet, crc) (crc_32_tab[((crc) ^ (octet)) & 0xff] ^ ((crc) >> 8))
+
+unsigned long int crc32file(char *name)
+{
+ register FILE *fin;
+ register unsigned int oldcrc32;
+ register unsigned int crc32;
+ register unsigned int oldcrc;
+ register unsigned char c;
+
+ oldcrc32 = 0;
+ if ((fin=fopen(name, "rb"))==NULL)
+ return 0;
+ while (1) {
+ c=getc(fin);
+ if (feof(fin)) break;
+ oldcrc32 = UPDC32(c, oldcrc32);
+ }
+
+ if (ferror(fin)){
+ fclose(fin);
+ return 0;
+ }
+ else {
+ crc32 = oldcrc32; oldcrc = oldcrc32 = ~oldcrc32;
+
+ fclose(fin);
+
+ return crc32;
+ }
+}
+
+unsigned long int crc32str(char *name)
+{
+ register unsigned int oldcrc32;
+ register unsigned int crc32;
+ register unsigned char c;
+ register int a=0;
+
+ oldcrc32 = 0;
+ while (name[a] != 0) {
+ c=name[a];
+ oldcrc32 = UPDC32(c, oldcrc32);
+ a++;
+ }
+
+ crc32 = oldcrc32; oldcrc32 = oldcrc32;
+
+ return crc32;
+} \ No newline at end of file
diff --git a/Src/nde/DBUtils.cpp b/Src/nde/DBUtils.cpp
new file mode 100644
index 00000000..93958a2c
--- /dev/null
+++ b/Src/nde/DBUtils.cpp
@@ -0,0 +1,573 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ All Purposes Functions
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "BinaryField.h"
+#include "Binary32Field.h"
+#include "vfs.h"
+#include "ColumnField.h"
+#include "IndexField.h"
+#include "StringField.h"
+#include "FilenameField.h"
+#include "IntegerField.h"
+#include "Int64Field.h"
+#include "Int128Field.h"
+#include <stdio.h>
+#include <string.h>
+
+#ifdef _WIN32
+int (WINAPI *findNLSString)(LCID Locale, DWORD dwFindNLSStringFlags, LPCWSTR lpStringSource, int cchSource, LPCWSTR lpStringValue, int cchValue, LPINT pcchFound) = NDE_FindNLSString;
+#endif
+
+//---------------------------------------------------------------------------
+bool CompatibleFields(unsigned char oldType, unsigned char newType)
+{
+ if (oldType == newType) // duh :)
+ return true;
+ // going from an int field to another int equivalent field is OK
+ if ((oldType == FIELD_INTEGER || oldType == FIELD_BOOLEAN || oldType == FIELD_DATETIME || oldType == FIELD_LENGTH || oldType == FIELD_INT64) &&
+ (newType == FIELD_INTEGER || newType == FIELD_BOOLEAN || newType == FIELD_DATETIME || newType == FIELD_LENGTH || newType == FIELD_INT64)) {
+ return true;
+ }
+
+ // going from string to filename or filename to string is OK
+ if ((oldType == FIELD_FILENAME && newType == FIELD_STRING)
+ || (oldType == FIELD_STRING && newType == FIELD_FILENAME))
+ {
+ return true;
+ }
+ return false;
+}
+
+//---------------------------------------------------------------------------
+uint32_t AllocNewPos(VFILE *Handle)
+{
+ Vfseek(Handle, 0, SEEK_END);
+ return Vftell(Handle);
+}
+
+//---------------------------------------------------------------------------
+Field *TranslateObject(unsigned char Type, Table *tbl)
+{
+ switch (Type)
+ {
+ case FIELD_COLUMN: //0
+ return new ColumnField();
+ case FIELD_INDEX: //1
+ return new IndexField();
+ case FIELD_STRING: // 3
+ return new StringField();
+ case FIELD_INTEGER: // 4
+ return new IntegerField();
+ case FIELD_BINARY: // 6
+ return new BinaryField();
+ case FIELD_DATETIME: // 10
+ return new DateTimeField();
+ case FIELD_LENGTH: // 11
+ return new LengthField();
+ case FIELD_FILENAME: // 12
+ return new FilenameField();
+ case FIELD_INT64: // 13
+ return new Int64Field();
+ case FIELD_BINARY32: // 14
+ return new Binary32Field();
+ case FIELD_INT128: // 15
+ return new Int128Field();
+ default:
+#ifdef WIN32
+ if (!tbl->HasErrors())
+ {
+ //MessageBox(plugin.hwndParent, "Your database has been corrupted!\n\nWinamp will try to continue, but some of the library metadata may be lost :(", "Database Error", 0);
+ }
+#else
+ printf("NDE Error: unknown field type encountered\n");
+#endif
+ tbl->IncErrorCount();
+ return new Field();
+ }
+}
+
+
+//---------------------------------------------------------------------------
+#ifndef __ANDROID__
+const void *memmem(const void *a, const void *b, size_t s, size_t l)
+{
+ size_t n = s - l;
+ while (n--)
+ {
+ if (!memcmp(a, b, l))
+ return a;
+ a = (const uint8_t *)a + 1;
+ }
+ return NULL;
+}
+#endif
+
+#ifdef _WIN32
+// a faster way of doing min(wcslen(str), _len)
+static size_t nde_wcsnlen(const wchar_t *str, size_t _len)
+{
+ size_t len = 0;
+ while (str && *str++)
+ {
+ if (_len == len)
+ return len;
+ len++;
+ }
+ return len;
+}
+
+// len must be <= wcslen(b)
+static int nde_wcsnicmp(const wchar_t *a, const wchar_t *b, size_t len)
+{
+ return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, a, (int)nde_wcsnlen(a, len), b, (int)len) - 2;
+}
+
+/* this is a VERY LIMITED emulation of the vista only function. it ONLY supports the ways we're currently call it. it's also slow. */
+int WINAPI NDE_FindNLSString(LCID Locale, DWORD dwFindNLSStringFlags, LPCWSTR lpStringSource, int cchSource, LPCWSTR lpStringValue, int cchValue, LPINT pcchFound)
+{
+ dwFindNLSStringFlags &= ~NORM_LINGUISTIC_CASING; // remove on XP and below, not supported
+ if (dwFindNLSStringFlags & FIND_STARTSWITH)
+ {
+ dwFindNLSStringFlags &= ~FIND_STARTSWITH; // clear flag
+ size_t len = wcslen(lpStringValue);
+ if (CompareStringW(Locale, dwFindNLSStringFlags, lpStringSource, (int)nde_wcsnlen(lpStringSource, len), lpStringValue, (int)len) == CSTR_EQUAL)
+ return 0;
+ else
+ return -1;
+ }
+ else if (dwFindNLSStringFlags & FIND_ENDSWITH)
+ {
+ dwFindNLSStringFlags &= ~FIND_ENDSWITH; // clear flag
+ int lenp = (int)wcslen(lpStringValue), lend = (int)wcslen(lpStringSource);
+ if (lend < lenp) return -1; // too short
+ if (CompareStringW(Locale, dwFindNLSStringFlags, lpStringSource+lend-lenp, -1, lpStringValue, -1) == CSTR_EQUAL)
+ return 0;
+ else
+ return -1;
+ }
+ else if (dwFindNLSStringFlags & FIND_FROMSTART)
+ {
+ dwFindNLSStringFlags &= ~FIND_FROMSTART; // clear flag
+ int s2len = (int)wcslen(lpStringValue);
+ int s1len = (int)wcslen(lpStringSource);
+ const wchar_t *p;
+ for (p = lpStringSource;*p && s1len >= s2len;p++,s1len--)
+ if (CompareStringW(Locale, dwFindNLSStringFlags, p, min(s1len, s2len), lpStringValue, (int)s2len) == CSTR_EQUAL)
+ return 0;
+ return -1;
+ }
+
+ return -1;
+}
+
+int nde_wcsicmp(const wchar_t *a, const wchar_t *b)
+{
+ return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE|NORM_IGNORENONSPACE, a, -1, b, -1) - 2;
+}
+
+bool nde_wcsbegins(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_STARTSWITH|NORM_LINGUISTIC_CASING|NORM_IGNORECASE|NORM_IGNORENONSPACE, a, -1, b, -1, 0);
+ return (index != -1);
+}
+
+bool nde_wcsends(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_ENDSWITH|NORM_LINGUISTIC_CASING|NORM_IGNORECASE|NORM_IGNORENONSPACE, a, -1, b, -1, 0);
+ return (index != -1);
+}
+
+bool nde_wcscontains(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_FROMSTART|NORM_LINGUISTIC_CASING|NORM_IGNORECASE|NORM_IGNORENONSPACE, a, -1, b, -1, 0);
+ return index != -1;
+}
+
+//---------------------------------------------------------------------------
+
+int mywcsicmp(const wchar_t *a, const wchar_t *b)
+{
+ if (!a && !b) return 0;
+ if (!a && b) return 1;
+ if (!b) return -1;
+ int r = nde_wcsicmp(a, b);
+ return min(max(r, -1), 1);
+}
+
+int mywcsicmp_fn(const wchar_t *a, const wchar_t *b)
+{
+ if (!a && !b) return 0;
+ if (!a && b) return 1;
+ if (!b) return -1;
+ int r = nde_wcsicmp_fn(a, b);
+ return min(max(r, -1), 1);
+}
+
+int nde_wcsicmp_fn(const wchar_t *a, const wchar_t *b)
+{
+ return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, a, -1, b, -1) - 2;
+}
+
+bool nde_fnbegins(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_STARTSWITH|NORM_IGNORECASE, a, -1, b, -1, 0);
+ return (index != -1);
+}
+
+bool nde_fnends(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_ENDSWITH|NORM_IGNORECASE, a, -1, b, -1, 0);
+ return (index != -1);
+}
+
+bool nde_fncontains(const wchar_t *a, const wchar_t *b)
+{
+ int index = findNLSString(LOCALE_USER_DEFAULT, FIND_FROMSTART|NORM_IGNORECASE, a, -1, b, -1, 0);
+ return index != -1;
+}
+
+#endif
+
+#ifdef __ANDROID__
+
+//---------------------------------------------------------------------------
+// a faster way of doing min(wcslen(str), _len)
+size_t nde_strnlen(const char *str, size_t _len)
+{
+ size_t len = 0;
+ while (str && *str++)
+ {
+ if (_len == len)
+ return len;
+ len++;
+ }
+ return len;
+}
+
+// len must be <= strlen(b)
+int nde_strnicmp(const char *a, const char *b, size_t len)
+{
+ return strncasecmp(a,b,len);
+}
+
+int nde_strnicmp_ignore(const char *a, const char *b, size_t len)
+{
+ return strncasecmp(a,b,len);
+}
+
+char *stristr(const char *s1, const char *s2)
+{
+ size_t s2len = strlen(s2);
+ const char *p;
+ for (p = s1;*p;p++)
+ if (!nde_strnicmp(p, s2, s2len))
+ return (char *)p;
+ return NULL;
+}
+
+char *stristr_ignore(const char *s1, const char *s2)
+{
+ size_t s2len = strlen(s2);
+ const char *p;
+ for (p = s1;*p;p++)
+ if (!nde_strnicmp_ignore(p, s2, s2len))
+ return (char *)p;
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+
+int nde_stricmp(const char *a, const char *b)
+{
+ return strcasecmp(a,b);
+}
+
+int nde_stricmp_ignore(const char *a, const char *b)
+{
+ return strcasecmp(a,b); // TODO: maybe strcoll?
+}
+
+int mystricmp(const char *a, const char *b)
+{
+ if (!a && !b) return 0;
+ if (!a && b) return 1;
+ if (!b) return -1;
+ int r = nde_stricmp(a, b);
+ return min(max(r, -1), 1);
+}
+
+char* mystristr(const char *a, const char *b)
+{
+ return (!a || !b) ? NULL : stristr(a, b);
+}
+
+char* mystristr_fn(const char *a, const char *b)
+{
+ return (!a || !b) ? NULL : stristr_fn(a, b);
+}
+
+int mystricmp_fn(const char *a, const char *b)
+{
+ if (!a && !b) return 0;
+ if (!a && b) return 1;
+ if (!b) return -1;
+ int r = nde_stricmp_fn(a, b);
+ return min(max(r, -1), 1);
+}
+
+char *stristr_fn(const char *s1, const char *s2)
+{
+ size_t s2len = strlen(s2);
+ const char *p;
+ for (p = s1;*p;p++)
+ if (!nde_strnicmp_fn(p, s2, s2len))
+ return (char *)p;
+ return NULL;
+}
+
+int nde_stricmp_fn(const char *a, const char *b)
+{
+ return strcasecmp(a,b);
+}
+
+int nde_strnicmp_fn(const char *a, const char *b, size_t len)
+{
+ return strncasecmp(a,b, len);
+
+}
+
+static uint16_t swap_utf16LE(uint16_t value)
+{
+#ifdef BIG_ENDIAN
+ return (value >> 8) | (value << 8);
+#else
+ return value;
+#endif
+}
+
+static uint16_t swap_utf16BE(uint16_t value)
+{
+#ifdef LITTLE_ENDIAN
+ return (value >> 8) | (value << 8);
+#else
+ return value;
+#endif
+}
+
+static size_t utf16LE_to_ucs4_character(const uint16_t *utf16_string, size_t len, uint32_t *codepoint)
+{
+ uint16_t lead = swap_utf16LE(utf16_string[0]);
+ if (lead < 0xD800 || lead >= 0xE000)
+ {
+ return lead;
+ }
+
+ if (lead < 0xDC00)
+ {
+ if (len >= 2)
+ {
+ uint16_t trail = swap_utf16LE(utf16_string[1]);
+ if (trail >= 0xDC00 && trail < 0xE000)
+ {
+ *codepoint = 0x10000 + ((lead - 0xD800) << 10) + (trail - 0xDC00);
+ return 2;
+ }
+ }
+ }
+
+ *codepoint=0xFFFD; // invalid
+ return 1;
+}
+
+static size_t utf16BE_to_ucs4_character(const uint16_t *utf16_string, size_t len, uint32_t *codepoint)
+{
+ uint16_t lead = swap_utf16LE(utf16_string[0]);
+ if (lead < 0xD800 || lead >= 0xE000)
+ {
+ return lead;
+ }
+
+ if (lead < 0xDC00)
+ {
+ if (len >= 2)
+ {
+ uint16_t trail = swap_utf16LE(utf16_string[1]);
+ if (trail >= 0xDC00 && trail < 0xE000)
+ {
+ *codepoint = 0x10000 + ((lead - 0xD800) << 10) + (trail - 0xDC00);
+ return 2;
+ }
+ }
+ }
+
+ *codepoint=0xFFFD; // invalid
+ return 1;
+}
+
+static size_t ucs4count(uint32_t codepoint)
+{
+ if (codepoint < 0x80)
+ return 1;
+ else if (codepoint < 0x800)
+ return 2;
+ else if (codepoint < 0x10000)
+ return 3;
+ else if (codepoint < 0x200000)
+ return 4;
+ else if (codepoint < 0x4000000)
+ return 5;
+ else if (codepoint <= 0x7FFFFFFF)
+ return 6;
+ else
+ return 0;
+}
+
+static size_t ucs4_to_utf8_character(char *target, uint32_t codepoint, size_t max)
+{
+ size_t count = ucs4count(codepoint);
+
+ if (!count)
+ return 0;
+
+ if (count>max) return 0;
+
+ if (target == 0)
+ return count;
+
+ switch (count)
+ {
+ case 6:
+ target[5] = 0x80 | (codepoint & 0x3F);
+ codepoint = codepoint >> 6;
+ codepoint |= 0x4000000;
+ case 5:
+ target[4] = 0x80 | (codepoint & 0x3F);
+ codepoint = codepoint >> 6;
+ codepoint |= 0x200000;
+ case 4:
+ target[3] = 0x80 | (codepoint & 0x3F);
+ codepoint = codepoint >> 6;
+ codepoint |= 0x10000;
+ case 3:
+ target[2] = 0x80 | (codepoint & 0x3F);
+ codepoint = codepoint >> 6;
+ codepoint |= 0x800;
+ case 2:
+ target[1] = 0x80 | (codepoint & 0x3F);
+ codepoint = codepoint >> 6;
+ codepoint |= 0xC0;
+ case 1:
+ target[0] = codepoint;
+ }
+
+ return count;
+}
+
+size_t utf16LE_to_utf8(const uint16_t *src, size_t source_len, char *dst, size_t out_len)
+{
+ uint32_t codepoint=0xFFFD;
+ size_t position=0;
+ size_t characters_processed=0;
+
+ if (!dst) // they just want the size
+ {
+ while (source_len)
+ {
+ characters_processed = utf16LE_to_ucs4_character(src, source_len, &codepoint);
+ if (codepoint == 0xFFFD)
+ break;
+
+ if (!codepoint)
+ break;
+
+ source_len -= characters_processed;
+
+ characters_processed = ucs4count(codepoint);
+ if (!characters_processed)
+ break;
+
+ position+=characters_processed;
+ }
+ return position;
+ }
+
+ while(source_len && position<out_len)
+ {
+ characters_processed = utf16LE_to_ucs4_character(src, source_len, &codepoint);
+ if (codepoint == 0xFFFD)
+ break;
+
+ if (!codepoint)
+ break;
+
+ source_len -= characters_processed;
+
+ characters_processed=ucs4_to_utf8_character(&dst[position], codepoint, out_len-position);
+ if (!characters_processed)
+ break;
+ position+=characters_processed;
+ }
+ if (position<out_len)
+ dst[position]=0;
+ return position;
+}
+
+size_t utf16BE_to_utf8(const uint16_t *src, size_t source_len, char *dst, size_t out_len)
+{
+ uint32_t codepoint=0xFFFD;
+ size_t position=0;
+ size_t characters_processed=0;
+
+ if (!dst) // they just want the size
+ {
+ while (source_len)
+ {
+ characters_processed = utf16BE_to_ucs4_character(src, source_len, &codepoint);
+ if (codepoint == 0xFFFD)
+ break;
+
+ if (!codepoint)
+ break;
+
+ source_len -= characters_processed;
+
+ characters_processed = ucs4count(codepoint);
+ if (!characters_processed)
+ break;
+
+ position+=characters_processed;
+ }
+ return position;
+ }
+
+ while(source_len && position<out_len)
+ {
+ characters_processed = utf16BE_to_ucs4_character(src, source_len, &codepoint);
+ if (codepoint == 0xFFFD)
+ break;
+
+ if (!codepoint)
+ break;
+
+ source_len -= characters_processed;
+
+ characters_processed=ucs4_to_utf8_character(&dst[position], codepoint, out_len-position);
+ if (!characters_processed)
+ break;
+ position+=characters_processed;
+ }
+ if (position<out_len)
+ dst[position]=0;
+ return position;
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/DBUtils.h b/Src/nde/DBUtils.h
new file mode 100644
index 00000000..7a446d5a
--- /dev/null
+++ b/Src/nde/DBUtils.h
@@ -0,0 +1,73 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ All Purposes Functions Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __DBUTILS_H
+#define __DBUTILS_H
+
+#include <stdio.h>
+#include "vfs.h"
+#include "field.h"
+
+bool CompatibleFields(unsigned char oldType, unsigned char newType);
+uint32_t AllocNewPos(VFILE *Handle);
+Field *TranslateObject(unsigned char Type, Table *tbl);
+
+#ifndef __ANDROID__
+const void *memmem(const void *a, const void *b, size_t s, size_t l);
+#endif
+
+#ifdef __ANDROID__
+char *stristr(const char *s1, const char *s2);
+int mystricmp(const char *a, const char *b);
+char* mystristr(const char *a, const char *b);
+int nde_stricmp(const char *a, const char *b);
+int nde_stricmp_ignore(const char *a, const char *b); // ignores accents
+int nde_strnicmp(const char *a, const char *b, size_t len); // len must be <= strlen(b)
+int nde_strnicmp_ignore(const char *a, const char *b, size_t len); // ignores accents
+char *stristr_ignore(const char *s1, const char *s2);
+// filesystem safe versions
+char* mystristr_fn(const char *a, const char *b);
+int mystricmp_fn(const char *a, const char *b);
+char *stristr_fn(const char *s1, const char *s2);
+extern "C" int NDE_API nde_stricmp_fn(const char *a, const char *b);
+int nde_strnicmp_fn(const char *a, const char *b, size_t len); // len must be <= strlen(b)
+
+size_t utf16LE_to_utf8(const uint16_t *src, size_t source_len, char *dst, size_t out_len);
+size_t utf16BE_to_utf8(const uint16_t *src, size_t source_len, char *dst, size_t out_len);
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+int WINAPI NDE_FindNLSString(LCID Locale, DWORD dwFindNLSStringFlags, LPCWSTR lpStringSource, int cchSource, LPCWSTR lpStringValue, int cchValue, LPINT pcchFound);
+extern int (WINAPI *findNLSString)(LCID Locale, DWORD dwFindNLSStringFlags, LPCWSTR lpStringSource, int cchSource, LPCWSTR lpStringValue, int cchValue, LPINT pcchFound);
+bool nde_wcsbegins(const wchar_t *a, const wchar_t *b);
+bool nde_wcsends(const wchar_t *a, const wchar_t *b);
+bool nde_wcscontains(const wchar_t *a, const wchar_t *b);
+
+int mywcsicmp(const wchar_t *a, const wchar_t *b);
+int nde_wcsicmp(const wchar_t *a, const wchar_t *b);
+
+// filesystem safe versions
+bool nde_fnbegins(const wchar_t *a, const wchar_t *b);
+bool nde_fnends(const wchar_t *a, const wchar_t *b);
+bool nde_fncontains(const wchar_t *a, const wchar_t *b);
+
+const wchar_t* mywcsistr_fn(const wchar_t *a, const wchar_t *b);
+int mywcsicmp_fn(const wchar_t *a, const wchar_t *b);
+extern "C" int NDE_API nde_wcsicmp_fn(const wchar_t *a, const wchar_t *b);
+#endif
+
+#ifdef __APPLE__
+wchar_t *_wcsdup(const wchar_t *val);
+#endif
+
+#endif \ No newline at end of file
diff --git a/Src/nde/Database.cpp b/Src/nde/Database.cpp
new file mode 100644
index 00000000..2c9c35d7
--- /dev/null
+++ b/Src/nde/Database.cpp
@@ -0,0 +1,76 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Database Class
+
+--------------------------------------------------------------------------- */
+
+#include "Database.h"
+#include "Table.h"
+
+//---------------------------------------------------------------------------
+Database::Database()
+{
+#ifdef WIN32
+ hInstance = (HINSTANCE)0;
+#endif
+
+}
+
+#ifdef WIN32
+//---------------------------------------------------------------------------
+Database::Database(HINSTANCE hinst)
+{
+ hInstance = hinst;
+}
+#endif
+
+//---------------------------------------------------------------------------
+Database::~Database()
+{
+}
+
+#ifdef WIN32
+//---------------------------------------------------------------------------
+void Database::SetInstance(HINSTANCE inst) {
+ hInstance = inst;
+}
+
+HINSTANCE Database::GetInstance() {
+ return hInstance;
+}
+#endif
+
+//--------------------------------------------------------------------------
+#ifdef _WIN32
+Table *Database::OpenTable(const wchar_t *TableName, const wchar_t *IdxName, BOOL Create, BOOL Cached)
+#else
+Table *Database::OpenTable(const char *TableName, const char *IdxName, BOOL Create, BOOL Cached)
+#endif
+//char *tablefn, char*indexfn, BOOL create)
+{
+ Table *table = new Table(TableName, IdxName, Create, this, Cached);
+ if (table)
+ {
+ if (table->Open())
+ return table;
+ table->Close();
+ delete table;
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+void Database::CloseTable(Table *table)
+{
+ if (table)
+ {
+ table->Close();
+ delete table;
+ }
+} \ No newline at end of file
diff --git a/Src/nde/Database.h b/Src/nde/Database.h
new file mode 100644
index 00000000..a691c218
--- /dev/null
+++ b/Src/nde/Database.h
@@ -0,0 +1,48 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Database Class Prototypes
+
+ --------------------------------------------------------------------------- */
+
+#ifndef __DATABASE_H
+#define __DATABASE_H
+
+#include "nde.h"
+
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+class Database
+{
+public:
+ Database();
+#ifdef WIN32
+ Database(HINSTANCE hinst);
+ HINSTANCE GetInstance();
+ void SetInstance(HINSTANCE hinst);
+#endif
+ ~Database();
+#ifdef _WIN32
+ Table *OpenTable(const wchar_t *table, const wchar_t *index, BOOL create, BOOL cached);
+#else
+ Table *OpenTable(const char *table, const char *index, BOOL create, BOOL cached);
+#endif
+ void CloseTable(Table *table);
+private:
+#ifdef WIN32
+ HINSTANCE hInstance;
+#endif
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/Field.cpp b/Src/nde/Field.cpp
new file mode 100644
index 00000000..f57800e5
--- /dev/null
+++ b/Src/nde/Field.cpp
@@ -0,0 +1,281 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Field Class
+
+--------------------------------------------------------------------------- */
+
+#include "field.h"
+#include "vfs.h"
+#include "table.h"
+#include "nde.h"
+#include "dbutils.h"
+
+#ifdef WIN32
+#include <malloc.h>
+#elif defined(__APPLE__)
+#include <alloca.h>
+#endif
+
+//---------------------------------------------------------------------------
+void PUT_BINARY(uint8_t *dest, const uint8_t *src, size_t size, size_t pos)
+{
+ if (src && dest && size > 0)
+ memcpy(dest+pos, src, size);
+}
+
+//---------------------------------------------------------------------------
+void GET_BINARY(uint8_t *dest, const uint8_t *src, size_t size, size_t pos)
+{
+ if (dest && src && size > 0)
+ memcpy(dest, src+pos, size);
+}
+
+//---------------------------------------------------------------------------
+void PUT_FLOAT(float f, uint8_t *data, size_t pos)
+{
+ unsigned int y = *(const unsigned int *)&f;
+ data[pos]=(unsigned char)(y&255); data[pos+1]=(unsigned char)((y>>8)&255); data[pos+2]=(unsigned char)((y>>16)&255); data[pos+3]=(unsigned char)((y>>24)&255);
+}
+
+//---------------------------------------------------------------------------
+float GET_FLOAT(const uint8_t *data, size_t pos)
+{
+ int a = data[pos]|(data[pos+1]<<8)|(data[pos+2]<<16)|(data[pos+3]<<24);
+ float f = *(const float *)&a;
+ return f;
+}
+
+//---------------------------------------------------------------------------
+Field::Field(int FieldPos)
+{
+ InitField();
+ Pos = FieldPos;
+}
+
+//---------------------------------------------------------------------------
+void Field::InitField(void)
+{
+ Type = FIELD_UNKNOWN;
+ Pos = 0;
+ ID = 0;
+ MaxSizeOnDisk = 0;
+}
+
+//---------------------------------------------------------------------------
+Field::Field()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+Field::~Field()
+{
+}
+
+//---------------------------------------------------------------------------
+Field *Field::ReadField(Table *pTable, int pos, uint32_t *next_position)
+{
+ return ReadField(pTable, pos, false, next_position);
+}
+
+//---------------------------------------------------------------------------
+Field *Field::ReadField(Table *pTable, int pos, bool Quick, uint32_t *next_position)
+{
+ VFILE *HTable = pTable->Handle;
+ int newPos = pos;
+ int rType = FIELD_REDIRECTOR;
+ unsigned char oType;
+
+ if (!Vflock(HTable, false))
+ return 0;
+
+ while (rType == FIELD_REDIRECTOR)
+ {
+ Vfseek(HTable, Pos, SEEK_SET);
+ if (Vfread(&ID, sizeof(ID), HTable) != 1 ||
+ Vfread(&oType, sizeof(oType), HTable) != 1)
+ {
+ Vfunlock(HTable, false);
+ return NULL;
+ }
+ if (oType == FIELD_REDIRECTOR)
+ {
+ if (Vfread(&newPos, sizeof(newPos), HTable) != 1)
+ {
+ Vfunlock(HTable, false);
+ return NULL;
+ }
+ Pos = newPos;
+ }
+ rType = oType;
+ }
+ if (Quick)
+ Vfseek(HTable, sizeof(MaxSizeOnDisk), SEEK_CUR);
+ else
+ {
+ if (Vfread(&MaxSizeOnDisk, sizeof(MaxSizeOnDisk), HTable) != 1)
+ {
+ Vfunlock(HTable, false);
+ return NULL;
+ }
+ }
+ uint32_t NextFieldPos = 0;
+ if (Vfread(&NextFieldPos, sizeof(NextFieldPos), HTable) != 1)
+ {
+ Vfunlock(HTable, false);
+ return NULL;
+ }
+ if (next_position) *next_position = NextFieldPos;
+ if (Quick)
+ {
+ Vfunlock(HTable, false);
+ return this;
+ }
+
+ uint32_t PreviousFieldPos = 0;
+ if (Vfread(&PreviousFieldPos, sizeof(PreviousFieldPos), HTable) != 1)
+ {
+ Vfunlock(HTable, false);
+ return NULL;
+ }
+
+ Field *O=NULL;
+ O = TranslateObject(oType, pTable);
+ if (O)
+ {
+ O->ID = ID;
+ O->Type = oType;
+ O->Pos = Pos;
+ O->MaxSizeOnDisk = MaxSizeOnDisk;
+ uint8_t *data = NULL;
+ if (HTable->cached && MaxSizeOnDisk > VFILE_INC)
+ {
+ pTable->IncErrorCount();
+ MaxSizeOnDisk = (uint32_t)GetDataSize();
+ O->MaxSizeOnDisk = MaxSizeOnDisk;
+ }
+ else if (HTable->cached)
+ {
+ data = HTable->data+HTable->ptr; // benski> uber-hack
+ Vfseek(HTable, MaxSizeOnDisk, SEEK_CUR);
+ }
+ else
+ {
+ data = (uint8_t *)_malloca(MaxSizeOnDisk);
+ Vfread(data, MaxSizeOnDisk, HTable);
+ }
+
+ if (data)
+ {
+ O->ReadTypedData(data, MaxSizeOnDisk);
+ if (!HTable->cached)
+ {
+ _freea(data);
+ }
+ }
+ Vfunlock(HTable, false);
+ return O;
+ }
+ Vfunlock(HTable, false);
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+void Field::WriteField(Table *pTable, Field *previous_field, Field *next_field)
+{
+ VFILE *HTable = pTable->Handle;
+ if (Pos == -1) return;
+ size_t data_size = GetDataSize();
+ if (HTable->cached && MaxSizeOnDisk > VFILE_INC) {
+ pTable->IncErrorCount();
+ MaxSizeOnDisk = (uint32_t)data_size;
+ }
+
+ if (Pos == 0 || (uint32_t)data_size > MaxSizeOnDisk)
+ {
+ MaxSizeOnDisk = (uint32_t)data_size;
+ uint32_t newPos = AllocNewPos(HTable);
+ if (Pos != 0)
+ {
+ unsigned char v = 0;
+ Vfseek(HTable, Pos, SEEK_SET);
+ Vfwrite(&v, sizeof(v), HTable);
+ v = FIELD_REDIRECTOR;
+ Vfwrite(&v, sizeof(v), HTable);
+ Vfwrite(&newPos, sizeof(newPos), HTable);
+ }
+ Pos = newPos;
+ if (previous_field)
+ {
+ //previous_field->NextFieldPos = Pos;
+ if (previous_field->Pos)
+ {
+ Vfseek(HTable, previous_field->Pos+sizeof(ID)+sizeof(MaxSizeOnDisk)+sizeof(Type), SEEK_SET);
+ Vfwrite(&Pos, sizeof(Pos), HTable);
+ }
+ }
+ if (next_field)
+ {
+ if (next_field->Pos)
+ {
+ Vfseek(HTable, next_field->Pos+sizeof(ID)+sizeof(uint32_t/*NextFieldPos*/)+sizeof(Type)+sizeof(MaxSizeOnDisk), SEEK_SET);
+ Vfwrite(&Pos, sizeof(Pos), HTable);
+ }
+ }
+ }
+
+ uint32_t PreviousFieldPos = 0, NextFieldPos = 0;
+ if (previous_field) PreviousFieldPos = previous_field->GetFieldPos(); else PreviousFieldPos = NULL;
+ if (next_field) NextFieldPos = next_field->GetFieldPos(); else NextFieldPos = NULL;
+
+ Vfseek(HTable, Pos, SEEK_SET);
+ Vfwrite(&ID, sizeof(ID), HTable);
+ Vfwrite(&Type, sizeof(Type), HTable);
+ Vfwrite(&MaxSizeOnDisk, sizeof(MaxSizeOnDisk), HTable);
+ Vfwrite(&NextFieldPos, sizeof(NextFieldPos), HTable);
+ Vfwrite(&PreviousFieldPos, sizeof(PreviousFieldPos), HTable);
+ uint8_t *data = (unsigned char*)_malloca(MaxSizeOnDisk);
+ WriteTypedData(data, MaxSizeOnDisk);
+ Vfwrite(data, MaxSizeOnDisk, HTable);
+ _freea(data);
+}
+
+//---------------------------------------------------------------------------
+uint32_t Field::GetFieldPos(void)
+{
+ return Pos;
+}
+
+//---------------------------------------------------------------------------
+Field *Field::Clone(Table *pTable)
+{
+ Field *clone = TranslateObject(Type, pTable);
+ size_t size = GetDataSize();
+ uint8_t *data = (unsigned char*)_malloca(size);
+ WriteTypedData(data, size);
+ clone->ReadTypedData(data, size);
+ if (data) _freea(data);
+ clone->Type = Type;
+ //clone->HTable = HTable;
+ clone->Pos = FIELD_CLONE;
+ clone->ID = ID;
+ //clone->NextFieldPos = 0;
+ clone->MaxSizeOnDisk=(uint32_t)size;
+ return clone;
+}
+
+//---------------------------------------------------------------------------
+int Field::GetType() {
+ return Type;
+}
+
+size_t Field::GetTotalSize()
+{
+ return GetDataSize() + sizeof(ID)+sizeof(Type)+sizeof(MaxSizeOnDisk)+sizeof(uint32_t/*NextFieldPos*/)+sizeof(uint32_t /*PreviousFieldPos*/);
+} \ No newline at end of file
diff --git a/Src/nde/Field.h b/Src/nde/Field.h
new file mode 100644
index 00000000..59af32d8
--- /dev/null
+++ b/Src/nde/Field.h
@@ -0,0 +1,110 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Field Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __FIELD_H
+#define __FIELD_H
+
+#ifdef __ANDROID__
+#include <foundation/types.h> // TODO
+#else
+#include <bfc/platform/types.h>
+#endif
+
+class Table;
+#include <stdio.h>
+
+#define PUT_INT(y) data[pos]=(uint8_t)(y&255); data[pos+1]=(uint8_t)((y>>8)&255); data[pos+2]=(uint8_t)((y>>16)&255); data[pos+3]=(uint8_t)((y>>24)&255)
+#define GET_INT() (int)(data[pos]|(data[pos+1]<<8)|(data[pos+2]<<16)|(data[pos+3]<<24))
+#define PUT_SHORT(y) data[pos]=(uint8_t)(y&255); data[pos+1]=(uint8_t)((y>>8)&255);
+#define GET_SHORT() (uint16_t)(data[pos]|(data[pos+1]<<8))
+#define PUT_PINT(y) data[*pos]=(uint8_t)(y&255); data[*pos+1]=(uint8_t)((y>>8)&255); data[*pos+2]=(uint8_t)((y>>16)&255); data[*pos+3]=(uint8_t)((y>>24)&255)
+extern float GET_FLOAT(const uint8_t *data, size_t pos);
+extern void PUT_FLOAT(float f, uint8_t *data, size_t pos);
+extern void PUT_BINARY(uint8_t *dest, const uint8_t *src, size_t size, size_t pos);
+extern void GET_BINARY(uint8_t *dest, const uint8_t *src, size_t size, size_t pos);
+#define PUT_CHAR(y) data[pos]=y
+#define GET_CHAR() data[pos]
+#define CHECK_CHAR(l) { if (l < 1) return; l--; }
+#define CHECK_INT(l) { if (l < 4) return; l-=4; }
+#define CHECK_INT64(l) { if (l < 8) return; l-=8; }
+#define CHECK_SHORT(l) { if (l < 2) return; l-=2; }
+#define CHECK_BIN(l, size) { if (l < size) return; l-=size; }
+
+#ifdef ASSERT
+#undef ASSERT
+#endif
+#define ASSERT(x) { \
+ if (!(x)) \
+ OutputDebugString("Assertion failed "); \
+}
+
+#define _CHECK_CHAR(l) { ASSERT(l >= 1); l-=1; }
+#define _CHECK_INT(l) { ASSERT(l >= 4); l-=4; }
+#define _CHECK_SHORT(l) { ASSERT(l >= 2); l-=2; }
+#define _CHECK_BIN(l, size) { ASSERT(l >= size); l-=size; }
+
+class Field //: public wa::lists::node_ptr
+{
+public:
+ Field();
+ virtual ~Field();
+ virtual Field *Clone(Table *pTable);
+ unsigned char GetFieldId(void)
+ {
+ return ID;
+ }
+ int GetType();
+ size_t GetTotalSize();
+ Field(int FieldPos);
+ Field *ReadField(Table *pTable, int pos, uint32_t *next_position=0);
+ Field *ReadField(Table *pTable, int pos, bool readTyped, uint32_t *next_position=0);
+ void WriteField(Table *pTable, Field *previous_field, Field *next_field);
+ virtual void ReadTypedData(const uint8_t *, size_t /*len*/) { };
+ virtual void WriteTypedData(uint8_t *, size_t) { };
+ virtual size_t GetDataSize(void)
+ {
+ return 0;
+ }
+ virtual int Compare(Field * /*Entry*/)
+ {
+ return 0;
+ }
+ virtual int Starts(Field * /*Entry*/)
+ {
+ return 0;
+ }
+ virtual int Contains(Field * /*Entry*/)
+ {
+ return 0;
+ }
+ virtual bool ApplyFilter(Field * /*Data*/, int /*flags*/)
+ {
+ return false;
+ }
+ uint32_t GetFieldPos(void);
+ void InitField(void);
+ union
+ {
+ struct
+ {
+ uint8_t ID;
+ uint8_t Type;
+ };
+ uint32_t _dummy_union_thingy;
+ };
+protected:
+ uint32_t Pos;
+
+ uint32_t MaxSizeOnDisk;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/FilenameField.h b/Src/nde/FilenameField.h
new file mode 100644
index 00000000..d9fc8fb3
--- /dev/null
+++ b/Src/nde/FilenameField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/FilenameField.h"
+#elif defined(_WIN32)
+#include "win/FilenameField.h"
+#elif defined(__ANDROID__)
+#include "android/FilenameField.h"
+#endif
diff --git a/Src/nde/Filter.cpp b/Src/nde/Filter.cpp
new file mode 100644
index 00000000..3071aebe
--- /dev/null
+++ b/Src/nde/Filter.cpp
@@ -0,0 +1,69 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Filter Class
+
+--------------------------------------------------------------------------- */
+
+// Filters can now be a test on a field or a single operator that will pop
+// one operation from the filters stack. upon AddFilter*, return value will
+
+#include "Filter.h"
+#include "Field.h"
+//---------------------------------------------------------------------------
+Filter::Filter(unsigned char _Op)
+{
+ DataField = 0;
+ Op = _Op;
+ Id = -1;
+}
+
+//---------------------------------------------------------------------------
+Filter::Filter(Field *Data, unsigned char _Id, unsigned char _Op)
+{
+ DataField = Data;
+ Op = _Op;
+ Id = _Id;
+}
+
+//---------------------------------------------------------------------------
+Filter::~Filter()
+{
+ delete DataField;
+}
+
+//---------------------------------------------------------------------------
+unsigned char Filter::GetOp(void) const
+{
+ return Op;
+}
+
+//---------------------------------------------------------------------------
+void Filter::SetOp(unsigned char _Op)
+{
+ Op = _Op;
+}
+
+//---------------------------------------------------------------------------
+Field *Filter::Data(void) const
+{
+ return DataField;
+}
+
+//---------------------------------------------------------------------------
+void Filter::SetData(Field *data)
+{
+ DataField = data;
+}
+
+//---------------------------------------------------------------------------
+int Filter::GetId(void) const
+{
+ return Id;
+}
+
diff --git a/Src/nde/Filter.h b/Src/nde/Filter.h
new file mode 100644
index 00000000..6355ffb1
--- /dev/null
+++ b/Src/nde/Filter.h
@@ -0,0 +1,37 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Filter Class Prototypes
+
+ --------------------------------------------------------------------------- */
+
+#ifndef __FILTER_H
+#define __FILTER_H
+
+#include "LinkedList.h"
+class Field;
+class Filter : public LinkedListEntry
+{
+private:
+ Field* DataField;
+ unsigned char Op;
+ unsigned char Id;
+
+public:
+ unsigned char GetOp(void) const;
+ void SetOp(unsigned char Op);
+ Field *Data(void) const;
+ int GetId(void) const;
+ Filter(unsigned char _Op);
+ Filter(Field *Data, unsigned char Id, unsigned char Op);
+ void SetData(Field *data);
+ ~Filter() ;
+
+};
+
+#endif
diff --git a/Src/nde/Index.cpp b/Src/nde/Index.cpp
new file mode 100644
index 00000000..83fe9097
--- /dev/null
+++ b/Src/nde/Index.cpp
@@ -0,0 +1,787 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Raw Index Class
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include <stdio.h>
+
+
+const char *iSign="NDEINDEX";
+
+//---------------------------------------------------------------------------
+Index::Index(VFILE *H, unsigned char id, int pos, int type, bool newindex, int nentries, Table *parentTable)
+{
+ Handle = H;
+ IndexTable = NULL;
+ MaxSize=0;
+ locateUpToDate = false;
+ Id = id;
+ Position = pos;
+// DataType = type;
+ SecIndex = NULL;
+ InChain=false;
+ InInsert=false;
+ InChainIdx = -1;
+ pTable = parentTable;
+ TableHandle = pTable->Handle;
+ SetGlobalLocateUpToDate(false);
+
+ if (!newindex)
+ {
+ Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__), SEEK_SET);
+ Vfread(&NEntries, sizeof(NEntries), Handle);
+ }
+ else
+ NEntries = nentries;
+ LoadIndex(newindex);
+}
+
+//---------------------------------------------------------------------------
+Index::~Index()
+{
+ if (IndexTable)
+ free(IndexTable);
+}
+
+//---------------------------------------------------------------------------
+int Index::Insert(int N)
+{
+ return Insert(NULL, N, false);
+}
+
+//---------------------------------------------------------------------------
+int Index::Insert(Index *parindex, int N, bool localonly)
+{
+ if (InChain) return -1;
+ Index *p = parindex;
+ IndexField *f = 0;
+ locateUpToDate = false;
+ SetGlobalLocateUpToDate(false);
+ InInsert=true;
+ if (N > NEntries) N = NEntries;
+ if ((NEntries+1) > (int)MaxSize)
+ {
+ MaxSize *=2;//+= BLOCK_SIZE;
+ int *newBlock = (int *)calloc(MaxSize, sizeof(int)*2);
+ memcpy(newBlock, IndexTable, NEntries*sizeof(int)*2);
+ free(IndexTable);
+ IndexTable = newBlock;
+ }
+
+ if (N < NEntries && Id == PRIMARY_INDEX)
+ {
+ memmove(IndexTable+(N+1)*2, IndexTable+(N*2), (NEntries-N)*sizeof(int)*2);
+ NEntries++;
+ }
+ else
+ {
+ N=NEntries;
+ NEntries++;
+ }
+
+ Update(N, 0, NULL, localonly);
+
+ // Should be always safe to cat the new record since if we are primary index,
+ // then secondary is sorted, so value will be moved at update
+ // if we are a secondary index, then an insertion will insert at the end of the primary index anyway
+ if (!localonly && SecIndex)
+ {
+ InChain=true;
+ int pp = SecIndex->index->Insert(this, N, false);
+ InChain=false;
+ IndexTable[N*2+1] = pp == -1 ? N : pp;
+ if (N < NEntries-1 && Id == PRIMARY_INDEX)
+ {
+ int pidx = -1;
+ if (!parindex)
+ {
+ int v = pp;
+ f = SecIndex;
+ if (f)
+ {
+ while (f->index->SecIndex->index != this)
+ {
+ v = f->index->GetCooperative(v);
+ f = f->index->SecIndex;
+ }
+ p = f->index;
+ pidx = v;
+ }
+ }
+ if (p && pidx != -1)
+ {
+ p->SetCooperative(pidx, N);
+ UpdateMe(p, N, NEntries);
+ }
+ }
+ }
+
+ InInsert=false;
+ return N;
+}
+
+//---------------------------------------------------------------------------
+void Index::LoadIndex(bool newindex)
+{
+ if (IndexTable)
+ free(IndexTable);
+ MaxSize = ((NEntries) / BLOCK_SIZE + 1) * BLOCK_SIZE;
+ IndexTable = (int *)calloc(MaxSize, sizeof(int)*2);
+ if (!newindex)
+ {
+ Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__)+4+((NEntries*4*2)+4)*(Position+1), SEEK_SET);
+ int v = 0;
+ Vfread(&v, sizeof(v), Handle);
+ Id = (unsigned char)v;
+ Vfread(IndexTable, NEntries*2*sizeof(int), Handle);
+ }
+}
+
+//---------------------------------------------------------------------------
+int Index::Update(int Idx, int Pos, RecordBase *record, bool localonly)
+{
+ return Update(NULL, -1, Idx, Pos, record, false, localonly);
+}
+
+//---------------------------------------------------------------------------
+int Index::Update(Index *parindex, int paridx, int Idx, int Pos, RecordBase *record, bool forceLast, bool localonly)
+{
+ if (InChain) return InChainIdx;
+ int NewIdx=Idx;
+ int oldSecPtr = IndexTable[Idx*2+1];
+ if (!forceLast && Id == PRIMARY_INDEX || record == NULL || Idx < NUM_SPECIAL_RECORDS)
+ {
+ if (Idx < NEntries && Idx >= 0)
+ {
+ IndexTable[Idx*2] = Pos;
+ if (!localonly && SecIndex && SecIndex->index != this && !InInsert)
+ {
+ InChain=true;
+ InChainIdx = Idx;
+ SecIndex->index->Update(this, Idx, IndexTable[Idx*2+1], Pos, record, forceLast, false);
+ InChainIdx = -1;
+ InChain=false;
+ }
+ }
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Updating outside range", L"Oops", 16);
+#else
+ printf("NDE Error: updating outside range!\n");
+#endif
+ }
+ }
+ else
+ {
+ if (forceLast)
+ NewIdx = NEntries;
+ else
+ {
+ if (Pos != Get(Idx) || Id != PRIMARY_INDEX)
+ {
+ int state = 0;
+ NewIdx = FindSortedPlace(record->GetField(Id), Idx, &state, QFIND_ENTIRE_SCOPE);
+ }
+ }
+
+ if (NewIdx <= NEntries && NewIdx >= NUM_SPECIAL_RECORDS)
+ {
+ if (NewIdx != Idx)
+ {
+ Index *p = parindex;
+ IndexField *f = 0;
+ int pidx = paridx;
+ NewIdx = MoveIndex(Idx, NewIdx);
+ if (SecIndex->index != this)
+ {
+ if (!parindex)
+ {
+ int v = GetCooperative(NewIdx);
+ f = SecIndex;
+ if (f)
+ {
+ while (f->index->SecIndex->index != this)
+ {
+ v = f->index->GetCooperative(v);
+ f = f->index->SecIndex;
+ }
+ p = f->index;
+ pidx = v;
+ }
+ }
+ if (p)
+ {
+ p->SetCooperative(pidx, NewIdx);
+ UpdateMe(p, NewIdx, Idx);
+ }
+ }
+ }
+ IndexTable[NewIdx*2] = Pos;
+ if (!localonly && SecIndex && SecIndex->index != this && !InInsert) // Actually, we should never be InInsert and here, but lets cover our ass
+ {
+ InChain=true;
+ InChainIdx = oldSecPtr;
+ SecIndex->index->Update(this, NewIdx, oldSecPtr, Pos, record, forceLast, false);
+ InChainIdx = -1;
+ InChain=false;
+ }
+ }
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"QSort failed and tried to update index outside range", L"Oops", 16);
+#else
+ printf("NDE Error: qsort failed and tried to update index outside range!\n");
+#endif
+ }
+ }
+ locateUpToDate = false;
+ SetGlobalLocateUpToDate(false);
+ pTable->IndexModified();
+ return NewIdx;
+}
+
+//---------------------------------------------------------------------------
+int Index::Get(int Idx)
+{
+ if (Idx < NEntries)
+ return IndexTable[Idx*2];
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Requested index outside range", L"Oops", 16);
+#else
+ printf("NDE Error: requested index outside range!\n");
+#endif
+ return -1;
+ }
+}
+
+//---------------------------------------------------------------------------
+void Index::Set(int Idx, int P)
+{
+ if (Idx < NEntries)
+ IndexTable[Idx*2]=P;
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Updating index outside range", L"Oops", 16);
+#else
+ printf("NDE Error: updating index outside range!\n");
+#endif
+ }
+}
+
+//---------------------------------------------------------------------------
+void Index::WriteIndex(void)
+{
+ if (Id == PRIMARY_INDEX)
+ {
+ Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__), SEEK_SET);
+ Vfwrite(&NEntries, sizeof(NEntries), Handle);
+ }
+ Vfseek(Handle, (int)strlen(__INDEX_SIGNATURE__)+4+((NEntries*4*2)+4)*(Position+1), SEEK_SET);
+ int v=(int)Id;
+ Vfwrite(&v, sizeof(v), Handle);
+ Vfwrite(IndexTable, NEntries*2*sizeof(int), Handle);
+}
+
+//---------------------------------------------------------------------------
+int Index::MoveIndex(int idx, int newidx)
+{
+ if (idx == newidx)
+ return newidx;
+ int oldPos=IndexTable[idx*2], oldPtr=IndexTable[idx*2+1];
+
+ if (NEntries > idx+1)
+ memmove(IndexTable+idx*2, IndexTable+(idx+1)*2, (NEntries-idx)*sizeof(int)*2);
+ if (newidx > idx)
+ newidx--;
+ if (NEntries > newidx)
+ memmove(IndexTable+(newidx+1)*2, IndexTable+newidx*2, (NEntries-newidx-1)*sizeof(int)*2);
+ IndexTable[newidx*2] = oldPos;
+ IndexTable[newidx*2+1] = oldPtr;
+ return newidx;
+}
+
+//---------------------------------------------------------------------------
+void Index::Colaborate(IndexField *secindex)
+{
+ SecIndex = secindex;
+ for (int i=0;i<NUM_SPECIAL_RECORDS;i++)
+ /*secindex->index->*/SetCooperative(i, i);
+
+}
+
+//---------------------------------------------------------------------------
+void Index::Propagate(void)
+{
+ int i;
+
+ if (!SecIndex || SecIndex->ID == PRIMARY_INDEX) return;
+ SecIndex->index->NEntries=NUM_SPECIAL_RECORDS;
+ for (i=0;i<NUM_SPECIAL_RECORDS;i++)
+ {
+ SecIndex->index->Set(i, Get(i));
+ SecIndex->index->SetCooperative(i, GetCooperative(i));
+ }
+
+ Scanner *s = pTable->NewScanner();
+ if (!s)
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Failed to create a scanner in reindex", L"Oops", 16);
+#else
+ printf("NDE Error: failed to create a scanner in reindex!\n");
+#endif
+ return;
+ }
+
+ int *coopSave = (int *)calloc(NEntries, sizeof(int));
+ if (!coopSave)
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Alloc failed in reindex", L"Oops", 16);
+#else
+ printf("NDE Error: alloc failed in reindex!\n");
+#endif
+ return;
+ }
+
+ for (i=0;i<NEntries;i++)
+ {
+ coopSave[i] = GetCooperative(i);
+ SetCooperative(i, i);
+ }
+
+ if (SecIndex->index->SecIndex->index->Id != PRIMARY_INDEX)
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Propagating existing index", L"Oops", 16);
+#else
+ printf("NDE Error: propagating existing index!\n");
+#endif
+ free(coopSave);
+ return;
+ }
+
+ s->SetWorkingIndexById(-1);
+
+ for (i=NUM_SPECIAL_RECORDS;i<NEntries;i++)
+ {
+ Record *rec = s->GetRecord(coopSave[i]);
+ if (rec)
+ {
+ SecIndex->index->NEntries++;
+ // SecIndex->index->Insert(NULL, i, true);
+ SecIndex->index->SetCooperative(i, coopSave[i]);
+ SecIndex->index->Set(i, Get(i));
+ SecIndex->index->Update(i, SecIndex->index->Get(i), rec, true);
+ /* SecIndex->index->SetCooperative(i, q);*/
+ rec->Release();
+ }
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Unable to read record in reindex", L"Oops", 16);
+#else
+ printf("NDE Error: unable to read record in reindex!\n");
+#endif
+ }
+ }
+
+ free(coopSave);
+
+ if (NEntries != SecIndex->index->NEntries) {
+#ifdef WIN32
+ MessageBox(NULL, L"Secindex->NEntries desynchronized in reindex", L"Oops", 16);
+#else
+ printf("NDE Error: Secindex->NEntries desynchronized in reindex!\n");
+#endif
+ }
+
+ pTable->DeleteScanner(s);
+}
+
+//---------------------------------------------------------------------------
+void Index::SetCooperative(int Idx, int secpos)
+{
+ if (Idx < NEntries && Idx >= 0)
+ IndexTable[Idx*2+1] = secpos;
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Cooperative update outside range", L"Oops", 16);
+#else
+ printf("NDE Error: cooperative update outside range!\n");
+#endif
+ }
+}
+
+//---------------------------------------------------------------------------
+int Index::GetCooperative(int Idx)
+{
+ if (Idx < NEntries && Idx >= 0)
+ return IndexTable[Idx*2+1];
+ else
+ {
+#ifdef WIN32
+ MessageBox(NULL, L"Cooperative request outside range", L"Oops", 16);
+#else
+ printf("NDE Error: cooperative request outside range!\n");
+#endif
+ }
+ return -1;
+}
+
+//---------------------------------------------------------------------------
+int Index::NeedFix() {
+ for (int i=NUM_SPECIAL_RECORDS;i<NEntries;i++) {
+ if (IndexTable[i*2+1] <= 0) return 1;
+ }
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+void Index::UpdateMe(Index *Me, int NewIdx, int OldIdx)
+{
+ int j=NewIdx > OldIdx ? -1 : 1;
+ for (int i=min(NewIdx, OldIdx);i<=max(NewIdx, OldIdx)&&i<NEntries;i++)
+ {
+ if (i == NewIdx) continue;
+ int v = GetCooperative(i);
+ IndexField *f = SecIndex;
+ while (f->index->SecIndex->index != this)
+ {
+ v = f->index->GetCooperative(v);
+ f = f->index->SecIndex;
+ }
+ Me->SetCooperative(v, Me->GetCooperative(v)+j);
+ }
+}
+
+//---------------------------------------------------------------------------
+Field *Index::QuickFindField(unsigned char Idx, int Pos)
+{
+ int ThisPos = Pos;
+
+ while (ThisPos)
+ {
+ Vfseek(TableHandle, ThisPos, SEEK_SET);
+ Field entry(ThisPos);
+ uint32_t next_pos;
+ entry.ReadField(pTable, ThisPos, true, &next_pos);
+ if (entry.GetFieldId() == Idx)
+ {
+ return entry.ReadField(pTable, ThisPos);
+ }
+ ThisPos = next_pos;
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+// Dynamic qsort (i definitly rule)
+int Index::FindSortedPlace(Field *thisField, int curIdx, int *laststate, int start)
+{
+ return FindSortedPlaceEx(thisField, curIdx, laststate, start, COMPARE_MODE_EXACT);
+}
+
+//---------------------------------------------------------------------------
+// and here again ugly switch
+int Index::FindSortedPlaceEx(Field *thisField, int curIdx, int *laststate, int start, int comp_mode)
+{
+ int top=start != QFIND_ENTIRE_SCOPE ? start : NUM_SPECIAL_RECORDS;
+ int bottom=NEntries-1;
+ int compEntry = 0;
+ int cePos = 0;
+ Field *compField=NULL;
+ int i = 0;
+ Field *cfV=NULL;
+
+ if (NEntries <= NUM_SPECIAL_RECORDS) return NUM_SPECIAL_RECORDS;
+ switch(comp_mode)
+ {
+ case COMPARE_MODE_EXACT:
+ while (bottom-top >= 1)
+ {
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) compEntry++;
+ if (compEntry == bottom) break;
+ cePos = Get(compEntry);
+ if (!cePos) bottom = compEntry;
+ else
+ {
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (!thisField)
+ {
+ if (!compField) i = 0;
+ else i = 1;
+ }
+ else i = thisField->Compare(cfV);
+ switch (i)
+ {
+ case 1:
+ top = compEntry+1;
+ break;
+ case -1:
+ bottom = compEntry-1;
+ break;
+ case 0:
+ *laststate=0;
+ delete compField;
+ return compEntry+1;
+ }
+ delete compField;
+ }
+ }
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) return curIdx;
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (thisField) *laststate = thisField->Compare(cfV);
+ else
+ {
+ if (!compField) *laststate = 0;
+ else *laststate = 1;
+ }
+ break;
+
+ case COMPARE_MODE_CONTAINS:
+ while (bottom-top >= 1)
+ {
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) compEntry++;
+ if (compEntry == bottom) break;
+ cePos = Get(compEntry);
+ if (!cePos) bottom = compEntry;
+ else
+ {
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (!thisField)
+ {
+ if (!compField) i = 0;
+ else i = 1;
+ }
+ else i = thisField->Contains(cfV);
+ switch (i)
+ {
+ case 1:
+ top = compEntry+1;
+ break;
+ case -1:
+ bottom = compEntry-1;
+ break;
+ case 0:
+ *laststate=0;
+ delete compField;
+ return compEntry+1;
+ }
+ if (compField)
+ {
+ delete compField;
+ }
+ }
+ }
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) return curIdx;
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (thisField) *laststate = thisField->Contains(cfV);
+ else
+ {
+ if (!compField) *laststate = 0;
+ else *laststate = 1;
+ }
+ break;
+
+ case COMPARE_MODE_STARTS:
+ while (bottom-top >= 1)
+ {
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) compEntry++;
+ if (compEntry == bottom) break;
+ cePos = Get(compEntry);
+ if (!cePos) bottom = compEntry;
+ else
+ {
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (!thisField)
+ {
+ if (!compField) i = 0;
+ else i = 1;
+ }
+ else i = thisField->Starts(cfV);
+ switch (i)
+ {
+ case 1:
+ top = compEntry+1;
+ break;
+ case -1:
+ bottom = compEntry-1;
+ break;
+ case 0:
+ *laststate=0;
+ delete compField;
+ return compEntry+1;
+ }
+ if (compField)
+ {
+ delete compField;
+ }
+ }
+ }
+ compEntry=(bottom-top)/2+top;
+ if (compEntry == curIdx) return curIdx;
+ compField = QuickFindField(Id, Get(compEntry));
+ cfV = compField;
+ if (thisField) *laststate = thisField->Starts(cfV);
+ else
+ {
+ if (!compField) *laststate = 0;
+ else *laststate = 1;
+ }
+ break;
+ }
+
+ switch (*laststate)
+ {
+ case -1:
+ delete compField;
+ return compEntry;
+ case 1:
+ case 0:
+ delete compField;
+ return /*compEntry==NEntries-1 ? compEntry : */compEntry+1;
+ }
+ return NUM_SPECIAL_RECORDS; // we're not supposed to be here :/
+}
+
+int Index::QuickFind(int Id, Field *field, int start)
+{
+ return QuickFindEx(Id, field, start, COMPARE_MODE_EXACT);
+}
+
+//---------------------------------------------------------------------------
+int Index::QuickFindEx(int Id, Field *field, int start, int comp_mode)
+{
+ int laststate = 0;
+ Field *compField=NULL, *cfV=NULL;
+ int i = FindSortedPlaceEx(field, Id, &laststate, start, comp_mode)-1; // -1 because we don't insert but just search
+ if (laststate != 0)
+ return FIELD_NOT_FOUND;
+ if (i < start)
+ return FIELD_NOT_FOUND;
+
+ switch(comp_mode)
+ {
+ case COMPARE_MODE_CONTAINS:
+ while (--i>=NUM_SPECIAL_RECORDS)
+ {
+ compField = QuickFindField(Id, Get(i));
+ cfV = compField;
+ if (field->Contains(cfV) != 0)
+ {
+ if (compField)
+ {
+ delete compField;
+ }
+ return i+1;
+ }
+ if (compField)
+ {
+ delete compField;
+ }
+ }
+ break;
+ case COMPARE_MODE_EXACT:
+ while (--i>=NUM_SPECIAL_RECORDS)
+ {
+ compField = QuickFindField(Id, Get(i));
+ cfV = compField;
+ if (field->Compare(cfV) != 0)
+ {
+ if (compField)
+ {
+ delete compField;
+ }
+ return i+1;
+ }
+ if (compField)
+ {
+ delete compField;
+ }
+ }
+ break;
+ case COMPARE_MODE_STARTS:
+ while (--i>=NUM_SPECIAL_RECORDS)
+ {
+ compField = QuickFindField(Id, Get(i));
+ cfV = compField;
+ if (field->Starts(cfV) != 0)
+ {
+ delete compField;
+ return i+1;
+ }
+ delete compField;
+ }
+ break;
+ }
+
+ return i+1;
+}
+
+//---------------------------------------------------------------------------
+int Index::TranslateIndex(int Pos, Index *index)
+{
+ int v = GetCooperative(Pos);
+ IndexField *f = SecIndex;
+ while (f->index != this && f->index != index)
+ {
+ v = f->index->GetCooperative(v);
+ f = f->index->SecIndex;
+ }
+ return v;
+}
+
+//---------------------------------------------------------------------------
+void Index::Delete(int Idx, int Pos, Record *record)
+{
+ Update(NULL, -1, Idx, Pos, record, true, false);
+ Shrink();
+}
+
+//---------------------------------------------------------------------------
+void Index::Shrink(void)
+{
+ if (InChain) return;
+ if (SecIndex && SecIndex->index != this) // Actually, we should never be InInsert and here, but lets cover our ass
+ {
+ InChain=true;
+ SecIndex->index->Shrink();
+ InChain=false;
+ }
+ NEntries--;
+}
+
+//---------------------------------------------------------------------------
+void Index::SetGlobalLocateUpToDate(bool isUptodate) {
+ if (!pTable) return;
+ pTable->SetGlobalLocateUpToDate(isUptodate);
+}
+
+unsigned char Index::GetId() {
+ return Id;
+} \ No newline at end of file
diff --git a/Src/nde/Index.h b/Src/nde/Index.h
new file mode 100644
index 00000000..1750f44e
--- /dev/null
+++ b/Src/nde/Index.h
@@ -0,0 +1,83 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Raw Index Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __RAWINDEX_H
+#define __RAWINDEX_H
+
+#include <stdio.h>
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include "vfs.h"
+#include "Table.h"
+
+class IndexField;
+#define BLOCK_SIZE 2048 // 8192 entries blocks
+class RecordBase;
+class Index
+{
+public:
+ Index(VFILE *Handle, unsigned char id, int pos, int type, bool newindex, int nentries, Table *parentTable);
+ ~Index();
+ int Get(int Idx);
+ void Set(int Idx, int P);
+
+ void LoadIndex(bool newindex);
+ void WriteIndex(void);
+ int Insert(Index *parindex, int N, bool localonly);
+ int Insert(int N);
+ int Update(int Idx, int Pos, RecordBase *record, bool localonly);
+ int Update(Index *parindex, int paridx, int Idx, int Pos, RecordBase *record, bool forceLast, bool localonly);
+ unsigned char GetId();
+
+ int FindSortedPlace(Field *field, int idx, int *laststate, int start);
+ int FindSortedPlaceEx(Field *field, int idx, int *laststate, int start, int comp_mode);
+ int MoveIndex(int idx, int newidx);
+ void Colaborate(IndexField *secindex);
+ void SetCooperative(int Idx, int secpos);
+ int GetCooperative(int Idx);
+ void UpdateMe(Index *Me, int newidx, int oldidx);
+ Field *QuickFindField(unsigned char Id, int Pos);
+ int QuickFind(int Id, Field *field, int start);
+ int QuickFindEx(int Id, Field *field, int start, int comp_mode);
+ int TranslateIndex(int Pos, Index *index);
+ void Delete(int Idx, int Pos, Record *record);
+ void Shrink(void);
+
+ void Propagate(void);
+ void SetGlobalLocateUpToDate(bool isUptodate);
+ int NeedFix();
+
+public:
+ // TODO: make these protected
+ int NEntries;
+ IndexField *SecIndex;
+ bool locateUpToDate;
+
+protected:
+ VFILE *Handle;
+ VFILE *TableHandle;
+ Table *pTable;
+
+ int *IndexTable;
+ size_t MaxSize;
+ unsigned char Id;
+
+ bool InChain;
+ int Position;
+ bool InInsert;
+ int InChainIdx;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/IndexField.h b/Src/nde/IndexField.h
new file mode 100644
index 00000000..a28e4203
--- /dev/null
+++ b/Src/nde/IndexField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/IndexField.h"
+#elif defined(_WIN32)
+#include "win/IndexField.h"
+#elif defined(__ANDROID__)
+#include "android/IndexField.h"
+#endif
diff --git a/Src/nde/IndexRecord.h b/Src/nde/IndexRecord.h
new file mode 100644
index 00000000..0c56545c
--- /dev/null
+++ b/Src/nde/IndexRecord.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/IndexRecord.h"
+#elif defined(_WIN32)
+#include "win/IndexRecord.h"
+#elif defined(__ANDROID__)
+#include "android/IndexRecord.h"
+#endif
diff --git a/Src/nde/Int128Field.cpp b/Src/nde/Int128Field.cpp
new file mode 100644
index 00000000..c63d1f48
--- /dev/null
+++ b/Src/nde/Int128Field.cpp
@@ -0,0 +1,126 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Int128Field Class
+Field data layout:
+[16 bytes] value
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "Int128Field.h"
+#include <time.h>
+
+//---------------------------------------------------------------------------
+Int128Field::Int128Field(void *data)
+{
+ InitField();
+ Type = FIELD_INT128;
+ memcpy(Value, data, 16);
+}
+
+//---------------------------------------------------------------------------
+void Int128Field::InitField(void)
+{
+ Type = FIELD_INT128;
+ memset(Value, 0, 16);
+}
+
+//---------------------------------------------------------------------------
+Int128Field::Int128Field()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+Int128Field::~Int128Field()
+{
+}
+
+//---------------------------------------------------------------------------
+void Int128Field::ReadTypedData(const uint8_t *data, size_t len)
+{
+ if (len < 16) return;
+ memcpy(Value, data, 16);
+}
+
+//---------------------------------------------------------------------------
+void Int128Field::WriteTypedData(uint8_t *data, size_t len)
+{
+
+ if (len < 16) return;
+ memcpy(data, Value, 16);
+}
+
+//---------------------------------------------------------------------------
+void *Int128Field::GetValue(void)
+{
+ return Value;
+}
+
+//---------------------------------------------------------------------------
+void Int128Field::SetValue(const void *Val)
+{
+ memcpy(Value, Val, 16);
+}
+
+//---------------------------------------------------------------------------
+size_t Int128Field::GetDataSize(void)
+{
+ return 16;
+}
+
+//---------------------------------------------------------------------------
+int Int128Field::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ return memcmp(Value, ((Int128Field*)Entry)->GetValue(), 16);
+}
+
+static char zerobuf[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+//---------------------------------------------------------------------------
+bool Int128Field::ApplyFilter(Field *Data, int op)
+{
+ void *p = ((Int128Field *)Data)->GetValue();
+ void *d = Value;
+ if (!p)
+ p = zerobuf;
+ if (!d)
+ d = zerobuf;
+ bool r;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = !memcmp(d, p, 16);
+ break;
+ case FILTER_CONTAINS:
+ r = !memcmp(d, p, 16);
+ break;
+ case FILTER_ABOVE:
+ r = (memcmp(d, p, 16) > 0);
+ break;
+ case FILTER_BELOW:
+ r = (memcmp(d, p, 16) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (memcmp(d, p, 16) <= 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (memcmp(d, p, 16) >= 0);
+ break;
+ case FILTER_ISEMPTY:
+ r = !d || (!memcmp(d, zerobuf, 16));
+ break;
+ case FILTER_ISNOTEMPTY:
+ r = d && (memcmp(d, zerobuf, 16));
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/nde/Int128Field.h b/Src/nde/Int128Field.h
new file mode 100644
index 00000000..a358a6e9
--- /dev/null
+++ b/Src/nde/Int128Field.h
@@ -0,0 +1,38 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Int128Field Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef NULLSOFT_NDE_INT128FIELD_H
+#define NULLSOFT_NDE_INT128FIELD_H
+
+#include "Field.h"
+
+class Int128Field : 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);
+ char Value[16];
+public:
+ ~Int128Field();
+ Int128Field(void *value);
+ Int128Field();
+ void *GetValue(void);
+ void SetValue(const void *value);
+};
+
+
+
+#endif \ No newline at end of file
diff --git a/Src/nde/Int64Field.cpp b/Src/nde/Int64Field.cpp
new file mode 100644
index 00000000..e26e4f6f
--- /dev/null
+++ b/Src/nde/Int64Field.cpp
@@ -0,0 +1,124 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Int64Field Class
+Field data layout:
+[8 bytes] value
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "Int64Field.h"
+#include <time.h>
+
+//---------------------------------------------------------------------------
+Int64Field::Int64Field(int64_t Val)
+{
+ InitField();
+ Type = FIELD_INT64;
+ Value = Val;
+}
+
+//---------------------------------------------------------------------------
+void Int64Field::InitField(void)
+{
+ Type = FIELD_INT64;
+ Value = 0;
+}
+
+//---------------------------------------------------------------------------
+Int64Field::Int64Field()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+Int64Field::~Int64Field()
+{
+}
+
+//---------------------------------------------------------------------------
+void Int64Field::ReadTypedData(const uint8_t *data, size_t len)
+{
+ CHECK_INT64(len);
+ Value = *((int64_t *)data);
+}
+
+//---------------------------------------------------------------------------
+void Int64Field::WriteTypedData(uint8_t *data, size_t len)
+{
+ CHECK_INT64(len);
+ *((int64_t *)data) = Value;
+}
+
+//---------------------------------------------------------------------------
+int64_t Int64Field::GetValue(void)
+{
+ return Value;
+}
+
+//---------------------------------------------------------------------------
+void Int64Field::SetValue(int64_t Val)
+{
+ Value = Val;
+}
+
+//---------------------------------------------------------------------------
+size_t Int64Field::GetDataSize(void)
+{
+ return sizeof(int64_t);
+}
+
+//---------------------------------------------------------------------------
+int Int64Field::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ return GetValue() < ((Int64Field*)Entry)->GetValue() ? -1 : (GetValue() > ((Int64Field*)Entry)->GetValue() ? 1 : 0);
+}
+
+//---------------------------------------------------------------------------
+bool Int64Field::ApplyFilter(Field *Data, int op)
+{
+ bool r;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = Value == ((Int64Field *)Data)->GetValue();
+ break;
+ case FILTER_NOTEQUALS:
+ r = Value != ((Int64Field *)Data)->GetValue();
+ break;
+ case FILTER_NOTCONTAINS:
+ r = (bool)!(Value & ((Int64Field *)Data)->GetValue());
+ break;
+ case FILTER_CONTAINS:
+ r = !!(Value & ((Int64Field *)Data)->GetValue());
+ break;
+ case FILTER_ABOVE:
+ r = (bool)(Value > ((Int64Field *)Data)->GetValue());
+ break;
+ case FILTER_BELOW:
+ r = (bool)(Value < ((Int64Field *)Data)->GetValue());
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (bool)(Value <= ((Int64Field *)Data)->GetValue());
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (bool)(Value >= ((Int64Field *)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;
+} \ No newline at end of file
diff --git a/Src/nde/Int64Field.h b/Src/nde/Int64Field.h
new file mode 100644
index 00000000..11f88eb7
--- /dev/null
+++ b/Src/nde/Int64Field.h
@@ -0,0 +1,39 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Int64Field Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef NULLSOFT_NDE_INT64FIELD_H
+#define NULLSOFT_NDE_INT64FIELD_H
+
+#include "Field.h"
+
+class Int64Field : 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);
+ int64_t Value;
+
+public:
+ ~Int64Field();
+ Int64Field(int64_t);
+ Int64Field();
+ int64_t GetValue(void);
+ void SetValue(int64_t);
+};
+
+
+
+#endif \ No newline at end of file
diff --git a/Src/nde/IntegerField.h b/Src/nde/IntegerField.h
new file mode 100644
index 00000000..4b631d79
--- /dev/null
+++ b/Src/nde/IntegerField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/IntegerField.h"
+#elif defined(_WIN32)
+#include "win/IntegerField.h"
+#elif defined(__ANDROID__)
+#include "android/IntegerField.h"
+#endif
diff --git a/Src/nde/LinkedList.cpp b/Src/nde/LinkedList.cpp
new file mode 100644
index 00000000..05f4d122
--- /dev/null
+++ b/Src/nde/LinkedList.cpp
@@ -0,0 +1,126 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Double-Linked List Class
+
+Caution: The entire library relies on this base class. Modify with care!
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "LinkedList.h"
+//---------------------------------------------------------------------------
+LinkedListEntry *LinkedListEntry::GetNext(void) const
+{
+ return Next;
+}
+
+//---------------------------------------------------------------------------
+LinkedListEntry *LinkedListEntry::GetPrevious(void) const
+{
+ return Previous;
+}
+
+//---------------------------------------------------------------------------
+LinkedListEntry::LinkedListEntry()
+{
+ //Int=0;
+ Next=NULL;
+ Previous=NULL;
+}
+
+//---------------------------------------------------------------------------
+LinkedListEntry::~LinkedListEntry()
+{
+}
+
+
+//---------------------------------------------------------------------------
+LinkedList::LinkedList()
+{
+ Head = NULL;
+ Foot = NULL;
+ NElements=0;
+}
+
+//---------------------------------------------------------------------------
+LinkedList::~LinkedList()
+{
+ while (Head)
+ RemoveEntry(Head);
+}
+
+//---------------------------------------------------------------------------
+void LinkedList::AddEntry(LinkedListEntry *Entry, bool Cat)
+{
+#ifdef WIN32
+ //EnterCriticalSection(&Critical);
+#endif
+ if (!Cat)
+ {
+ if (Head)
+ Head->Previous = Entry;
+ Entry->Next = Head;
+ Entry->Previous = NULL;
+ Head = Entry;
+ if (!Foot)
+ Foot = Entry;
+ }
+ else
+ {
+ if (Foot)
+ Foot->Next = Entry;
+ Entry->Previous = Foot;
+ Entry->Next = NULL;
+ Foot = Entry;
+ if (!Head)
+ Head = Entry;
+ }
+ NElements++;
+#ifdef WIN32
+ //LeaveCriticalSection(&Critical);
+#endif
+}
+
+//---------------------------------------------------------------------------
+void LinkedList::RemoveEntry(LinkedListEntry *Entry)
+{
+ if (!Entry) return;
+#ifdef WIN32
+ //EnterCriticalSection(&Critical);
+#endif
+ if (Entry->Next)
+ Entry->Next->Previous = Entry->Previous;
+ else
+ Foot = Entry->Previous;
+ if (Entry->Previous)
+ Entry->Previous->Next = Entry->Next;
+ else
+ Head = Entry->Next;
+
+ if (NElements > 0)
+ {
+ delete Entry;
+ NElements--;
+ }
+#ifdef WIN32
+ //LeaveCriticalSection(&Critical);
+#endif
+}
+
+//---------------------------------------------------------------------------
+void LinkedList::WalkList(WalkListProc WalkProc, int ID, void *Data1, void *Data2)
+{
+ if (!WalkProc) return;
+ LinkedListEntry *Entry = Head;
+ while (Entry)
+ {
+ if (!WalkProc(Entry, ID, Data1, Data2)) break;
+ Entry = Entry->Next;
+ }
+} \ No newline at end of file
diff --git a/Src/nde/LinkedList.h b/Src/nde/LinkedList.h
new file mode 100644
index 00000000..4fffddaf
--- /dev/null
+++ b/Src/nde/LinkedList.h
@@ -0,0 +1,82 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Double-Linked List Class Prototypes
+
+ --------------------------------------------------------------------------- */
+
+#ifndef __LINKEDLIST_H
+#define __LINKEDLIST_H
+
+class LinkedListEntry
+{
+public:
+ LinkedListEntry *Next;
+ LinkedListEntry *Previous;
+public:
+ LinkedListEntry *GetNext() const;
+ LinkedListEntry *GetPrevious() const;
+ LinkedListEntry();
+ virtual ~LinkedListEntry();
+};
+
+template <class T> class VListEntry : public LinkedListEntry
+{
+public:
+ void SetVal(T val)
+ {
+ Val = val;
+ }
+ T GetVal(void)
+ {
+ return Val;
+ }
+
+private:
+ T Val;
+};
+
+template <class T> class PListEntry : public LinkedListEntry
+{
+public:
+ void SetVal(T *val)
+ {
+ Val = val;
+ }
+ T *GetVal(void)
+ {
+ return Val;
+ }
+
+private:
+ T *Val;
+};
+
+
+typedef bool (*WalkListProc)(LinkedListEntry *Entry, int, void*, void*);
+
+class LinkedList
+{
+protected:
+ int NElements;
+ LinkedListEntry *Head;
+ LinkedListEntry *Foot;
+
+public:
+ LinkedList();
+ ~LinkedList();
+ void AddEntry(LinkedListEntry *Entry, bool Cat);
+ void RemoveEntry(LinkedListEntry *Entry);
+
+ void WalkList(WalkListProc WalkProc, int ID, void *Data1, void *Data2);
+ int GetNElements(void) { return NElements; }
+ LinkedListEntry *GetHead(void) { return Head; }
+ LinkedListEntry *GetFoot(void) { return Foot; }
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/NDEString.cpp b/Src/nde/NDEString.cpp
new file mode 100644
index 00000000..8c20ef99
--- /dev/null
+++ b/Src/nde/NDEString.cpp
@@ -0,0 +1,84 @@
+#include "NDEString.h"
+#include "foundation/error.h"
+
+#include <windows.h>
+
+typedef BOOL (WINAPI *HEAPSETINFORMATION)(HANDLE HeapHandle, HEAP_INFORMATION_CLASS HeapInformationClass, PVOID HeapInformation, SIZE_T HeapInformationLength);
+
+static HANDLE string_heap=0;
+static HMODULE kernel=0;
+extern "C" void NDE_HeapInit()
+{
+ if (!string_heap)
+ {
+ string_heap=HeapCreate(0, 0, 0);
+ kernel = LoadLibraryW(L"kernel32.dll");
+ if (kernel)
+ {
+ HEAPSETINFORMATION hsi = (HEAPSETINFORMATION)GetProcAddress(kernel, "HeapSetInformation");
+ if (hsi)
+ {
+ ULONG argh = 2;
+ hsi(string_heap, HeapCompatibilityInformation, &argh, sizeof(ULONG));
+ }
+ }
+ NXStringSetHeap(string_heap);
+ }
+}
+
+extern "C" void NDE_HeapQuit()
+{
+ if (kernel)
+ FreeLibrary(kernel);
+}
+
+nx_string_t ndestring_get_string(wchar_t *str)
+{
+ if (!str)
+ return 0;
+ nx_string_t self = (nx_string_t)((uint8_t *)str - sizeof(size_t) - sizeof(size_t));
+ return self;
+}
+
+wchar_t *ndestring_wcsdup(const wchar_t *str)
+{
+ NDE_HeapInit();
+ if (!str)
+ return 0;
+ nx_string_t value;
+ if (NXStringCreateWithUTF16(&value, str) != NErr_Success)
+ return 0;
+ return value->string;
+}
+
+wchar_t *ndestring_wcsndup(const wchar_t *str, size_t len)
+{
+ NDE_HeapInit();
+ if (!str)
+ return 0;
+ nx_string_t value;
+ if (NXStringCreateWithBytes(&value, str, len*2, nx_charset_utf16le) != NErr_Success)
+ return 0;
+ return value->string;
+}
+
+wchar_t *ndestring_malloc(size_t str_size)
+{
+ NDE_HeapInit();
+ nx_string_t value=NXStringMalloc((str_size+1)/2 - 1);
+ if (!value)
+ return 0;
+ return value->string;
+}
+
+void ndestring_release(wchar_t *str)
+{
+ nx_string_t value = ndestring_get_string(str);
+ NXStringRelease(value);
+}
+
+void ndestring_retain(wchar_t *str)
+{
+ nx_string_t value = ndestring_get_string(str);
+ NXStringRetain(value);
+} \ No newline at end of file
diff --git a/Src/nde/NDEString.h b/Src/nde/NDEString.h
new file mode 100644
index 00000000..f7182827
--- /dev/null
+++ b/Src/nde/NDEString.h
@@ -0,0 +1,31 @@
+/*
+ Ben Allison benski@winamp.com Nov 14 2007
+ Simple reference counted string, to avoid a whole bunch of _wcsdup's in NDE and ml_local
+ */
+
+#pragma once
+#include "foundation/types.h"
+#include "nx/nxstring.h"
+
+enum
+{
+ STRING_IS_WCHAR=0,
+ STRING_IS_NDESTRING=1,
+};
+
+#include "nde_defines.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ NDE_API wchar_t *ndestring_wcsdup(const wchar_t *str);
+ NDE_API wchar_t *ndestring_wcsndup(const wchar_t *str, size_t n);
+ NDE_API wchar_t *ndestring_malloc(size_t str_size);
+ NDE_API void ndestring_release(wchar_t *str);
+ NDE_API void ndestring_retain(wchar_t *str);
+ NDE_API nx_string_t ndestring_get_string(wchar_t *str);
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/Query.h b/Src/nde/Query.h
new file mode 100644
index 00000000..df373af8
--- /dev/null
+++ b/Src/nde/Query.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Query.h"
+#elif defined(_WIN32)
+#include "win/Query.h"
+#elif defined(__ANDROID__)
+#include "android/Query.h"
+#endif
diff --git a/Src/nde/Record.h b/Src/nde/Record.h
new file mode 100644
index 00000000..697ce093
--- /dev/null
+++ b/Src/nde/Record.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Record.h"
+#elif defined(_WIN32)
+#include "win/Record.h"
+#elif defined(__ANDROID__)
+#include "android/Record.h"
+#endif
diff --git a/Src/nde/Scanner.h b/Src/nde/Scanner.h
new file mode 100644
index 00000000..16aed736
--- /dev/null
+++ b/Src/nde/Scanner.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Scanner.h"
+#elif defined(_WIN32)
+#include "win/Scanner.h"
+#elif defined(__ANDROID__)
+#include "android/Scanner.h"
+#endif
diff --git a/Src/nde/StringField.h b/Src/nde/StringField.h
new file mode 100644
index 00000000..99a70ecf
--- /dev/null
+++ b/Src/nde/StringField.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/StringField.h"
+#elif defined(_WIN32)
+#include "win/StringField.h"
+#elif defined(__ANDROID__)
+#include "android/StringField.h"
+#endif
diff --git a/Src/nde/Table.h b/Src/nde/Table.h
new file mode 100644
index 00000000..7e885963
--- /dev/null
+++ b/Src/nde/Table.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Table.h"
+#elif defined(_WIN32)
+#include "win/Table.h"
+#elif defined(__ANDROID__)
+#include "android/Table.h"
+#endif
diff --git a/Src/nde/Vfs.h b/Src/nde/Vfs.h
new file mode 100644
index 00000000..87fee06e
--- /dev/null
+++ b/Src/nde/Vfs.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/Vfs.h"
+#elif defined(_WIN32)
+#include "win/Vfs.h"
+#elif defined(__ANDROID__)
+#include "android/vfs.h"
+#endif
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
+
diff --git a/Src/nde/nde.def b/Src/nde/nde.def
new file mode 100644
index 00000000..e18dd25a
--- /dev/null
+++ b/Src/nde/nde.def
@@ -0,0 +1,33 @@
+EXPORTS
+LinkedListEntry
+LinkedList
+Field
+ColumnField
+~ColumnField
+Compare
+ColumnField::GetDataType
+ColumnField::Compare
+ColumnField::GetFieldName
+ColumnField::GetUniqueDataScanner
+ColumnField::NewScanner
+ColumnField::DeleteScanner
+ColumnField::GetUnique
+IndexField
+StringField
+IntegerField
+GUIDField
+FloatField
+BooleanField
+BinaryField
+BitmapField
+PrivateField
+Record
+Scanner
+Table
+Index
+Filter
+Database
+~Database
+Database
+OpenTable
+NdeApi
diff --git a/Src/nde/nde.h b/Src/nde/nde.h
new file mode 100644
index 00000000..e8a60770
--- /dev/null
+++ b/Src/nde/nde.h
@@ -0,0 +1,168 @@
+/* ---------------------------------------------------------------------------
+
+ Nullsoft Database Engine - Codename: Neutrino
+
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Global Include File
+
+--------------------------------------------------------------------------- */
+
+#ifndef __NDE_H
+#define __NDE_H
+
+// TODO: find better Mac preproc symbol
+#include "nde_defines.h"
+
+extern const char *tSign;
+extern const char *iSign;
+
+// Magic headers
+#define __TABLE_SIGNATURE__ tSign
+#define __INDEX_SIGNATURE__ iSign
+
+// Linked list entry types
+#define UNKNOWN 0
+#define FIELD 1
+#define FILTER 2
+#define SCANNER 3
+
+
+
+// Records constants
+#define NEW_RECORD -128
+#define FIELD_NOT_FOUND -1
+#define INVALID_RECORD -1
+#define NUM_SPECIAL_RECORDS 2 //
+#define FIELDS_RECORD_NUM 0
+#define INDEX_RECORD_NUM 1
+
+// Index constants
+#define PRIMARY_INDEX 255
+#define QFIND_ENTIRE_SCOPE -1
+
+
+
+
+#define throwException(x) {}
+
+#define FILTER_NOT 0
+#define FILTER_AND 1
+#define FILTER_OR 2
+
+#define FILTERS_INVALID -1
+#define FILTERS_INCOMPLETE 1
+#define FILTERS_COMPLETE 1
+#define ADDFILTER_FAILED 2
+
+// Permissions
+#define PERM_DENYALL 0
+#define PERM_READ 1
+#define PERM_READWRITE 3
+
+
+
+// All our classes so we can foreward reference
+class NDE_API LinkedListEntry;
+class LinkedList;
+class Field;
+class ColumnField;
+class IndexField;
+class StringField;
+class IntegerField;
+class Int64Field;
+class Int128Field;
+class GUIDField;
+class FloatField;
+class BooleanField;
+class BinaryField;
+class Binary32Field;
+class BitmapField;
+class PrivateField;
+class FilenameField;
+class Record;
+class NDE_API Scanner;
+class Table;
+class Index;
+class Filter;
+class Database;
+
+#if !defined (WIN32) && !defined(WIN64)
+#define NDE_NOWIN32FILEIO
+#define NO_TABLE_WIN32_LOCKING
+typedef int BOOL;
+typedef void* HANDLE;
+#define TRUE 1
+#define FALSE 0
+#ifdef __APPLE__
+#include <bfc/platform/types.h>
+#else
+#include <foundation/types.h> // TODO
+#endif
+#define HINSTANCE int
+//#define HWND int
+#define DWORD int
+#define wsprintf sprintf
+#define OutputDebugString(x) ;
+#define GetCurrentProcessId() getpid()
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <strings.h>
+#include <string.h>
+#include <wchar.h>
+#include <sys/stat.h>
+void clear_stdin(void);
+
+void CopyFile(const char *filename, const char *destfilename, BOOL b);
+void DeleteFile(const char *filename);
+BOOL MoveFile(const char *filename, const char *destfilename);
+void Sleep(int ms);
+
+#define _stricmp strcasecmp
+#define strcmpi strcasecmp
+#define _strnicmp strncasecmp
+#define strncmpi strncasecmp
+
+#define _wtoi(x) wcstol(x,0,10)
+// TODO: find case insensitive compare on Mac OS X
+#define _wcsicmp wcscmp
+#define _wcsnicmp wcsncmp
+
+#define _MAX_PATH 8192
+#define _MAX_DRIVE 256
+#define _MAX_DIR 7424
+#define _MAX_FNAME 256
+#define _MAX_EXT 256
+
+#define min(x,y) ((x > y) ? y : x)
+#define max(x,y) ((x < y) ? y : x)
+
+#endif
+
+// All our includes+
+
+#include "Vfs.h"
+#include "LinkedList.h"
+#include "Field.h"
+#include "ColumnField.h"
+#include "IndexField.h"
+#include "StringField.h"
+#include "IntegerField.h"
+#include "Int64Field.h"
+#include "Int128Field.h"
+#include "BinaryField.h"
+#include "Binary32Field.h"
+#include "FilenameField.h"
+#include "Record.h"
+#include "Scanner.h"
+#include "Table.h"
+#include "Database.h"
+#include "Index.h"
+#include "Filter.h"
+#include "DBUtils.h"
+
+
+#endif
diff --git a/Src/nde/nde.rc b/Src/nde/nde.rc
new file mode 100644
index 00000000..fcff7711
--- /dev/null
+++ b/Src/nde/nde.rc
@@ -0,0 +1,76 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.K.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/nde/nde.sln b/Src/nde/nde.sln
new file mode 100644
index 00000000..d7ec6ed6
--- /dev/null
+++ b/Src/nde/nde.sln
@@ -0,0 +1,77 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/nde/nde.vcxproj b/Src/nde/nde.vcxproj
new file mode 100644
index 00000000..8cbd95f3
--- /dev/null
+++ b/Src/nde/nde.vcxproj
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{4D25C321-7F8B-424E-9899-D80A364BAF1A}</ProjectGuid>
+ <RootNamespace>nde</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;NDE_EXPORTS;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x86_Debug\$(ProjectName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;NDE_EXPORTS;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x64_Debug\$(ProjectName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;NDE_EXPORTS;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4018;4355;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x86_Release\$(ProjectName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>../Wasabi;../replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;NDE_EXPORTS;_WIN32_WINNT=0x601;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4018;4355;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(ProjectDir)x64_Release\$(ProjectName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Shared\'</Message>
+ </PostBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\replicant\nx\nx.vcxproj">
+ <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\nu\RingBuffer.cpp" />
+ <ClCompile Include="Database.cpp" />
+ <ClCompile Include="DBUtils.cpp" />
+ <ClCompile Include="Field.cpp" />
+ <ClCompile Include="Filter.cpp" />
+ <ClCompile Include="Index.cpp" />
+ <ClCompile Include="Int128Field.cpp" />
+ <ClCompile Include="Int64Field.cpp" />
+ <ClCompile Include="LinkedList.cpp" />
+ <ClCompile Include="NDEString.cpp" />
+ <ClCompile Include="win\Binary32Field.cpp" />
+ <ClCompile Include="win\BinaryField.cpp" />
+ <ClCompile Include="win\ColumnField.cpp" />
+ <ClCompile Include="win\FilenameField.cpp" />
+ <ClCompile Include="win\IndexField.cpp" />
+ <ClCompile Include="win\IndexRecord.cpp" />
+ <ClCompile Include="win\IntegerField.cpp" />
+ <ClCompile Include="win\nde_c.cpp" />
+ <ClCompile Include="win\nde_init.cpp" />
+ <ClCompile Include="win\Query.cpp" />
+ <ClCompile Include="win\Record.cpp" />
+ <ClCompile Include="win\Scanner.cpp" />
+ <ClCompile Include="win\StringField.cpp" />
+ <ClCompile Include="win\Table.cpp" />
+ <ClCompile Include="win\Vfs.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="Database.h" />
+ <ClInclude Include="DBUtils.h" />
+ <ClInclude Include="Field.h" />
+ <ClInclude Include="Filter.h" />
+ <ClInclude Include="Index.h" />
+ <ClInclude Include="Int128Field.h" />
+ <ClInclude Include="Int64Field.h" />
+ <ClInclude Include="LinkedList.h" />
+ <ClInclude Include="nde.h" />
+ <ClInclude Include="NDEString.h" />
+ <ClInclude Include="nde_defines.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="win\Binary32Field.h" />
+ <ClInclude Include="win\BinaryField.h" />
+ <ClInclude Include="win\ColumnField.h" />
+ <ClInclude Include="win\FilenameField.h" />
+ <ClInclude Include="win\IndexField.h" />
+ <ClInclude Include="win\IndexRecord.h" />
+ <ClInclude Include="win\IntegerField.h" />
+ <ClInclude Include="win\nde_c.h" />
+ <ClInclude Include="win\Query.h" />
+ <ClInclude Include="win\Record.h" />
+ <ClInclude Include="win\Scanner.h" />
+ <ClInclude Include="win\StringField.h" />
+ <ClInclude Include="win\Table.h" />
+ <ClInclude Include="win\Vfs.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="nde.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/nde/nde.vcxproj.filters b/Src/nde/nde.vcxproj.filters
new file mode 100644
index 00000000..9bf3eb6b
--- /dev/null
+++ b/Src/nde/nde.vcxproj.filters
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="win\Binary32Field.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\BinaryField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\ColumnField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Database.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DBUtils.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Field.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\FilenameField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Filter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Index.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\IndexField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\IndexRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Int64Field.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Int128Field.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\IntegerField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LinkedList.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\nde_c.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\nde_init.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="NDEString.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\Query.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\Record.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\nu\RingBuffer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\Scanner.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\StringField.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\Table.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="win\Vfs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="win\Binary32Field.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\BinaryField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\ColumnField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Database.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DBUtils.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Field.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\FilenameField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Filter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Index.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\IndexField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\IndexRecord.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Int64Field.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Int128Field.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\IntegerField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LinkedList.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="nde.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\nde_c.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="nde_defines.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="NDEString.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\Query.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\Record.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\Scanner.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\StringField.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\Table.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="win\Vfs.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{9547ac55-c20b-484a-875b-0e9f6787c1f9}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{4278bf6e-44fa-4a76-a1e7-f3d3c96d4df0}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{e61e1158-723a-449b-b09f-bd63c3c611a7}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="nde.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/nde/nde.xcodeproj/project.pbxproj b/Src/nde/nde.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..023634ae
--- /dev/null
+++ b/Src/nde/nde.xcodeproj/project.pbxproj
@@ -0,0 +1,427 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 42;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 0C0879F512A2D8AC001B1FD2 /* NDEString.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C0879F412A2D8AC001B1FD2 /* NDEString.h */; };
+ 0C087AB412A2DB4C001B1FD2 /* IndexRecord.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C087AB212A2DB4C001B1FD2 /* IndexRecord.cpp */; };
+ 0C087AB512A2DB4C001B1FD2 /* IndexRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087AB312A2DB4C001B1FD2 /* IndexRecord.h */; };
+ 0C087AC312A2DB85001B1FD2 /* nde_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C087AC012A2DB85001B1FD2 /* nde_c.cpp */; };
+ 0C087AC412A2DB85001B1FD2 /* nde_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087AC112A2DB85001B1FD2 /* nde_c.h */; };
+ 0C087AC512A2DB85001B1FD2 /* nde_defines.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087AC212A2DB85001B1FD2 /* nde_defines.h */; };
+ 0C087BCA12A2E962001B1FD2 /* Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C087BB612A2E962001B1FD2 /* Field.cpp */; };
+ 0C087BCB12A2E962001B1FD2 /* Field.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087BB712A2E962001B1FD2 /* Field.h */; };
+ 0C087BD012A2E962001B1FD2 /* Int64Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C087BBC12A2E962001B1FD2 /* Int64Field.cpp */; };
+ 0C087BD112A2E962001B1FD2 /* Int64Field.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087BBD12A2E962001B1FD2 /* Int64Field.h */; };
+ 0C087BD212A2E962001B1FD2 /* Int128Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C087BBE12A2E962001B1FD2 /* Int128Field.cpp */; };
+ 0C087BD312A2E962001B1FD2 /* Int128Field.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C087BBF12A2E962001B1FD2 /* Int128Field.h */; };
+ 0C89CB7912A5EFFA00366857 /* IndexField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C89CB7712A5EFFA00366857 /* IndexField.cpp */; };
+ 0C89CB7A12A5EFFA00366857 /* IndexField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C89CB7812A5EFFA00366857 /* IndexField.h */; };
+ 0C99F2380B93CE4C00AB7751 /* Crc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F2140B93CE4C00AB7751 /* Crc.cpp */; };
+ 0C99F2390B93CE4C00AB7751 /* CRC.H in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2150B93CE4C00AB7751 /* CRC.H */; };
+ 0C99F23A0B93CE4C00AB7751 /* Database.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F2160B93CE4C00AB7751 /* Database.cpp */; };
+ 0C99F23B0B93CE4C00AB7751 /* Database.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2170B93CE4C00AB7751 /* Database.h */; };
+ 0C99F23C0B93CE4C00AB7751 /* DBUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F2180B93CE4C00AB7751 /* DBUtils.cpp */; };
+ 0C99F23D0B93CE4C00AB7751 /* DBUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2190B93CE4C00AB7751 /* DBUtils.h */; };
+ 0C99F2410B93CE4C00AB7751 /* Filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F21D0B93CE4C00AB7751 /* Filter.cpp */; };
+ 0C99F2420B93CE4C00AB7751 /* Filter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F21E0B93CE4C00AB7751 /* Filter.h */; };
+ 0C99F2430B93CE4C00AB7751 /* Index.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F21F0B93CE4C00AB7751 /* Index.cpp */; };
+ 0C99F2440B93CE4C00AB7751 /* Index.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2200B93CE4C00AB7751 /* Index.h */; };
+ 0C99F2490B93CE4C00AB7751 /* LinkedList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F2250B93CE4C00AB7751 /* LinkedList.cpp */; };
+ 0C99F24A0B93CE4C00AB7751 /* LinkedList.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2260B93CE4C00AB7751 /* LinkedList.h */; };
+ 0C99F24B0B93CE4C00AB7751 /* nde.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F2270B93CE4C00AB7751 /* nde.h */; };
+ 0C99F24E0B93CE4C00AB7751 /* Record.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F22A0B93CE4C00AB7751 /* Record.cpp */; };
+ 0C99F24F0B93CE4C00AB7751 /* Record.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F22B0B93CE4C00AB7751 /* Record.h */; };
+ 0C99F2500B93CE4C00AB7751 /* Scanner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C99F22C0B93CE4C00AB7751 /* Scanner.cpp */; };
+ 0C99F2510B93CE4C00AB7751 /* Scanner.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C99F22D0B93CE4C00AB7751 /* Scanner.h */; };
+ 0CBA4FEF12A5DD6D008056C7 /* Binary32Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FE912A5DD6D008056C7 /* Binary32Field.cpp */; };
+ 0CBA4FF012A5DD6D008056C7 /* Binary32Field.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FEA12A5DD6D008056C7 /* Binary32Field.h */; };
+ 0CBA4FF112A5DD6D008056C7 /* BinaryField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FEB12A5DD6D008056C7 /* BinaryField.cpp */; };
+ 0CBA4FF212A5DD6D008056C7 /* BinaryField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FEC12A5DD6D008056C7 /* BinaryField.h */; };
+ 0CBA4FF312A5DD6D008056C7 /* ColumnField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FED12A5DD6D008056C7 /* ColumnField.cpp */; };
+ 0CBA4FF412A5DD6D008056C7 /* ColumnField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FEE12A5DD6D008056C7 /* ColumnField.h */; };
+ 0CBA4FF912A5DD77008056C7 /* FilenameField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FF512A5DD77008056C7 /* FilenameField.cpp */; };
+ 0CBA4FFA12A5DD77008056C7 /* FilenameField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FF612A5DD77008056C7 /* FilenameField.h */; };
+ 0CBA4FFB12A5DD77008056C7 /* StringField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FF712A5DD77008056C7 /* StringField.cpp */; };
+ 0CBA4FFC12A5DD77008056C7 /* StringField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FF812A5DD77008056C7 /* StringField.h */; };
+ 0CBA4FFF12A5DD80008056C7 /* Vfs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CBA4FFD12A5DD80008056C7 /* Vfs.cpp */; };
+ 0CBA500012A5DD80008056C7 /* Vfs.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CBA4FFE12A5DD80008056C7 /* Vfs.h */; };
+ 0CC9A3A712A723F500BCFEA6 /* IntegerField.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CC9A3A512A723F500BCFEA6 /* IntegerField.cpp */; };
+ 0CC9A3A812A723F500BCFEA6 /* IntegerField.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC9A3A612A723F500BCFEA6 /* IntegerField.h */; };
+ 0CC9A3AB12A7240400BCFEA6 /* Table.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CC9A3A912A7240400BCFEA6 /* Table.cpp */; };
+ 0CC9A3AC12A7240400BCFEA6 /* Table.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC9A3AA12A7240400BCFEA6 /* Table.h */; };
+ 0CC9A3AF12A7240900BCFEA6 /* Query.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CC9A3AD12A7240900BCFEA6 /* Query.cpp */; };
+ 0CC9A3B012A7240900BCFEA6 /* Query.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC9A3AE12A7240900BCFEA6 /* Query.h */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 0C0879F412A2D8AC001B1FD2 /* NDEString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NDEString.h; sourceTree = "<group>"; };
+ 0C087AB212A2DB4C001B1FD2 /* IndexRecord.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IndexRecord.cpp; sourceTree = "<group>"; };
+ 0C087AB312A2DB4C001B1FD2 /* IndexRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IndexRecord.h; sourceTree = "<group>"; };
+ 0C087AC012A2DB85001B1FD2 /* nde_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = nde_c.cpp; sourceTree = "<group>"; };
+ 0C087AC112A2DB85001B1FD2 /* nde_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nde_c.h; sourceTree = "<group>"; };
+ 0C087AC212A2DB85001B1FD2 /* nde_defines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nde_defines.h; sourceTree = "<group>"; };
+ 0C087BB612A2E962001B1FD2 /* Field.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Field.cpp; sourceTree = "<group>"; };
+ 0C087BB712A2E962001B1FD2 /* Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Field.h; sourceTree = "<group>"; };
+ 0C087BBC12A2E962001B1FD2 /* Int64Field.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Int64Field.cpp; sourceTree = "<group>"; };
+ 0C087BBD12A2E962001B1FD2 /* Int64Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Int64Field.h; sourceTree = "<group>"; };
+ 0C087BBE12A2E962001B1FD2 /* Int128Field.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Int128Field.cpp; sourceTree = "<group>"; };
+ 0C087BBF12A2E962001B1FD2 /* Int128Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Int128Field.h; sourceTree = "<group>"; };
+ 0C89CB7712A5EFFA00366857 /* IndexField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IndexField.cpp; path = osx/IndexField.cpp; sourceTree = "<group>"; };
+ 0C89CB7812A5EFFA00366857 /* IndexField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IndexField.h; path = osx/IndexField.h; sourceTree = "<group>"; };
+ 0C99F2140B93CE4C00AB7751 /* Crc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Crc.cpp; sourceTree = "<group>"; };
+ 0C99F2150B93CE4C00AB7751 /* CRC.H */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CRC.H; sourceTree = "<group>"; };
+ 0C99F2160B93CE4C00AB7751 /* Database.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Database.cpp; sourceTree = "<group>"; };
+ 0C99F2170B93CE4C00AB7751 /* Database.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Database.h; sourceTree = "<group>"; };
+ 0C99F2180B93CE4C00AB7751 /* DBUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DBUtils.cpp; sourceTree = "<group>"; };
+ 0C99F2190B93CE4C00AB7751 /* DBUtils.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DBUtils.h; sourceTree = "<group>"; };
+ 0C99F21D0B93CE4C00AB7751 /* Filter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Filter.cpp; sourceTree = "<group>"; };
+ 0C99F21E0B93CE4C00AB7751 /* Filter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Filter.h; sourceTree = "<group>"; };
+ 0C99F21F0B93CE4C00AB7751 /* Index.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Index.cpp; sourceTree = "<group>"; };
+ 0C99F2200B93CE4C00AB7751 /* Index.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Index.h; sourceTree = "<group>"; };
+ 0C99F2250B93CE4C00AB7751 /* LinkedList.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LinkedList.cpp; sourceTree = "<group>"; };
+ 0C99F2260B93CE4C00AB7751 /* LinkedList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LinkedList.h; sourceTree = "<group>"; };
+ 0C99F2270B93CE4C00AB7751 /* nde.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = nde.h; sourceTree = "<group>"; };
+ 0C99F22A0B93CE4C00AB7751 /* Record.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Record.cpp; sourceTree = "<group>"; };
+ 0C99F22B0B93CE4C00AB7751 /* Record.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Record.h; sourceTree = "<group>"; };
+ 0C99F22C0B93CE4C00AB7751 /* Scanner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Scanner.cpp; sourceTree = "<group>"; };
+ 0C99F22D0B93CE4C00AB7751 /* Scanner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Scanner.h; sourceTree = "<group>"; };
+ 0CBA4FE912A5DD6D008056C7 /* Binary32Field.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Binary32Field.cpp; path = osx/Binary32Field.cpp; sourceTree = "<group>"; };
+ 0CBA4FEA12A5DD6D008056C7 /* Binary32Field.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Binary32Field.h; path = osx/Binary32Field.h; sourceTree = "<group>"; };
+ 0CBA4FEB12A5DD6D008056C7 /* BinaryField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BinaryField.cpp; path = osx/BinaryField.cpp; sourceTree = "<group>"; };
+ 0CBA4FEC12A5DD6D008056C7 /* BinaryField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BinaryField.h; path = osx/BinaryField.h; sourceTree = "<group>"; };
+ 0CBA4FED12A5DD6D008056C7 /* ColumnField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ColumnField.cpp; path = osx/ColumnField.cpp; sourceTree = "<group>"; };
+ 0CBA4FEE12A5DD6D008056C7 /* ColumnField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ColumnField.h; path = osx/ColumnField.h; sourceTree = "<group>"; };
+ 0CBA4FF512A5DD77008056C7 /* FilenameField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FilenameField.cpp; path = osx/FilenameField.cpp; sourceTree = "<group>"; };
+ 0CBA4FF612A5DD77008056C7 /* FilenameField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FilenameField.h; path = osx/FilenameField.h; sourceTree = "<group>"; };
+ 0CBA4FF712A5DD77008056C7 /* StringField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringField.cpp; path = osx/StringField.cpp; sourceTree = "<group>"; };
+ 0CBA4FF812A5DD77008056C7 /* StringField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringField.h; path = osx/StringField.h; sourceTree = "<group>"; };
+ 0CBA4FFD12A5DD80008056C7 /* Vfs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Vfs.cpp; path = osx/Vfs.cpp; sourceTree = "<group>"; };
+ 0CBA4FFE12A5DD80008056C7 /* Vfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Vfs.h; path = osx/Vfs.h; sourceTree = "<group>"; };
+ 0CC9A3A512A723F500BCFEA6 /* IntegerField.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IntegerField.cpp; path = osx/IntegerField.cpp; sourceTree = "<group>"; };
+ 0CC9A3A612A723F500BCFEA6 /* IntegerField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IntegerField.h; path = osx/IntegerField.h; sourceTree = "<group>"; };
+ 0CC9A3A912A7240400BCFEA6 /* Table.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Table.cpp; path = osx/Table.cpp; sourceTree = "<group>"; };
+ 0CC9A3AA12A7240400BCFEA6 /* Table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Table.h; path = osx/Table.h; sourceTree = "<group>"; };
+ 0CC9A3AD12A7240900BCFEA6 /* Query.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Query.cpp; path = osx/Query.cpp; sourceTree = "<group>"; };
+ 0CC9A3AE12A7240900BCFEA6 /* Query.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Query.h; path = osx/Query.h; sourceTree = "<group>"; };
+ D2AAC046055464E500DB518D /* libnde.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libnde.a; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ D289987405E68DCB004EDB86 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 08FB7794FE84155DC02AAC07 /* nde */ = {
+ isa = PBXGroup;
+ children = (
+ 0C087BAD12A2E93E001B1FD2 /* Fields */,
+ 0C087ABE12A2DB7A001B1FD2 /* API */,
+ 0C087AA212A2DB0E001B1FD2 /* Records */,
+ 08FB7795FE84155DC02AAC07 /* Source */,
+ C6A0FF2B0290797F04C91782 /* Documentation */,
+ 1AB674ADFE9D54B511CA2CBB /* Products */,
+ );
+ name = nde;
+ sourceTree = "<group>";
+ };
+ 08FB7795FE84155DC02AAC07 /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ 0C99F2140B93CE4C00AB7751 /* Crc.cpp */,
+ 0C99F2150B93CE4C00AB7751 /* CRC.H */,
+ 0C99F2160B93CE4C00AB7751 /* Database.cpp */,
+ 0C99F2170B93CE4C00AB7751 /* Database.h */,
+ 0C99F2180B93CE4C00AB7751 /* DBUtils.cpp */,
+ 0C99F2190B93CE4C00AB7751 /* DBUtils.h */,
+ 0C99F21D0B93CE4C00AB7751 /* Filter.cpp */,
+ 0C99F21E0B93CE4C00AB7751 /* Filter.h */,
+ 0C99F21F0B93CE4C00AB7751 /* Index.cpp */,
+ 0C99F2200B93CE4C00AB7751 /* Index.h */,
+ 0C99F2250B93CE4C00AB7751 /* LinkedList.cpp */,
+ 0C99F2260B93CE4C00AB7751 /* LinkedList.h */,
+ 0CC9A3AD12A7240900BCFEA6 /* Query.cpp */,
+ 0CC9A3AE12A7240900BCFEA6 /* Query.h */,
+ 0C99F2270B93CE4C00AB7751 /* nde.h */,
+ 0CC9A3A912A7240400BCFEA6 /* Table.cpp */,
+ 0CC9A3AA12A7240400BCFEA6 /* Table.h */,
+ 0C0879F412A2D8AC001B1FD2 /* NDEString.h */,
+ 0C99F22C0B93CE4C00AB7751 /* Scanner.cpp */,
+ 0C99F22D0B93CE4C00AB7751 /* Scanner.h */,
+ 0CBA4FFD12A5DD80008056C7 /* Vfs.cpp */,
+ 0CBA4FFE12A5DD80008056C7 /* Vfs.h */,
+ );
+ name = Source;
+ sourceTree = "<group>";
+ };
+ 0C087AA212A2DB0E001B1FD2 /* Records */ = {
+ isa = PBXGroup;
+ children = (
+ 0C087AB212A2DB4C001B1FD2 /* IndexRecord.cpp */,
+ 0C087AB312A2DB4C001B1FD2 /* IndexRecord.h */,
+ 0C99F22A0B93CE4C00AB7751 /* Record.cpp */,
+ 0C99F22B0B93CE4C00AB7751 /* Record.h */,
+ );
+ name = Records;
+ sourceTree = "<group>";
+ };
+ 0C087ABE12A2DB7A001B1FD2 /* API */ = {
+ isa = PBXGroup;
+ children = (
+ 0C087AC012A2DB85001B1FD2 /* nde_c.cpp */,
+ 0C087AC112A2DB85001B1FD2 /* nde_c.h */,
+ 0C087AC212A2DB85001B1FD2 /* nde_defines.h */,
+ );
+ name = API;
+ sourceTree = "<group>";
+ };
+ 0C087BAD12A2E93E001B1FD2 /* Fields */ = {
+ isa = PBXGroup;
+ children = (
+ 0CC9A3A512A723F500BCFEA6 /* IntegerField.cpp */,
+ 0CC9A3A612A723F500BCFEA6 /* IntegerField.h */,
+ 0C89CB7712A5EFFA00366857 /* IndexField.cpp */,
+ 0C89CB7812A5EFFA00366857 /* IndexField.h */,
+ 0CBA4FE912A5DD6D008056C7 /* Binary32Field.cpp */,
+ 0CBA4FEA12A5DD6D008056C7 /* Binary32Field.h */,
+ 0CBA4FEB12A5DD6D008056C7 /* BinaryField.cpp */,
+ 0CBA4FEC12A5DD6D008056C7 /* BinaryField.h */,
+ 0CBA4FED12A5DD6D008056C7 /* ColumnField.cpp */,
+ 0CBA4FEE12A5DD6D008056C7 /* ColumnField.h */,
+ 0C087BB612A2E962001B1FD2 /* Field.cpp */,
+ 0C087BB712A2E962001B1FD2 /* Field.h */,
+ 0CBA4FF512A5DD77008056C7 /* FilenameField.cpp */,
+ 0CBA4FF612A5DD77008056C7 /* FilenameField.h */,
+ 0C087BBE12A2E962001B1FD2 /* Int128Field.cpp */,
+ 0C087BBF12A2E962001B1FD2 /* Int128Field.h */,
+ 0C087BBC12A2E962001B1FD2 /* Int64Field.cpp */,
+ 0C087BBD12A2E962001B1FD2 /* Int64Field.h */,
+ 0CBA4FF712A5DD77008056C7 /* StringField.cpp */,
+ 0CBA4FF812A5DD77008056C7 /* StringField.h */,
+ );
+ name = Fields;
+ sourceTree = "<group>";
+ };
+ 1AB674ADFE9D54B511CA2CBB /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ D2AAC046055464E500DB518D /* libnde.a */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ C6A0FF2B0290797F04C91782 /* Documentation */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Documentation;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ D2AAC043055464E500DB518D /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0C99F2390B93CE4C00AB7751 /* CRC.H in Headers */,
+ 0C99F23B0B93CE4C00AB7751 /* Database.h in Headers */,
+ 0C99F23D0B93CE4C00AB7751 /* DBUtils.h in Headers */,
+ 0C99F2420B93CE4C00AB7751 /* Filter.h in Headers */,
+ 0C99F2440B93CE4C00AB7751 /* Index.h in Headers */,
+ 0C99F24A0B93CE4C00AB7751 /* LinkedList.h in Headers */,
+ 0C99F24B0B93CE4C00AB7751 /* nde.h in Headers */,
+ 0C99F24F0B93CE4C00AB7751 /* Record.h in Headers */,
+ 0C99F2510B93CE4C00AB7751 /* Scanner.h in Headers */,
+ 0C0879F512A2D8AC001B1FD2 /* NDEString.h in Headers */,
+ 0C087AB512A2DB4C001B1FD2 /* IndexRecord.h in Headers */,
+ 0C087AC412A2DB85001B1FD2 /* nde_c.h in Headers */,
+ 0C087AC512A2DB85001B1FD2 /* nde_defines.h in Headers */,
+ 0C087BCB12A2E962001B1FD2 /* Field.h in Headers */,
+ 0C087BD112A2E962001B1FD2 /* Int64Field.h in Headers */,
+ 0C087BD312A2E962001B1FD2 /* Int128Field.h in Headers */,
+ 0CBA4FF012A5DD6D008056C7 /* Binary32Field.h in Headers */,
+ 0CBA4FF212A5DD6D008056C7 /* BinaryField.h in Headers */,
+ 0CBA4FF412A5DD6D008056C7 /* ColumnField.h in Headers */,
+ 0CBA4FFA12A5DD77008056C7 /* FilenameField.h in Headers */,
+ 0CBA4FFC12A5DD77008056C7 /* StringField.h in Headers */,
+ 0CBA500012A5DD80008056C7 /* Vfs.h in Headers */,
+ 0C89CB7A12A5EFFA00366857 /* IndexField.h in Headers */,
+ 0CC9A3A812A723F500BCFEA6 /* IntegerField.h in Headers */,
+ 0CC9A3AC12A7240400BCFEA6 /* Table.h in Headers */,
+ 0CC9A3B012A7240900BCFEA6 /* Query.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ D2AAC045055464E500DB518D /* nde */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1DEB91EB08733DB70010E9CD /* Build configuration list for PBXNativeTarget "nde" */;
+ buildPhases = (
+ D2AAC043055464E500DB518D /* Headers */,
+ D2AAC044055464E500DB518D /* Sources */,
+ D289987405E68DCB004EDB86 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = nde;
+ productName = nde;
+ productReference = D2AAC046055464E500DB518D /* libnde.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 08FB7793FE84155DC02AAC07 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ ORGANIZATIONNAME = "Nullsoft, Inc.";
+ };
+ buildConfigurationList = 1DEB91EF08733DB70010E9CD /* Build configuration list for PBXProject "nde" */;
+ compatibilityVersion = "Xcode 2.4";
+ developmentRegion = English;
+ hasScannedForEncodings = 1;
+ knownRegions = (
+ English,
+ Japanese,
+ French,
+ German,
+ );
+ mainGroup = 08FB7794FE84155DC02AAC07 /* nde */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ D2AAC045055464E500DB518D /* nde */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ D2AAC044055464E500DB518D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0C99F2380B93CE4C00AB7751 /* Crc.cpp in Sources */,
+ 0C99F23A0B93CE4C00AB7751 /* Database.cpp in Sources */,
+ 0C99F23C0B93CE4C00AB7751 /* DBUtils.cpp in Sources */,
+ 0C99F2410B93CE4C00AB7751 /* Filter.cpp in Sources */,
+ 0C99F2430B93CE4C00AB7751 /* Index.cpp in Sources */,
+ 0C99F2490B93CE4C00AB7751 /* LinkedList.cpp in Sources */,
+ 0C99F24E0B93CE4C00AB7751 /* Record.cpp in Sources */,
+ 0C99F2500B93CE4C00AB7751 /* Scanner.cpp in Sources */,
+ 0C087AB412A2DB4C001B1FD2 /* IndexRecord.cpp in Sources */,
+ 0C087AC312A2DB85001B1FD2 /* nde_c.cpp in Sources */,
+ 0C087BCA12A2E962001B1FD2 /* Field.cpp in Sources */,
+ 0C087BD012A2E962001B1FD2 /* Int64Field.cpp in Sources */,
+ 0C087BD212A2E962001B1FD2 /* Int128Field.cpp in Sources */,
+ 0CBA4FEF12A5DD6D008056C7 /* Binary32Field.cpp in Sources */,
+ 0CBA4FF112A5DD6D008056C7 /* BinaryField.cpp in Sources */,
+ 0CBA4FF312A5DD6D008056C7 /* ColumnField.cpp in Sources */,
+ 0CBA4FF912A5DD77008056C7 /* FilenameField.cpp in Sources */,
+ 0CBA4FFB12A5DD77008056C7 /* StringField.cpp in Sources */,
+ 0CBA4FFF12A5DD80008056C7 /* Vfs.cpp in Sources */,
+ 0C89CB7912A5EFFA00366857 /* IndexField.cpp in Sources */,
+ 0CC9A3A712A723F500BCFEA6 /* IntegerField.cpp in Sources */,
+ 0CC9A3AB12A7240400BCFEA6 /* Table.cpp in Sources */,
+ 0CC9A3AF12A7240900BCFEA6 /* Query.cpp in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1DEB91EC08733DB70010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = nde;
+ ZERO_LINK = YES;
+ };
+ name = Debug;
+ };
+ 1DEB91ED08733DB70010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1)";
+ ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1 = "x86_64 i386 ppc";
+ GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
+ GCC_MODEL_TUNING = G5;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = nde;
+ };
+ name = Release;
+ };
+ 1DEB91F008733DB70010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1)";
+ ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1 = "x86_64 i386 ppc";
+ GCC_ENABLE_CPP_EXCEPTIONS = NO;
+ GCC_ENABLE_CPP_RTTI = NO;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = ../Wasabi;
+ PREBINDING = NO;
+ SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
+ };
+ name = Debug;
+ };
+ 1DEB91F108733DB70010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_64_BIT_PRE_XCODE_3_1)";
+ ARCHS_STANDARD_64_BIT_PRE_XCODE_3_1 = x86_64;
+ GCC_ENABLE_CPP_EXCEPTIONS = NO;
+ GCC_ENABLE_CPP_RTTI = NO;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = ../Wasabi;
+ PREBINDING = NO;
+ SDKROOT = /Developer/SDKs/MacOSX10.5.sdk;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1DEB91EB08733DB70010E9CD /* Build configuration list for PBXNativeTarget "nde" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB91EC08733DB70010E9CD /* Debug */,
+ 1DEB91ED08733DB70010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1DEB91EF08733DB70010E9CD /* Build configuration list for PBXProject "nde" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB91F008733DB70010E9CD /* Debug */,
+ 1DEB91F108733DB70010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
+}
diff --git a/Src/nde/nde_c.h b/Src/nde/nde_c.h
new file mode 100644
index 00000000..1fe8f546
--- /dev/null
+++ b/Src/nde/nde_c.h
@@ -0,0 +1,8 @@
+#pragma once
+#ifdef __APPLE__
+#include "osx/nde_c.h"
+#elif defined(_WIN32)
+#include "win/nde_c.h"
+#elif defined(__ANDROID__)
+#include "android/nde_c.h"
+#endif
diff --git a/Src/nde/nde_defines.h b/Src/nde/nde_defines.h
new file mode 100644
index 00000000..7b8cbf10
--- /dev/null
+++ b/Src/nde/nde_defines.h
@@ -0,0 +1,69 @@
+#pragma once
+/* important defines and enums that are shared between the C++ api and the C api */
+
+#define NDE_CACHE TRUE
+#define NDE_NOCACHE FALSE
+
+#define NDE_OPEN_ALWAYS TRUE
+#define NDE_OPEN_EXISTING FALSE
+
+#if defined(WIN32_NOLIB) || !defined(WIN32) && !defined(WIN64)
+#define NDE_API
+#else
+ #ifdef NDE_EXPORTS
+ #define NDE_API __declspec(dllexport)
+ #else
+ #define NDE_API __declspec(dllimport)
+ #endif
+#endif
+
+// Field types
+enum
+{
+ FIELD_COLUMN = 0,
+ FIELD_INDEX = 1,
+ FIELD_REDIRECTOR =2,
+ FIELD_STRING =3,
+ FIELD_INTEGER = 4,
+ FIELD_BOOLEAN = 5,
+ FIELD_BINARY =6, // max size 65536
+ FIELD_GUID =7,
+ FIELD_PRIVATE = 8,
+ FIELD_BITMAP =6,
+ FIELD_FLOAT = 9,
+ FIELD_DATETIME =10,
+ FIELD_LENGTH =11,
+ FIELD_FILENAME =12,
+ FIELD_INT64 = 13,
+ FIELD_BINARY32 =14, // binary field, but 32bit sizes instead of 16bit
+ FIELD_INT128 = 15, // mainly for storing MD5 hashes
+ FIELD_UNKNOWN = 255,
+ FIELD_CLONE = 255,// internal use
+};
+
+// Filter types
+enum {
+ FILTER_NONE = 100,
+ FILTER_EQUALS,
+ FILTER_NOTEQUALS,
+ FILTER_CONTAINS,
+ FILTER_NOTCONTAINS,
+ FILTER_ABOVE,
+ FILTER_BELOW,
+ FILTER_ABOVEOREQUAL,
+ FILTER_BELOWOREQUAL,
+ FILTER_BEGINS,
+ FILTER_ENDS,
+ FILTER_LIKE,
+ FILTER_ISEMPTY,
+ FILTER_ISNOTEMPTY,
+ FILTER_BEGINSLIKE,
+};
+
+// compare modes
+#define COMPARE_MODE_CONTAINS 1
+#define COMPARE_MODE_EXACT 2
+#define COMPARE_MODE_STARTS 3
+
+// scanner 'from' special constant
+#define FIRST_RECORD -1
diff --git a/Src/nde/osx/Binary32Field.cpp b/Src/nde/osx/Binary32Field.cpp
new file mode 100644
index 00000000..dce7c8cc
--- /dev/null
+++ b/Src/nde/osx/Binary32Field.cpp
@@ -0,0 +1,78 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Binary32Field Class
+
+--------------------------------------------------------------------------- */
+
+#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_t size = c;
+ uint8_t *buf = (uint8_t *)malloc(size);
+ GET_BINARY(buf, data, c, pos);
+ Data = CFDataCreateWithBytesNoCopy(NULL, buf, size, kCFAllocatorMalloc);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Binary32Field::WriteTypedData(uint8_t *data, size_t len)
+{
+ uint32_t c;
+ size_t pos = 0;
+
+ CHECK_INT(len); //len-=4;
+
+ size_t Size = CFDataGetLength(Data);
+ if (Data && Size<=len)
+ {
+ c = CFDataGetLength(Data);
+ PUT_INT(c); pos += 4;
+ CFDataGetBytes(Data, CFRangeMake(0, Size), data+pos);
+ }
+ else
+ {
+ PUT_INT(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+size_t Binary32Field::GetDataSize(void)
+{
+ if (!Data)
+ return 4;
+ return CFDataGetLength(Data) + 4;
+}
diff --git a/Src/nde/osx/Binary32Field.h b/Src/nde/osx/Binary32Field.h
new file mode 100644
index 00000000..ddd794d7
--- /dev/null
+++ b/Src/nde/osx/Binary32Field.h
@@ -0,0 +1,32 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Binary32Field Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __NDE_BINARY32FIELD_H
+#define __NDE_BINARY32FIELD_H
+
+#include "BinaryField.h"
+#include <bfc/platform/types.h>
+
+class Binary32Field : public BinaryField
+{
+protected:
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ void InitField(void);
+
+public:
+ Binary32Field(const uint8_t *Data, size_t len);
+ Binary32Field();
+};
+
+#endif
diff --git a/Src/nde/osx/BinaryField.cpp b/Src/nde/osx/BinaryField.cpp
new file mode 100644
index 00000000..7ff0ed4c
--- /dev/null
+++ b/Src/nde/osx/BinaryField.cpp
@@ -0,0 +1,172 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ BinaryField Class
+
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "BinaryField.h"
+
+//---------------------------------------------------------------------------
+BinaryField::BinaryField(const uint8_t *_Data, int len)
+{
+ InitField();
+ Type = FIELD_BINARY;
+ if (_Data && len > 0)
+ {
+ Data = CFDataCreate(NULL, _Data, len);
+ }
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::InitField(void)
+{
+ Type = FIELD_BINARY;
+ Data = NULL;
+}
+
+//---------------------------------------------------------------------------
+BinaryField::BinaryField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+BinaryField::~BinaryField()
+{
+ CFRelease(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)
+ {
+ Data = CFDataCreate(NULL, data+pos, c);
+ }
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::WriteTypedData(uint8_t *data, size_t len)
+{
+ unsigned short c;
+ size_t pos = 0;
+
+ CHECK_SHORT(len);
+
+ size_t Size = CFDataGetLength(Data);
+ if (Data && Size<=len)
+ {
+ c = CFDataGetLength(Data);
+ PUT_SHORT(c); pos += 2;
+ CFDataGetBytes(Data, CFRangeMake(0, Size), data+pos);
+ }
+ else
+ {
+ PUT_SHORT(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+const uint8_t *BinaryField::GetData(size_t *len)
+{
+ if (len)
+ *len = CFDataGetLength(Data);
+ return CFDataGetBytePtr(Data);
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::SetData(const uint8_t *_Data, size_t len)
+{
+ if (!_Data || !len) return;
+ if (Data)
+ CFRelease(Data);
+ Data = CFDataCreate(NULL, _Data, len);
+}
+
+CFDataRef BinaryField::GetCFData()
+{
+ return Data;
+}
+
+//---------------------------------------------------------------------------
+size_t BinaryField::GetDataSize(void)
+{
+ if (!Data) return 2;
+ return CFDataGetLength(Data) + 2;
+}
+
+//---------------------------------------------------------------------------
+int BinaryField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ size_t compare_length;
+ const uint8_t *compare_data = ((BinaryField*)Entry)->GetData(&compare_length);
+
+ size_t size;
+ const uint8_t *data = GetData(&size);
+ return memcmp(data, compare_data, min(compare_length, size));
+}
+
+//---------------------------------------------------------------------------
+bool BinaryField::ApplyFilter(Field *FilterData, int op)
+{
+ size_t l, s;
+ const uint8_t *p = ((BinaryField *)FilterData)->GetData(&l);
+ const uint8_t *d = GetData(&s);
+ if (!p)
+ p = (const uint8_t *)"";
+ if (!d)
+ d = (const uint8_t *)"";
+ bool r;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ if (l != s)
+ r = false;
+ else
+ r = !memcmp(d, p, min(s, l));
+ break;
+ case FILTER_CONTAINS:
+ if (l > s)
+ r = FALSE;
+ else
+ r = !!memmem(d, p, s, l);
+ break;
+ case FILTER_ABOVE:
+ r = (memcmp(d, p, min(s, l)) > 0);
+ break;
+ case FILTER_BELOW:
+ r = (memcmp(d, p, min(s, l)) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (memcmp(d, p, min(s, l)) <= 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (memcmp(d, p, min(s, l)) >= 0);
+ break;
+ case FILTER_ISEMPTY:
+ r = (s == 0);
+ break;
+ case FILTER_ISNOTEMPTY:
+ r = (s != 0);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+}
+
diff --git a/Src/nde/osx/BinaryField.h b/Src/nde/osx/BinaryField.h
new file mode 100644
index 00000000..61b1768c
--- /dev/null
+++ b/Src/nde/osx/BinaryField.h
@@ -0,0 +1,38 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ BinaryField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __BINARYFIELD_H
+#define __BINARYFIELD_H
+
+#include "Field.h"
+#include <bfc/platform/types.h>
+class BinaryField : public Field
+{
+protected:
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ virtual bool ApplyFilter(Field *Data, int flags);
+ void InitField(void);
+ CFDataRef Data;
+
+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);
+ CFDataRef GetCFData();
+};
+
+#endif
diff --git a/Src/nde/osx/ColumnField.cpp b/Src/nde/osx/ColumnField.cpp
new file mode 100644
index 00000000..8c1b263d
--- /dev/null
+++ b/Src/nde/osx/ColumnField.cpp
@@ -0,0 +1,160 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ColumnField Class
+Mac OS X implementation
+
+Field data layout:
+[1 byte] Field Type
+[1 byte] Unused (maybe convert to 'searchable')
+[1 byte] Name Length
+[Name Length bytes] Name (UTF-8)
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+
+//---------------------------------------------------------------------------
+ColumnField::ColumnField(unsigned char FieldID, CFStringRef FieldName, unsigned char FieldType, Table *parentTable)
+{
+ InitField();
+ Type = FIELD_COLUMN;
+ MyType = FieldType;
+ Name = (CFStringRef)CFRetain(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)
+ CFRelease(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;
+
+ CHECK_CHAR(len);
+ MyType = GET_CHAR(); pos++;
+
+ CHECK_CHAR(len);
+ pos++;
+
+ CHECK_CHAR(len);
+ c = GET_CHAR(); pos++;
+ if (c)
+ {
+ CHECK_BIN(len, c);
+ if (Name)
+ CFRelease(Name);
+ Name = CFStringCreateWithBytes(kCFAllocatorDefault, data+pos, c, kCFStringEncodingUTF8, false);
+ }
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::WriteTypedData(uint8_t *data, size_t len)
+{
+ int pos = 0;
+
+ CHECK_CHAR(len);
+ PUT_CHAR(MyType); pos++;
+
+ CHECK_CHAR(len);
+ PUT_CHAR(0/*(char)indexUnique*/);
+ pos++;
+
+ if (Name)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, NULL, 0, &lengthRequired);
+ CHECK_BIN(len, lengthRequired+1);
+ PUT_CHAR(lengthRequired); pos++;
+
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, data+pos, lengthRequired, 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;
+ }
+}
+
+//---------------------------------------------------------------------------
+CFStringRef ColumnField::GetFieldName(void)
+{
+ return Name;
+}
+
+//---------------------------------------------------------------------------
+size_t ColumnField::GetDataSize(void)
+{
+ if (Name)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, NULL, 0, &lengthRequired);
+ return lengthRequired+3;
+ }
+ else
+ return 3;
+}
+
+//---------------------------------------------------------------------------
+int ColumnField::Compare(Field * /*Entry*/)
+{
+ return 0;
+}
+
+
diff --git a/Src/nde/osx/ColumnField.h b/Src/nde/osx/ColumnField.h
new file mode 100644
index 00000000..680afba6
--- /dev/null
+++ b/Src/nde/osx/ColumnField.h
@@ -0,0 +1,49 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ColumnField Class Prototypes
+Mac OS X 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, CFStringRef FieldName, unsigned char FieldType, Table *parentTable);
+ ColumnField();
+ ~ColumnField();
+
+ /* Field implementation */
+ 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);
+
+ /* ColumnField methods */
+ void InitField(void);
+ void SetDataType(unsigned char type);
+ bool IsSearchableField() const;
+ void SetSearchable(bool val);
+ unsigned char GetDataType(void);
+ CFStringRef GetFieldName(void);
+
+protected:
+ bool searchable;
+ CFStringRef Name;
+ unsigned char MyType;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/osx/FilenameField.cpp b/Src/nde/osx/FilenameField.cpp
new file mode 100644
index 00000000..7cb50f49
--- /dev/null
+++ b/Src/nde/osx/FilenameField.cpp
@@ -0,0 +1,45 @@
+#include "FilenameField.h"
+#include "nde.h"
+
+/*
+Mac OS X implementation of FilenameField
+ only the equals operator will be case-sensitive. substring search, ends, starts, etc. will be case-insensitive,
+ to make things like "filename ends .mp3" easier
+
+TODO: it'd be massive overhead, but it'd be more correct to check if the file system is actually case sensitive (for the path being searched)
+*/
+
+//---------------------------------------------------------------------------
+FilenameField::FilenameField(CFStringRef Str) : StringField(Str)
+{
+ Type = FIELD_FILENAME;
+}
+
+//---------------------------------------------------------------------------
+FilenameField::FilenameField()
+{
+ Type = FIELD_FILENAME;
+}
+
+//---------------------------------------------------------------------------
+int FilenameField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+
+ CFStringRef compareString = ((StringField*)Entry)->GetString();
+ if (!String && !compareString) return 0;
+ if (!String && compareString) return 1;
+ if (!compareString) return -1;
+
+ return CFStringCompare(String, compareString, 0);
+}
+
+Field *FilenameField::Clone(Table *pTable)
+{
+ FilenameField *clone = new FilenameField(String);
+ clone->Pos = FIELD_CLONE;
+ clone->ID = ID;
+ clone->MaxSizeOnDisk = GetDataSize();
+ return clone;
+}
diff --git a/Src/nde/osx/FilenameField.h b/Src/nde/osx/FilenameField.h
new file mode 100644
index 00000000..56553329
--- /dev/null
+++ b/Src/nde/osx/FilenameField.h
@@ -0,0 +1,23 @@
+#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 Field *Clone(Table *pTable);
+
+public:
+ FilenameField(CFStringRef Str);
+ FilenameField();
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/osx/IndexField.cpp b/Src/nde/osx/IndexField.cpp
new file mode 100644
index 00000000..77d39664
--- /dev/null
+++ b/Src/nde/osx/IndexField.cpp
@@ -0,0 +1,126 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+IndexField Class
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+
+//---------------------------------------------------------------------------
+IndexField::IndexField(unsigned char id, int Pos, int type, CFStringRef FieldName)
+{
+ InitField();
+ Type = FIELD_INDEX;
+ Name = (CFStringRef)CFRetain(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()
+{
+ if (Name)
+ CFRelease(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);
+ if (Name)
+ CFRelease(Name);
+ Name = CFStringCreateWithBytes(kCFAllocatorDefault, data+pos, c, kCFStringEncodingUTF8, false);
+ }
+}
+
+//---------------------------------------------------------------------------
+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);
+
+ if (Name)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, NULL, 0, &lengthRequired);
+ CHECK_BIN(len, lengthRequired+1);
+ PUT_CHAR(lengthRequired); pos++;
+
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, data+pos, lengthRequired, 0);
+ }
+}
+
+//---------------------------------------------------------------------------
+CFStringRef IndexField::GetIndexName(void)
+{
+ return Name;
+}
+
+//---------------------------------------------------------------------------
+size_t IndexField::GetDataSize(void)
+{
+ if (Name)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(Name, CFRangeMake(0, CFStringGetLength(Name)), kCFStringEncodingUTF8, 0, false, NULL, 0, &lengthRequired);
+ return lengthRequired+9;
+ }
+ else
+ return 9;
+}
+
+//---------------------------------------------------------------------------
+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/osx/IndexField.h b/Src/nde/osx/IndexField.h
new file mode 100644
index 00000000..faf6c7cb
--- /dev/null
+++ b/Src/nde/osx/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, CFStringRef 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);
+
+ CFStringRef GetIndexName(void);
+ int TranslateToIndex(int Id, IndexField *index);
+ Index *index; // TODO: make protected
+protected:
+ int Position;
+ int DataType;
+ CFStringRef Name;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/osx/IndexRecord.cpp b/Src/nde/osx/IndexRecord.cpp
new file mode 100644
index 00000000..0889c622
--- /dev/null
+++ b/Src/nde/osx/IndexRecord.cpp
@@ -0,0 +1,170 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+IndexRecord Class
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include <stdio.h>
+
+IndexRecord::IndexRecord(int RecordPos, int insertionPoint, VFILE *TableHandle, Table *ParentTable)
+{
+ InsertionPoint = insertionPoint;
+ if (RecordPos != 0)
+ {
+ int n=0;
+ uint32_t ThisPos = RecordPos;
+ while (ThisPos)
+ {
+ if (n >= 128)
+ break;
+ Vfseek(TableHandle, ThisPos, SEEK_SET);
+ Field Entry (ThisPos);
+ Field *TypedEntry = Entry.ReadField(ParentTable, ThisPos, &ThisPos);
+
+ if (!TypedEntry) break; // some db error?
+
+ AddField(TypedEntry);
+ // ThisPos = TypedEntry->GetNextFieldPos();
+ n++;
+ }
+ }
+}
+
+
+void IndexRecord::BuildCollaboration()
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ if (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;
+}
+
+#ifdef _WIN32
+IndexField *IndexRecord::GetIndexByName(const wchar_t *name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ if (!_wcsicmp(p->GetIndexName(), name))
+ return p;
+ }
+ return NULL;
+}
+#else
+IndexField *IndexRecord::GetIndexByName(CFStringRef name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ if (CFStringCompare(p->GetIndexName(), name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ return p;
+ }
+ return NULL;
+}
+#endif
+
+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;
+}
+
+
+void IndexRecord::SetModified(BOOL modifiedVal)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ p->index->Modified = modifiedVal;
+ }
+}
+
+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/osx/IndexRecord.h b/Src/nde/osx/IndexRecord.h
new file mode 100644
index 00000000..f55b8f3a
--- /dev/null
+++ b/Src/nde/osx/IndexRecord.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <stdio.h>
+#include "Record.h"
+#include "../nu/PtrDeque2.h"
+class IndexRecord : public RecordBase
+{
+public:
+ IndexRecord(int RecordPos, int insertionPoint, VFILE *FileHandle,Table *p);
+ void BuildCollaboration();
+ bool NeedFix();
+ IndexField *GetIndexByName(const wchar_t *name);
+ int GetColumnCount() { return 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/osx/IntegerField.cpp b/Src/nde/osx/IntegerField.cpp
new file mode 100644
index 00000000..5fd203cc
--- /dev/null
+++ b/Src/nde/osx/IntegerField.cpp
@@ -0,0 +1,1018 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ IntegerField Class
+
+ --------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "Query.h"
+#include <time.h>
+#ifdef _WIN32
+#include <malloc.h> // for alloca
+#endif
+//---------------------------------------------------------------------------
+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 {
+ CFStringRef 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...
+ {CFSTR("ago"), TOKEN_AGO},
+ {CFSTR("now"), TOKEN_NOW},
+ {CFSTR("am"), TOKEN_AM},
+ {CFSTR("pm"), TOKEN_PM},
+ {CFSTR("this"), TOKEN_THIS},
+ {CFSTR("date"), TOKEN_DATE},
+ {CFSTR("time"), TOKEN_TIME},
+ {CFSTR("of"), TOKEN_OF},
+ {CFSTR("at"), TOKEN_AT},
+ {CFSTR("the"), TOKEN_THE},
+ {CFSTR("yesterday"), TOKEN_YESTERDAY},
+ {CFSTR("tomorrow"), TOKEN_TOMORROW},
+ {CFSTR("today"), TOKEN_TODAY},
+ {CFSTR("from"), TOKEN_FROM},
+ {CFSTR("before"), TOKEN_BEFORE},
+ {CFSTR("after"), TOKEN_AFTER},
+ {CFSTR("past"), TOKEN_AFTER},
+ {CFSTR("monday"), TOKEN_MONDAY},
+ {CFSTR("mon"), TOKEN_MONDAY},
+ {CFSTR("tuesday"), TOKEN_TUESDAY},
+ {CFSTR("tue"), TOKEN_TUESDAY},
+ {CFSTR("wednesday"), TOKEN_WEDNESDAY},
+ {CFSTR("wed"), TOKEN_WEDNESDAY},
+ {CFSTR("thursday"), TOKEN_THURSDAY},
+ {CFSTR("thu"), TOKEN_THURSDAY},
+ {CFSTR("friday"), TOKEN_FRIDAY},
+ {CFSTR("fri"), TOKEN_FRIDAY},
+ {CFSTR("saturday"), TOKEN_SATURDAY},
+ {CFSTR("sat"), TOKEN_SATURDAY},
+ {CFSTR("sunday"), TOKEN_SUNDAY},
+ {CFSTR("sun"), TOKEN_SUNDAY},
+ {CFSTR("midnight"), TOKEN_MIDNIGHT},
+ {CFSTR("noon"), TOKEN_NOON},
+ {CFSTR("second"), TOKEN_SECOND},
+ {CFSTR("seconds"), TOKEN_SECOND},
+ {CFSTR("sec"), TOKEN_SECOND},
+ {CFSTR("s"), TOKEN_SECOND},
+ {CFSTR("minute"), TOKEN_MINUTE},
+ {CFSTR("minutes"), TOKEN_MINUTE},
+ {CFSTR("min"), TOKEN_MINUTE},
+ {CFSTR("mn"), TOKEN_MINUTE},
+ {CFSTR("m"), TOKEN_MINUTE},
+ {CFSTR("hour"), TOKEN_HOUR},
+ {CFSTR("hours"), TOKEN_HOUR},
+ {CFSTR("h"), TOKEN_HOUR},
+ {CFSTR("day"), TOKEN_DAY},
+ {CFSTR("days"), TOKEN_DAY},
+ {CFSTR("d"), TOKEN_DAY},
+ {CFSTR("week"), TOKEN_WEEK},
+ {CFSTR("weeks"), TOKEN_WEEK},
+ {CFSTR("w"), TOKEN_WEEK},
+ {CFSTR("month"), TOKEN_MONTH},
+ {CFSTR("months"), TOKEN_MONTH},
+ {CFSTR("year"), TOKEN_YEAR},
+ {CFSTR("years"), TOKEN_YEAR},
+ {CFSTR("y"), TOKEN_YEAR},
+ {CFSTR("january"), TOKEN_JANUARY},
+ {CFSTR("jan"), TOKEN_JANUARY},
+ {CFSTR("february"), TOKEN_FEBRUARY},
+ {CFSTR("feb"), TOKEN_FEBRUARY},
+ {CFSTR("march"), TOKEN_MARCH},
+ {CFSTR("mar"), TOKEN_MARCH},
+ {CFSTR("april"), TOKEN_APRIL},
+ {CFSTR("apr"), TOKEN_APRIL},
+ {CFSTR("may"), TOKEN_MAY},
+ {CFSTR("june"), TOKEN_JUNE},
+ {CFSTR("jun"), TOKEN_JUNE},
+ {CFSTR("july"), TOKEN_JULY},
+ {CFSTR("jul"), TOKEN_JULY},
+ {CFSTR("august"), TOKEN_AUGUST},
+ {CFSTR("aug"), TOKEN_AUGUST},
+ {CFSTR("september"), TOKEN_SEPTEMBER},
+ {CFSTR("sep"), TOKEN_SEPTEMBER},
+ {CFSTR("october"), TOKEN_OCTOBER},
+ {CFSTR("oct"), TOKEN_OCTOBER},
+ {CFSTR("november"), TOKEN_NOVEMBER},
+ {CFSTR("nov"), TOKEN_NOVEMBER},
+ {CFSTR("december"), TOKEN_DECEMBER},
+ {CFSTR("dec"), TOKEN_DECEMBER},
+};
+
+//---------------------------------------------------------------------------
+int IntegerField::LookupToken(CFStringRef t) {
+ for (int i=0;i<sizeof(Int_Tokens)/sizeof(tokenstruct);i++) {
+ if (CFStringCompare(Int_Tokens[i].token, t, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ return Int_Tokens[i].tid;
+ }
+ return TOKEN_IDENTIFIER;
+}
+
+static int isallnum(CFStringRef p)
+{
+ // TODO: ideally we should cache this or create it during initialization (but need to b be careful about thread safety)
+ CFCharacterSetRef set = CFCharacterSetCreateInvertedSet(NULL, CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit));
+
+ CFRange range;
+ Boolean ret = CFStringFindCharacterFromSet(p, set, CFRangeMake(0, CFStringGetLength(p)), 0, &range);
+ CFRelease(set);
+ return ret;
+}
+
+static bool Ends(CFStringRef str1, CFStringRef str2)
+{
+
+ CFRange findRange = CFStringFind(str1, str2, kCFCompareCaseInsensitive|kCFCompareAnchored|kCFCompareBackwards);
+ if (findRange.location == kCFNotFound)
+ return false;
+ else
+ return findRange.location != 0;
+}
+
+//---------------------------------------------------------------------------
+int IntegerField::ApplyConversion(const char *format, TimeParse *tp) {
+ size_t size;
+
+ int value = GetValue();
+ CFStringRef token = NULL;
+ 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 = CFStringGetIntValue(token);
+ 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.
+ if (Ends(token, CFSTR("st")) | Ends(token, CFSTR("nd")) | Ends(token, CFSTR("rd")) | Ends(token, CFSTR("th")))
+ {
+ int j = CFStringGetIntValue(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 (##:##:##)
+#ifdef _WIN32
+ z = wcschr(token, L':');
+ if (z)
+ {
+ if (tp) tp->absolute_hastime = 1;
+ wchar_t *zz = wcschr(z+1, L':');
+ 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 = wcstol(zz+1,0,10); }
+ else b = wcstol(z+1,0,10);
+ origin.tm_hour = a;
+ origin.tm_min = b;
+ if (tp) {
+ tp->relative_hour = a;
+ tp->relative_min = b;
+ }
+ if (zz && !origin_flags.tm_sec) {
+ origin.tm_sec = c;
+ if (tp) tp->relative_sec = c;
+ } else if (!origin_flags.tm_sec) {
+ origin.tm_sec = 0;
+ }
+ origin_flags.tm_sec = 1;
+ origin_flags.tm_hour = 1;
+ origin_flags.tm_min = 1;
+ break;
+ }
+#else
+ // TODO!!! maybe CFDateFormatterGetAbsoluteTimeFromString ?
+#endif
+
+ // check for a date string in the format ##/##/##
+#ifdef _WIN32
+ z = wcschr(token, L'/');
+ if (z) {
+ if (tp) tp->absolute_hasdate = 1;
+ wchar_t *zz = wcschr(z+1, L'/');
+ 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 = wcstol(zz+1,0,10); }
+ else b = _wtoi(z+1);
+ if (b > 1969 && b < 2038) {
+ // mm/yyyy
+ origin.tm_year = b-1900;
+ origin_flags.tm_year = 1;
+ origin.tm_mon = a-1;
+ origin_flags.tm_mon = 1;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) {
+ tp->relative_year = b;
+ tp->relative_month = a-1;
+ }
+ } else {
+ // mm/dd(/yy[yy])
+ if (c < 70) c += 100;
+ if (c > 138) c -= 1900;
+ origin.tm_year = c;
+ origin.tm_mon = a-1;
+ origin.tm_mday = b == 0 ? 1 : b;
+ origin_flags.tm_year = 1;
+ origin_flags.tm_mon = 1;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) {
+ tp->relative_year = c+1900;
+ tp->relative_month = a-1;
+ tp->relative_day = b;
+ }
+ }
+ origin_flags.tm_year = 1;
+ origin_flags.tm_mon = 1;
+ origin_flags.tm_mday = 1;
+ break;
+ }
+#else
+ // TODO!!! maybe CFDateFormatterCreateDateFromString ?
+#endif
+
+ 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);
+ if (token) CFRelease(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);
+
+ if (token) CFRelease(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);
+
+ if (token) CFRelease(token);
+
+ if (tp) tp->absolute_datetime = GetValue();
+ return 1;
+ }
+
+ if (token) CFRelease(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/osx/IntegerField.h b/Src/nde/osx/IntegerField.h
new file mode 100644
index 00000000..9c8dac87
--- /dev/null
+++ b/Src/nde/osx/IntegerField.h
@@ -0,0 +1,93 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ IntegerField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __INTEGERFIELD_H
+#define __INTEGERFIELD_H
+
+#include <bfc/platform/types.h>
+
+class TimeParse {
+public:
+ int is_relative; // ago/after/before used
+ int absolute_datetime; // if not is_relative, this is the date/time, otherwise it's the resulting date/time if the query was ran now
+ int absolute_hasdate; // if not, use only the time portion of absolute_datetime, the rest is now
+ int absolute_hastime; // if not, use only the date portion of absolute_datetime, the rest is now
+ int relative_year; // -1 = this year
+ int relative_month; // -1 = this month
+ int relative_day; // -1 = this day, -2 = this week
+ int relative_hour; // -1 = this hour
+ int relative_min; // -1 = this minute
+ int relative_sec; // -1 = this second
+ int relative_kwday; // not used(-1), 0 to 6 for sunday to saturday, yesterday(7), today(8), tomorrow(9)
+ int offset_value; // timeago/offsetby numerical edit field
+ int offset_what; // not used(-1) years(0), months(1), weeks(2), days(3), hours(4), minutes(5), seconds(6)
+ int offset_whence; // not used(-1), after(0), ago/before(1)
+ int offset_used; // if 1, 'time ago' / 'offset by' should be checked
+};
+
+class IntegerField : public Field
+ {
+ protected:
+
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ virtual bool ApplyFilter(Field *Data, int op);
+ void InitField(void);
+ int Value;
+
+ enum {
+ WHAT_YEARS,
+ WHAT_MONTHS,
+ WHAT_WEEKS,
+ WHAT_DAYS,
+ WHAT_HOURS,
+ WHAT_MINUTES,
+ WHAT_SECONDS,
+ };
+
+ enum {
+ FROM_BARE,
+ FROM_AGO,
+ FROM_SINCE,
+ };
+
+ public:
+
+ ~IntegerField();
+ IntegerField(int);
+ IntegerField();
+ int GetValue(void);
+ void SetValue(int);
+ int ApplyConversion(const char *format, TimeParse *tp=NULL);
+
+ static int LookupToken(CFStringRef 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/osx/Query.cpp b/Src/nde/osx/Query.cpp
new file mode 100644
index 00000000..724485b6
--- /dev/null
+++ b/Src/nde/osx/Query.cpp
@@ -0,0 +1,936 @@
+#include "nde.h"
+#include "NDEString.h"
+#include "Query.h"
+
+//---------------------------------------------------------------------------
+
+BOOL Scanner::Query(const char *query)
+{
+ if (!query) return FALSE;
+ if (last_query) CFRelease(last_query);
+ last_query = CFStringCreateWithBytes(NULL, (const uint8_t *)query, strlen(query), kCFStringEncodingUTF32, false);
+ 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();
+}
+
+CFStringRef Scanner::GetLastQuery()
+{
+ return last_query;
+}
+
+typedef struct
+{
+ CFStringRef token;
+ int tid;
+} tokenstruct;
+
+tokenstruct Tokens[] = // Feel free to add more...
+{
+ {CFSTR("AND"), TOKEN_AND },
+ {CFSTR("OR"), TOKEN_OR },
+ {CFSTR("HAS"), TOKEN_CONTAINS },
+ {CFSTR("NOTHAS"),TOKEN_NOTCONTAINS},
+ {CFSTR("BEGINS"), TOKEN_BEGINS },
+ {CFSTR("ENDS"), TOKEN_ENDS },
+ {CFSTR("ISEMPTY"), TOKEN_ISEMPTY},
+ {CFSTR("ISNOTEMPTY"),TOKEN_ISNOTEMPTY},
+ {CFSTR("LIKE"), TOKEN_LIKE},
+ {CFSTR("BEGINSLIKE"), TOKEN_BEGINSLIKE},
+};
+
+
+typedef struct
+{
+ int Op;
+ int Level;
+} OpLevel;
+
+
+static int Query_ParseLength(CFStringRef str)
+{
+ int x;
+ CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, str, CFSTR(":"));
+ if (array)
+ {
+ CFIndex count = CFArrayGetCount(array);
+ if (count == 2)
+ {
+ CFStringRef t1 = (CFStringRef)CFArrayGetValueAtIndex(array, 0);
+ CFStringRef t2 = (CFStringRef)CFArrayGetValueAtIndex(array, 1);
+ x = CFStringGetIntValue(t1) * 60 + CFStringGetIntValue(t2);
+ CFRelease(array);
+ return x;
+ }
+
+ if (count == 3)
+ {
+ CFStringRef t1 = (CFStringRef)CFArrayGetValueAtIndex(array, 0);
+ CFStringRef t2 = (CFStringRef)CFArrayGetValueAtIndex(array, 1);
+ CFStringRef t3 = (CFStringRef)CFArrayGetValueAtIndex(array, 2);
+ x = CFStringGetIntValue(t1) * 60 * 60 + CFStringGetIntValue(t2) * 60 + CFStringGetIntValue(t3);
+ CFRelease(array);
+ return x;
+ }
+ CFRelease(array);
+ }
+ return CFStringGetIntValue(str);;
+}
+
+/*
+ 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
+ size_t 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++;
+
+ CFStringRef format = CFStringCreateWithBytes(NULL, (const uint8_t *)p, (s-p), kCFStringEncodingUTF32, false);
+ 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(CFSTR("")));
+ 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);
+ if (format) CFRelease(format);
+ p = s+1;
+ continue;
+ }
+
+
+ if (format) CFRelease(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();
+ }
+
+ // TODO: make sure this is safe (that p and s havn't been iterated)
+ char *temp_format = (char *)malloc((s-p+1));
+ strncpy(temp_format, p, s-p);
+ temp_format[s-p] = 0;
+ int r = field->ApplyConversion(temp_format);
+ free(temp_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 = CFStringGetIntValue(token);
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_INT64:
+ {
+ int64_t i;
+ Int64Field *i_f = new Int64Field();
+ #ifdef _WIN32
+ i = _wtoi64(token); // todo: Replace with own conversion and error checking
+ #else
+ i = CFStringGetIntValue(token); // TODO!!! 64bit integer here ... maybe use CFNumberFormatterCreateNumberFromString but it's a lot of overhead
+ #endif
+ 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++;
+ CFStringRef format = CFStringCreateWithBytes(NULL, (const uint8_t *)p, (s-p), kCFStringEncodingUTF32, false);
+ 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(CFSTR("")));
+ 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);
+ if (format) CFRelease(format);
+ p = s+1;
+ continue;
+ }
+ if (format) CFRelease(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();
+ }
+
+ // TODO: make sure this is safe (that p and s havn't been iterated)
+ char *temp_format = (char *)malloc((s-p+1));
+ strncpy(temp_format, p, s-p);
+ temp_format[s-p] = 0;
+ int r = field->ApplyConversion(temp_format);
+ free(temp_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(CFStringRef t)
+{
+ for (int i=0;i<sizeof(Tokens)/sizeof(tokenstruct);i++)
+ {
+ if (CFStringCompare(Tokens[i].token, t, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ return Tokens[i].tid;
+ }
+ return TOKEN_IDENTIFIER;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Query_GetNextToken(const char *p, size_t *size, CFStringRef *_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
+ {
+ if (*_token) CFRelease(*_token);
+
+ // check for a quoted string
+ if (*p == '\"' && e != p+1 && e[-1] == '\"')
+ {
+ CFStringRef inner_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)(p+1), (e-p-2), kCFStringEncodingUTF8, false);
+
+ // escape it (ugh)
+ CFStringRef escaped_string = CFURLCreateStringByReplacingPercentEscapes(NULL, inner_string, CFSTR(""));
+ if (escaped_string)
+ {
+ *_token = escaped_string;
+ CFRelease(inner_string);
+ }
+ else
+ {
+ *_token = inner_string;
+ }
+ }
+ else
+ {
+ *_token = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)p, (e-p), kCFStringEncodingUTF8, false);
+ }
+
+ 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;
+}
+
diff --git a/Src/nde/osx/Query.h b/Src/nde/osx/Query.h
new file mode 100644
index 00000000..748bf36a
--- /dev/null
+++ b/Src/nde/osx/Query.h
@@ -0,0 +1,27 @@
+#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)
diff --git a/Src/nde/osx/Record.cpp b/Src/nde/osx/Record.cpp
new file mode 100644
index 00000000..7d6aae23
--- /dev/null
+++ b/Src/nde/osx/Record.cpp
@@ -0,0 +1,138 @@
+/* ---------------------------------------------------------------------------
+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;
+ int n=0;
+ if (RecordPos != 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();
+}
+
+
+#ifdef _WIN32
+ColumnField *Record::GetColumnByName(const wchar_t *name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ ColumnField *p = (ColumnField *)*itr;
+ if (!_wcsicmp(p->GetFieldName(), name))
+ return p;
+ }
+ return NULL;
+}
+#elif defined(__APPLE__)
+ColumnField *Record::GetColumnByName(CFStringRef name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ ColumnField *p = (ColumnField *)*itr;
+ if (CFStringCompare(p->GetFieldName(), name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ return p;
+ }
+ return NULL;
+}
+#endif
+
+
+//---------------------------------------------------------------------------
+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/osx/Record.h b/Src/nde/osx/Record.h
new file mode 100644
index 00000000..bb42d17d
--- /dev/null
+++ b/Src/nde/osx/Record.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "field.h"
+#include "Vfs.h"
+#include <stdio.h>
+#include "../nu/PtrMap.h"
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+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; }
+
+#ifdef __APPLE__
+ ColumnField *GetColumnByName(CFStringRef name);
+#else
+ ColumnField *GetColumnByName(const wchar_t *name);
+#endif
+ 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);
+ NDE_API void WalkFields(FieldsWalker callback, void *context);
+};
+
diff --git a/Src/nde/osx/Scanner.cpp b/Src/nde/osx/Scanner.cpp
new file mode 100644
index 00000000..cc4a7e4e
--- /dev/null
+++ b/Src/nde/osx/Scanner.cpp
@@ -0,0 +1,1283 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Scanner Class
+
+--------------------------------------------------------------------------- */
+
+#include "nde.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "BinaryField.h"
+#include "Binary32Field.h"
+#include <assert.h>
+//---------------------------------------------------------------------------
+Scanner::Scanner(Table *parentTable)
+{
+ disable_date_resolution=0;
+ index = NULL;
+ pTable = parentTable;
+ Edition=FALSE;
+
+ lastLocateIndex = NULL;
+ lastLocateId = -1;
+ lastLocateIdx = -1;
+ lastLocateFrom = -128;
+ lastLocateFieldClone = NULL;
+ CurrentRecord=NULL;
+ CurrentRecordIdx=0;
+ iModified = FALSE;
+ FiltersOK = FALSE;
+
+ search_any = FALSE;
+ ResultPtr = 0;
+
+ /*inMatchJoins = 0;
+ lastJoinCache = 0;
+ invalidJoinCache = 1;*/
+ last_query = NULL;
+ last_query_failed = FALSE;
+ token = NULL;
+ in_query_parser = 0;
+}
+
+//---------------------------------------------------------------------------
+Scanner::~Scanner()
+{
+ if (CurrentRecord) CurrentRecord->Release();
+
+ if (lastLocateFieldClone)
+ {
+ delete lastLocateFieldClone;
+ }
+
+ Query_CleanUp();
+
+ if (token) CFRelease(token);
+ if (last_query) CFRelease(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;
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+Field *Scanner::GetFieldByName(const wchar_t *FieldName)
+{
+ ColumnField *header = pTable->GetColumnByName(FieldName);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ return GetFieldById(Idx);
+ }
+ return NULL;
+}
+#else
+Field *Scanner::GetFieldByName(CFStringRef FieldName)
+{
+ ColumnField *header = pTable->GetColumnByName(FieldName);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ return GetFieldById(Idx);
+ }
+ return NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------
+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;
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+Field *Scanner::NewFieldByName(const wchar_t *fieldName, unsigned char Perm)
+{
+ ColumnField *header = pTable->GetColumnByName(fieldName);
+ if (header)
+ {
+ unsigned char Id = header->ID;
+ Field *field = NewFieldById(Id, Perm);
+ return field;
+ }
+ return NULL;
+}
+#else
+Field *Scanner::NewFieldByName(CFStringRef 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;
+}
+#endif
+
+//---------------------------------------------------------------------------
+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:
+#ifdef WIN32
+ MessageBox(NULL, "unknown field type for id", "debug", 0);
+#else
+ printf("NDE Error: unknown field type for id\n");
+#endif
+ 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();
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+BOOL Scanner::LocateByName(const wchar_t *col, int From, Field *field, int *nskip)
+{
+ ColumnField *f = pTable->GetColumnByName(col);
+ if (!f)
+ return NULL;
+ return LocateById(f->GetFieldId(), From, field, nskip);
+}
+#else
+BOOL Scanner::LocateByName(CFStringRef col, int From, Field *field, int *nskip)
+{
+ ColumnField *f = pTable->GetColumnByName(col);
+ if (!f)
+ return NULL;
+ return LocateById(f->GetFieldId(), From, field, nskip);
+}
+#endif
+
+//---------------------------------------------------------------------------
+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;
+}
+
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+void Scanner::DeleteFieldByName(const wchar_t *name)
+{
+ ColumnField *header = pTable->GetColumnByName(name);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ DeleteFieldById(Idx);
+ }
+ return;
+}
+#else
+void Scanner::DeleteFieldByName(CFStringRef name)
+{
+ ColumnField *header = pTable->GetColumnByName(name);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ DeleteFieldById(Idx);
+ }
+ return;
+}
+#endif
+
+//---------------------------------------------------------------------------
+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);
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+BOOL Scanner::SetWorkingIndexByName(const wchar_t *desc)
+{
+ IndexField *indx = pTable->GetIndexByName(desc);
+ if (indx)
+ return SetWorkingIndexById(indx->ID);
+ else
+ return SetWorkingIndexById(-1);
+}
+#else
+BOOL Scanner::SetWorkingIndexByName(CFStringRef desc)
+{
+ IndexField *indx = pTable->GetIndexByName(desc);
+ if (indx)
+ return SetWorkingIndexById(indx->ID);
+ else
+ return SetWorkingIndexById(-1);
+}
+#endif
+
+//---------------------------------------------------------------------------
+void Scanner::IndexModified(void)
+{
+ iModified = TRUE;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::ClearDirtyBit(void)
+{
+ iModified = FALSE;
+}
+
+//---------------------------------------------------------------------------
+Table *Scanner::GetTable(void)
+{
+ return pTable;
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+ColumnField *Scanner::GetColumnByName(const wchar_t *FieldName)
+{
+ return pTable->GetColumnByName(FieldName);
+}
+#else
+ColumnField *Scanner::GetColumnByName(CFStringRef FieldName)
+{
+ return pTable->GetColumnByName(FieldName);
+}
+
+#endif
+
+//---------------------------------------------------------------------------
+ColumnField *Scanner::GetColumnById(unsigned char Idx)
+{
+ return pTable->GetColumnById(Idx);
+}
+
+//---------------------------------------------------------------------------
+#ifdef _WIN32
+int Scanner::AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op)
+{
+ ColumnField *f = pTable->GetColumnByName(name);
+ if (f)
+ return AddFilterById(f->GetFieldId(), Data, Op);
+ return ADDFILTER_FAILED;
+}
+#else
+int Scanner::AddFilterByName(CFStringRef name, Field *Data, unsigned char Op)
+{
+ ColumnField *f = pTable->GetColumnByName(name);
+ if (f)
+ return AddFilterById(f->GetFieldId(), Data, Op);
+ return ADDFILTER_FAILED;
+}
+#endif
+
+//---------------------------------------------------------------------------
+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;
+ }
+ }
+}
+
+#ifdef _WIN32
+void Scanner::Search(const wchar_t *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 == L'*' && search_string[1] == L' ')
+ {
+ 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 wchar_t *end=search_string;
+
+ wchar_t c = *search_string;
+ if (c == L'\"') // 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
+ {
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ }
+ else if (end > (search_string+1)) // at least one character in the quotes
+ {
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ end++;
+ }
+ }
+ search_string=end;
+ }
+ else if (c)
+ {
+ while (*end && *end != ' ' && *end != '\t')
+ end++;
+
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ }
+ }
+ }
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/osx/Scanner.h b/Src/nde/osx/Scanner.h
new file mode 100644
index 00000000..3f6ff3c0
--- /dev/null
+++ b/Src/nde/osx/Scanner.h
@@ -0,0 +1,210 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Scanner Class Prototypes
+
+ --------------------------------------------------------------------------- */
+
+#ifndef __SCANNER_H
+#define __SCANNER_H
+
+#include "record.h"
+#include "Table.h"
+#include "index.h"
+#include "../nu/Vector.h"
+#include "../nu/ValueSet.h"
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+class Table;
+class Index;
+#pragma warning( disable: 4251 )
+class Scanner;
+class Record;
+class Scanner : public LinkedListEntry
+{
+public:
+ Record *GetRecord(int Idx);
+ Scanner(Table *parentTable);
+ void IndexModified(void);
+ Index *GetIndex() { return index; }
+ Index *index; // TODO: make protected
+
+protected:
+ ~Scanner();
+
+ Table *pTable;
+ BOOL iModified;
+ typedef 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);
+ int CheckFilters(void);
+ void CacheLastLocate(int Id, int From, Field *field, Index *i, int j);
+
+#ifdef _WIN32
+ static int Query_LookupToken(const wchar_t *token);
+#else
+ static int Query_LookupToken(CFStringRef token);
+#endif
+ void Query_CleanUp(void);
+ void Query_SyntaxError(int c);
+public:
+#ifdef _WIN32
+ static int Query_GetNextToken(const wchar_t *p, int *size, wchar_t **token, int tokentable=0);
+ static const wchar_t *Query_EatSpace(const wchar_t *p);
+ static wchar_t *Query_ProbeSpace(wchar_t *p);
+ static const wchar_t *Query_ProbeNonAlphaNum(const wchar_t *p);
+ static wchar_t *Query_ProbeAlphaNum(wchar_t *p);
+ static int Query_isControlChar(wchar_t p);
+
+ BOOL Query(const wchar_t *query);
+ BOOL Query_Parse(const wchar_t *query);
+#else
+ static int Query_GetNextToken(const char *p, size_t *size, CFStringRef *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);
+#endif
+
+#ifdef _WIN32
+ const wchar_t *GetLastQuery();
+#elif defined(__APPLE__)
+ CFStringRef GetLastQuery();
+#endif
+
+public://fucko: protected
+ //String token;
+ LinkedList pstack;
+#ifdef _WIN32
+ wchar_t *token;
+#else
+ CFStringRef token;
+#endif
+
+#ifdef _WIN32
+ wchar_t *last_query;
+#elif defined(__APPLE__)
+ CFStringRef last_query;
+#endif
+ 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);
+
+#ifdef _WIN32
+ ColumnField *GetColumnByName(const wchar_t *FieldName);
+#else
+ ColumnField *GetColumnByName(CFStringRef FieldName);
+#endif
+ ColumnField *GetColumnById(unsigned char id);
+
+#ifdef _WIN32
+ Field *NewFieldByName(const wchar_t *fieldName, unsigned char Perm);
+#else
+ Field *NewFieldByName(CFStringRef fieldName, unsigned char Perm);
+#endif
+ Field *NewFieldById(unsigned char Id, unsigned char Perm);
+ void DeleteField(Field *field);
+#ifdef _WIN32
+ void DeleteFieldByName(const wchar_t *name);
+#else
+ void DeleteFieldByName(CFStringRef name);
+#endif
+ void DeleteFieldById(unsigned char Id);
+
+ void Cancel(void);
+ void Insert(void);
+ void Edit(void);
+ void Post(void);
+ void Delete(void);
+
+#ifdef _WIN32
+ Field *GetFieldByName(const wchar_t *FieldName);
+#else
+ Field *GetFieldByName(CFStringRef FieldName);
+#endif
+ 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);
+#ifdef _WIN32
+ BOOL LocateByName(const wchar_t *column, int From, Field *field, int *nskip=NULL);
+#else
+ BOOL LocateByName(CFStringRef column, int From, Field *field, int *nskip=NULL);
+#endif
+ 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
+#ifdef _WIN32
+ int AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op);
+#else
+ int AddFilterByName(CFStringRef name, Field *Data, unsigned char Op);
+#endif
+ int AddFilterById(unsigned char Id, Field *Data, unsigned char Op);
+ int AddFilterOp(unsigned char Op);
+ void RemoveFilters(void);
+ Filter *GetLastFilter(void);
+
+#ifdef _WIN32
+ BOOL SetWorkingIndexByName(const wchar_t *desc);
+#else
+ BOOL SetWorkingIndexByName(CFStringRef desc);
+#endif
+ BOOL SetWorkingIndexById(unsigned char Id);
+
+#ifdef _WIN32
+ void Search(const wchar_t *search_string);
+#endif
+ 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/osx/StringField.cpp b/Src/nde/osx/StringField.cpp
new file mode 100644
index 00000000..91c1c4c4
--- /dev/null
+++ b/Src/nde/osx/StringField.cpp
@@ -0,0 +1,297 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+StringField Class
+
+Mac OS X (CFStringRef) implementation
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "StringField.h"
+
+//---------------------------------------------------------------------------
+StringField::StringField(CFStringRef Str)
+{
+ InitField();
+ Type = FIELD_STRING;
+ if (Str)
+ {
+ SetNDEString(Str);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+void StringField::InitField(void)
+{
+ Type = FIELD_STRING;
+ String=0;
+}
+
+//---------------------------------------------------------------------------
+StringField::StringField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+StringField::~StringField()
+{
+ if (String)
+ CFRelease(String);
+}
+
+
+//---------------------------------------------------------------------------
+void StringField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ unsigned short c;
+
+ CHECK_SHORT(len);
+ c = (unsigned short)(data[0]|(data[1]<<8));
+ data+=2;
+ if (c)
+ {
+ bool unicode=false;
+ if (c >= 2 // enough room for BOM
+ && (c % 2) == 0) // can't be unicode if it's not an even multiple of 2
+ {
+ wchar_t BOM=0;
+ memcpy(&BOM, data, 2);
+ if (BOM == 0xFEFF || BOM == 0xFFFE)
+ {
+ unicode=true;
+ }
+ }
+
+ CHECK_BIN(len, c);
+ if (unicode)
+ {
+ if (String)
+ CFRelease(String);
+ String = CFStringCreateWithBytes(kCFAllocatorDefault, data, c, kCFStringEncodingUTF16, true);
+ }
+ else
+ {
+ if (String)
+ CFRelease(String);
+ String = CFStringCreateWithBytes(kCFAllocatorDefault, data, c, kCFStringEncodingWindowsLatin1, false);
+ }
+ }
+}
+
+//---------------------------------------------------------------------------
+void StringField::WriteTypedData(uint8_t *data, size_t len)
+{
+ int pos=0;
+
+ CHECK_SHORT(len);
+ if (String)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(String, CFRangeMake(0, CFStringGetLength(String)), kCFStringEncodingUTF16, 0, true, NULL, 0, &lengthRequired);
+ CHECK_BIN(len, lengthRequired+2);
+ PUT_SHORT(lengthRequired); pos+=2;
+
+ CFStringGetBytes(String, CFRangeMake(0, CFStringGetLength(String)), kCFStringEncodingUTF16, 0, true, data+pos, lengthRequired, 0);
+ }
+ else
+ {
+ PUT_SHORT(0);
+ }
+}
+
+CFStringRef StringField::GetString()
+{
+ return String;
+}
+
+void StringField::SetNDEString(CFStringRef Str)
+{
+ if (!Str)
+ return;
+
+ CFStringRef old = String;
+ String = (CFStringRef)CFRetain(Str);
+ CFRelease(old);
+}
+
+//---------------------------------------------------------------------------
+size_t StringField::GetDataSize(void)
+{
+ if (String)
+ {
+ CFIndex lengthRequired=0;
+ CFStringGetBytes(String, CFRangeMake(0, CFStringGetLength(String)), kCFStringEncodingUTF16, 0, true, NULL, 0, &lengthRequired);
+ return lengthRequired+2;
+ }
+ else
+ return 2;
+}
+
+//---------------------------------------------------------------------------
+int StringField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+
+ CFStringRef compareString = ((StringField*)Entry)->GetString();
+ if (!String && !compareString) return 0;
+ if (!String && compareString) return 1;
+ if (!compareString) return -1;
+
+ return CFStringCompare(String, compareString, kCFCompareCaseInsensitive|kCFCompareNonliteral|kCFCompareDiacriticInsensitive);
+}
+
+//---------------------------------------------------------------------------
+int StringField::Starts(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+
+ CFStringRef compareString = ((StringField*)Entry)->GetString();
+ if (!String || !compareString) return 0;
+
+ CFRange findRange = CFStringFind(String, compareString, kCFCompareCaseInsensitive|kCFCompareNonliteral|kCFCompareAnchored|kCFCompareDiacriticInsensitive);
+ if (findRange.location == kCFNotFound)
+ return 0;
+ return findRange.location == 0;
+}
+
+//---------------------------------------------------------------------------
+int StringField::Contains(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+
+ CFStringRef compareString = ((StringField*)Entry)->GetString();
+ if (!String || !compareString) return 0;
+
+ CFRange findRange = CFStringFind(String, compareString, kCFCompareCaseInsensitive|kCFCompareNonliteral|kCFCompareDiacriticInsensitive);
+ return findRange.location != kCFNotFound;
+
+}
+
+Field *StringField::Clone(Table *pTable)
+{
+ StringField *clone = new StringField(String);
+ clone->Pos = FIELD_CLONE;
+ clone->ID = ID;
+ clone->MaxSizeOnDisk = GetDataSize();
+ return clone;
+}
+
+
+//---------------------------------------------------------------------------
+bool StringField::ApplyFilter(Field *Data, int op)
+{
+ if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
+ {
+ bool r = (op == FILTER_ISEMPTY);
+ if (!String)
+ return r;
+
+ if (CFStringGetLength(String) == 0)
+ return r;
+
+ return !r;
+ }
+ //
+ bool r;
+ StringField *compField = (StringField *)Data;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = !Compare(Data);
+ break;
+ case FILTER_NOTEQUALS:
+ r = !!Compare(Data);
+ break;
+ case FILTER_CONTAINS:
+ r = !!Contains(Data);
+ break;
+ case FILTER_NOTCONTAINS:
+ r = !Contains(Data);
+ break;
+ case FILTER_ABOVE:
+ r = (bool)(Compare(Data) > 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (bool)(Compare(compField) >= 0);
+ break;
+ case FILTER_BELOW:
+ r = (bool)(Compare(compField) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (bool)(Compare(compField) <= 0);
+ break;
+ case FILTER_BEGINS:
+ r = !!Starts(compField);
+ break;
+ case FILTER_ENDS:
+ {
+ CFStringRef compareString = ((StringField*)Data)->GetString();
+ if (!String || !compareString) return 0;
+
+ CFRange findRange = CFStringFind(String, compareString, kCFCompareCaseInsensitive|kCFCompareNonliteral|kCFCompareAnchored|kCFCompareBackwards);
+ if (findRange.location == kCFNotFound)
+ r=0;
+ else
+ r = findRange.location != 0;
+ }
+ break;
+ case FILTER_LIKE:
+ /* TODO
+ if (compField->optimized_the)
+ p = compField->optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(p);
+ compField->optimized_the = p;
+ }
+
+ if (optimized_the)
+ d = optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(d);
+ optimized_the=d;
+ }
+ r = (bool)(nde_wcsicmp(d, p) == 0);
+ */
+ r = !!Compare(compField);
+ break;
+ case FILTER_BEGINSLIKE:
+ /*
+ if (compField->optimized_the)
+ p = compField->optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(p);
+ compField->optimized_the = p;
+ }
+
+ if (optimized_the)
+ d = optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(d);
+ optimized_the=d;
+ }
+
+ r = (bool)(nde_wcsnicmp(d, p, wcslen(p)) == 0);
+ */
+ r = !!Starts(compField);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+}
+
diff --git a/Src/nde/osx/StringField.h b/Src/nde/osx/StringField.h
new file mode 100644
index 00000000..ac906fc7
--- /dev/null
+++ b/Src/nde/osx/StringField.h
@@ -0,0 +1,50 @@
+/*
+---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+---------------------------------------------------------------------------
+*/
+
+/*
+---------------------------------------------------------------------------
+
+StringField Class Prototypes
+
+---------------------------------------------------------------------------
+*/
+
+#ifndef __STRINGFIELD_H
+#define __STRINGFIELD_H
+
+#include <CoreFoundation/CFString.h>
+
+class StringField : public Field
+{
+
+public:
+ StringField();
+ ~StringField();
+
+ StringField(CFStringRef Str);
+ CFStringRef GetString(); // CFRetain this if you need to keep it for a while
+ void SetNDEString(CFStringRef 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);
+
+ CFStringRef String;
+
+};
+
+#endif
+
diff --git a/Src/nde/osx/Table.cpp b/Src/nde/osx/Table.cpp
new file mode 100644
index 00000000..31087b1f
--- /dev/null
+++ b/Src/nde/osx/Table.cpp
@@ -0,0 +1,659 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Table Class
+Apple Mac OS X implementation
+--------------------------------------------------------------------------- */
+#include "../nde.h"
+#include <stdio.h>
+#include <string.h>
+#include "../CRC.H"
+
+const char *tSign="NDETABLE";
+
+//---------------------------------------------------------------------------
+Table::Table(const char *TableName, const char *Idx, BOOL Create, Database *_db, BOOL _Cached)
+: columns_cached(false), use_row_cache(false), Scanner(this)
+{
+ Handle = 0;
+ memset(column_ids, FIELD_UNKNOWN, 255);
+ Cached = _Cached;
+ db = _db;
+ AutoCreate = Create;
+ Name = strdup(TableName);
+ IdxName = strdup(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;
+
+ free(Name);
+ free(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(void)
+{
+ 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", TRUE);
+ Valid = (Handle && IdxHandle);
+
+ // unlock
+ if (Valid || !AutoCreate)
+ {
+ //if (Handle)
+ //Vfunlock(Handle);
+ }
+ else
+ {
+ if (Handle)
+ {
+ Vfclose(Handle);
+ if (IdxHandle)
+ Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ }
+ else
+ {
+ if (IdxHandle)
+ Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ Handle = Vfnew(Name, "w+b", Cached);
+ if (!Vflock(Handle))
+ {
+ Vfdestroy(Handle);
+ return FALSE;
+ }
+ }
+
+ Handle = Vfopen(Handle, Name, "w+b", Cached);
+ IdxHandle = Vfopen(0, IdxName, "w+b", TRUE);
+ Valid = (Handle && IdxHandle);
+
+ if (Valid)
+ {
+ Vfwrite(__TABLE_SIGNATURE__, strlen(__TABLE_SIGNATURE__), Handle);
+ Vfwrite(__INDEX_SIGNATURE__, strlen(__TABLE_SIGNATURE__), IdxHandle);
+ // TODO bensk> change if NUM_SPECIAL_RECORDS ever increases
+ int v=NUM_SPECIAL_RECORDS;//strlen(__TABLE_SIGNATURE__);
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ // v = 0; fwrite(&v, sizeof(v), 1, IdxHandle);
+ v = -1; Vfwrite(&v, sizeof(v), IdxHandle); // write ID
+ v = 0;
+ for (int i=0;i<NUM_SPECIAL_RECORDS;i++)
+ {
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ }
+ Sync();
+ justcreated = 1;
+ }
+ }
+
+
+ if (!Valid)
+ {
+ if (Handle) Vfdestroy(Handle);
+ if (IdxHandle) Vfdestroy(IdxHandle);
+ Handle = NULL;
+ IdxHandle = NULL;
+ }
+ else
+ {
+ 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, CFSTR("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, CFStringRef 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(CFStringRef 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(CFStringRef name, CFStringRef desc)
+{
+ ColumnField *header = GetColumnByName(name);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ AddIndexById(Idx, desc);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Table::AddIndexById(unsigned char Id, CFStringRef 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->SetModified(TRUE);
+ IndexList->WriteFields(this);
+}
+
+//---------------------------------------------------------------------------
+void Table::DropIndexByName(CFStringRef desc)
+{
+ IndexField *indx = GetIndexByName(desc);
+ if (CFStringCompare(desc, CFSTR("None"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ 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(void)
+{
+ 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;
+}
+
+ColumnField *Table::GetColumnById(unsigned char Idx)
+{
+ if (!FieldsRecord)
+ return NULL;
+ return (ColumnField *)FieldsRecord->GetField(Idx);
+}
+
+ColumnField *Table::GetColumnByName(CFStringRef 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/osx/Table.h b/Src/nde/osx/Table.h
new file mode 100644
index 00000000..df27143b
--- /dev/null
+++ b/Src/nde/osx/Table.h
@@ -0,0 +1,165 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Table Class Prototypes
+Apple Mac OS X 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; // TODO: CFStringRef
+ char *IdxName; // TODO: CFStringRef
+
+ VFILE *IdxHandle;
+ BOOL AutoCreate;
+ Record *FieldsRecord;
+ IndexRecord *IndexList;
+ Database *db;
+ BOOL 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, BOOL Create, Database *db, BOOL Cached);
+ ~Table();
+ BOOL Open(void);
+ void Close(void);
+
+ // Columns
+ ColumnField *NewColumn(unsigned char Id, CFStringRef name, unsigned char type, BOOL indexUniques);
+
+ void DeleteColumn(ColumnField *field); // todo
+ void DeleteColumnByName(const wchar_t *name); // todo
+ void DeleteColumnById(unsigned char Id); // todo
+ void PostColumns(void);
+ NDE_API Record *GetColumns(void);
+ ColumnField *GetColumnByName(CFStringRef 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(CFStringRef FieldName, CFStringRef KeyName);
+ void AddIndexById(unsigned char Id, CFStringRef KeyName);
+
+ void WalkIndices(IndexWalker callback, void *context);
+
+ IndexField *GetIndexByName(CFStringRef name);
+ IndexField *GetIndexById(unsigned char Id);
+ using Scanner::SetWorkingIndexByName;
+ using Scanner::SetWorkingIndexById;
+ NDE_API BOOL CheckIndexing(void);
+ void DropIndexByName(CFStringRef 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);
+ 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/osx/Vfs.cpp b/Src/nde/osx/Vfs.cpp
new file mode 100644
index 00000000..a9dce43d
--- /dev/null
+++ b/Src/nde/osx/Vfs.cpp
@@ -0,0 +1,542 @@
+
+/* ---------------------------------------------------------------------------
+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
+#include "../nu/strsafe.h"
+/* the FILE (fopen/fwrite/etc) implementation for Mac and Linux */
+
+
+
+VFILE *Vfnew(const char *fl, const char *mode, BOOL 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 = TRUE;
+#endif
+
+ if (!strchr(mode, '+'))
+ {
+ if (strchr(mode, 'r'))
+ f->mode = VFS_READ | VFS_MUSTEXIST;
+ if (strchr(mode, 'w'))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
+ if (strchr(mode, 'a'))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
+ }
+ else
+ {
+ if (strstr(mode, "r+"))
+ f->mode = VFS_WRITE | VFS_MUSTEXIST;
+ if (strstr(mode, "w+"))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
+ if (strstr(mode, "a+"))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
+ }
+
+ if (f->mode == 0 || ((f->mode & VFS_READ) && (f->mode & VFS_WRITE)))
+ {
+ 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, BOOL 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);
+ if (rv == 0)
+ f->dirty=0;
+ 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/osx/Vfs.h b/Src/nde/osx/Vfs.h
new file mode 100644
index 00000000..2044aeb5
--- /dev/null
+++ b/Src/nde/osx/Vfs.h
@@ -0,0 +1,66 @@
+#ifndef __NDE_VFS_H
+#define __NDE_VFS_H
+
+#include <bfc/platform/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;
+ BOOL 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, BOOL Cached);
+VFILE *Vfopen(VFILE *f, const char *fl, const char *mode, BOOL 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
+
diff --git a/Src/nde/osx/nde_c.cpp b/Src/nde/osx/nde_c.cpp
new file mode 100644
index 00000000..56791bd8
--- /dev/null
+++ b/Src/nde/osx/nde_c.cpp
@@ -0,0 +1,640 @@
+#include "nde_c.h"
+#include "nde.h"
+#ifdef _WIN32
+#include "../nu/AutoCharFn.h"
+#include "../nu/AutoWide.h"
+#endif
+
+/* Database */
+#ifdef _WIN32
+nde_database_t NDE_CreateDatabase(HINSTANCE hInstance)
+{
+ return (nde_database_t)new Database(hInstance);
+}
+#else
+nde_database_t NDE_CreateDatabase()
+{
+ return (nde_database_t)new Database();
+}
+#endif
+
+void NDE_DestroyDatabase(nde_database_t db)
+{
+ delete (Database *)db;
+}
+
+#ifdef _WIN32
+nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *indexname, int create, int cache)
+{
+ Database *database = (Database *)db;
+ if (database && filename)
+ return (nde_table_t)database->OpenTable(filename, indexname, (BOOL)create, (BOOL)cache);
+ else
+ return 0;
+}
+#else
+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;
+}
+#endif
+
+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 */
+
+#ifdef _WIN32
+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, AutoWide(name), type, FALSE);
+ else
+ return 0;
+}
+#else
+nde_field_t NDE_Table_NewColumn(nde_table_t t, unsigned char id, CFStringRef name, unsigned char type)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return (nde_field_t)table->NewColumn(id, name, type, FALSE);
+ else
+ return 0;
+}
+#endif
+
+void NDE_Table_PostColumns(nde_table_t t)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->PostColumns();
+}
+
+#ifdef _WIN32
+void NDE_Table_AddIndexByID(nde_table_t t, unsigned char id, const char *name)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->AddIndexById(id, AutoWide(name));
+}
+#else
+void NDE_Table_AddIndexByID(nde_table_t t, unsigned char id, CFStringRef name)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->AddIndexById(id, name);
+}
+#endif
+
+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();
+}
+
+#ifdef _WIN32
+void NDE_Table_Compact(nde_table_t t, int *progress)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->Compact(progress);
+}
+#endif
+
+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;
+}
+
+#ifdef _WIN32
+nde_field_t NDE_Table_GetColumnByName(nde_table_t t, const char *name)
+{
+ Table *table = (Table *)t;
+ if (table && name)
+ return (nde_field_t)table->GetColumnByName(AutoWide(name));
+ else
+ return 0;
+}
+#else
+nde_field_t NDE_Table_GetColumnByName(nde_table_t t, CFStringRef name)
+{
+ Table *table = (Table *)t;
+ if (table && name)
+ return (nde_field_t)table->GetColumnByName(name);
+ else
+ return 0;
+}
+#endif
+
+void NDE_Table_SetColumnSearchableByID(nde_table_t t, unsigned char id, int searchable)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->SetFieldSearchableById(id, !!searchable);
+}
+/* Scanner */
+#ifdef _WIN32
+int NDE_Scanner_Query(nde_scanner_t s, const wchar_t *query)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Query(query);
+ else
+ return 0;
+}
+
+void NDE_Scanner_Search(nde_scanner_t s, const wchar_t *search_term)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->Search(search_term);
+}
+
+const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->GetLastQuery();
+ else
+ return 0;
+}
+#elif defined(__APPLE__)
+int NDE_Scanner_Query(nde_scanner_t s, const char *query)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Query(query);
+ else
+ return 0;
+}
+
+CFStringRef NDE_Scanner_GetLastQuery(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->GetLastQuery();
+ else
+ return 0;
+}
+#endif
+
+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;
+}
+
+#ifdef _WIN32
+nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t s, const char *name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->NewFieldByName(AutoWide(name), PERM_READWRITE);
+ else
+ return 0;
+}
+#else
+nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t s, CFStringRef name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->NewFieldByName(name, PERM_READWRITE);
+ else
+ return 0;
+}
+#endif
+
+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;
+}
+
+#ifdef _WIN32
+nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t s, const char *name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner && name)
+ return (nde_field_t)scanner->GetFieldByName(AutoWide(name));
+ else
+ return 0;
+}
+#else
+nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t s, CFStringRef name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner && name)
+ return (nde_field_t)scanner->GetFieldByName(name);
+ else
+ return 0;
+}
+#endif
+
+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;
+}
+
+#ifdef _WIN32
+int NDE_Scanner_LocateString(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ StringField field(value);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+
+int NDE_Scanner_LocateNDEString(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ StringField field(value, STRING_IS_NDESTRING);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+#endif
+
+#ifdef _WIN32
+int NDE_Scanner_LocateFilename(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ FilenameField field(value);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+int NDE_Scanner_LocateNDEFilename(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ FilenameField field(value, STRING_IS_NDESTRING);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+#endif
+
+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 */
+#ifdef _WIN32
+void NDE_StringField_SetNDEString(nde_field_t f, wchar_t *nde_string)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ field->SetNDEString(nde_string);
+}
+
+wchar_t *NDE_StringField_GetString(nde_field_t f)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ return field->GetStringW();
+ else
+ return 0;
+}
+
+#ifdef _WIN32
+void NDE_StringField_SetString(nde_field_t f, const wchar_t *str)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ field->SetStringW(str);
+}
+#endif
+
+#elif defined(__APPLE__)
+void NDE_StringField_SetString(nde_field_t f, CFStringRef nde_string)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ field->SetNDEString(nde_string);
+}
+
+CFStringRef NDE_StringField_GetString(nde_field_t f)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ return field->GetString();
+ else
+ return 0;
+}
+#endif
+
+
+/* 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;
+}
+#ifdef __APPLE__
+CFDataRef NDE_BinaryField_GetCFData(nde_field_t f)
+{
+ BinaryField *field = (BinaryField *)(Field *)f;
+ if (field)
+ return field->GetCFData();
+ else
+ return 0;
+}
+#endif
+
+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 */
+#ifdef _WIN32
+const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetFieldName();
+ else
+ return 0;
+}
+#else
+CFStringRef NDE_ColumnField_GetFieldName(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetFieldName();
+ else
+ return 0;
+}
+#endif
+
+unsigned char NDE_ColumnField_GetDataType(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetDataType();
+ else
+ return FIELD_UNKNOWN;
+}
+
+#ifdef _WIN32
+__time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp)
+{
+ IntegerField f(value);
+ f.ApplyConversion(str, tp);
+ return f.GetValue();
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/osx/nde_c.h b/Src/nde/osx/nde_c.h
new file mode 100644
index 00000000..b0b4244f
--- /dev/null
+++ b/Src/nde/osx/nde_c.h
@@ -0,0 +1,246 @@
+#pragma once
+/* C style API.
+
+We'll eventually deprecate the C++ API as it presents a lot of linking challenges
+*/
+#include "nde_defines.h"
+
+#ifdef _WIN32
+#include "NDEString.h"
+#include <bfc/platform/types.h>
+#endif
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef __APPLE__
+ typedef time_t __time64_t; // TODO: find a way to do native 64bit time values
+#endif
+ typedef struct nde_database_struct_t { } *nde_database_t;
+ typedef struct nde_table_struct_t { } *nde_table_t;
+ typedef struct nde_scanner_t_struct_t { } *nde_scanner_t;
+ typedef struct nde_field_t_struct_t { } *nde_field_t;
+ typedef struct nde_filter_struct_t { } *nde_filter_t;
+
+ /* Database functions */
+
+ // Windows API
+#ifdef _WIN32
+NDE_API void NDE_Init();
+NDE_API void NDE_Quit();
+#ifdef __cplusplus
+NDE_API nde_database_t NDE_CreateDatabase(HINSTANCE hInstance=0);
+#else
+NDE_API nde_database_t NDE_CreateDatabase(HINSTANCE hInstance);
+#endif
+NDE_API void NDE_DestroyDatabase(nde_database_t db);
+NDE_API nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *filename_index, int create, int cache);
+
+NDE_API void NDE_Database_CloseTable(nde_database_t db, nde_table_t table);
+
+/* Table functions */
+NDE_API nde_field_t NDE_Table_NewColumn(nde_table_t table, unsigned char id, const char *name, unsigned char type);
+NDE_API 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);
+#ifdef _WIN32
+NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, const char *name);
+#else
+NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, CFStringRef name);
+#endif
+NDE_API void NDE_Table_SetColumnSearchableByID(nde_table_t table, unsigned char id, int searchable);
+
+/* Scanner functions */
+NDE_API int NDE_Scanner_Query(nde_scanner_t scanner, const wchar_t *query);
+NDE_API void NDE_Scanner_Search(nde_scanner_t scanner, const wchar_t *search_term);
+NDE_API const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t scanner);
+NDE_API int NDE_Scanner_GetRecordsCount(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_New(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_Post(nde_scanner_t scanner);
+#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 wchar_t *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, wchar_t *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 wchar_t *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, wchar_t *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 wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, wchar_t *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 wchar_t *NDE_StringField_GetString(nde_field_t field); /* returns non-const because it's an NDE string (reference counted, see ndestring.h) */
+NDE_API void NDE_StringField_SetNDEString(nde_field_t field, wchar_t *nde_string);
+NDE_API void NDE_StringField_SetString(nde_field_t field, const wchar_t *str);
+
+/* IntegerField functions */
+NDE_API void NDE_IntegerField_SetValue(nde_field_t field, int value);
+NDE_API int NDE_IntegerField_GetValue(nde_field_t field);
+NDE_API nde_field_t NDE_IntegerField_Create(int value); /* mainly used for NDE_Scanner_AddFilterByID */
+
+/* BinaryField functions */
+// on windows, the data pointer is optionally reference counted via ndestring (ndestring_retain if you plan on keeping it)
+NDE_API void *NDE_BinaryField_GetData(nde_field_t field, size_t *length);
+NDE_API void NDE_BinaryField_SetData(nde_field_t field, const void *data, size_t length);
+
+/* Int128Field functions */
+NDE_API void NDE_Int128Field_SetValue(nde_field_t field, const void *value);
+
+/* ColumnField functions */
+NDE_API const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t field);
+NDE_API unsigned char NDE_ColumnField_GetDataType(nde_field_t field);
+
+NDE_API __time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp);
+#elif defined(__APPLE__) // Mac OS X API, uses CFStringRef for a lot of stuff
+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);
+#ifdef _WIN32
+NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, const char *name);
+#else
+NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, CFStringRef name);
+#endif
+
+/* Scanner functions */
+NDE_API int NDE_Scanner_Query(nde_scanner_t scanner, const wchar_t *query);
+NDE_API CFStringRef 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 wchar_t *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, wchar_t *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 wchar_t *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, wchar_t *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 wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode);
+NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, wchar_t *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 CFStringRef 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_SetString(nde_field_t field, CFStringRef string);
+
+/* 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 */
+NDE_API void *NDE_BinaryField_GetData(nde_field_t field, size_t *length);
+NDE_API CFDataRef NDE_BinaryField_GetCFData(nde_field_t field);
+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 */
+#ifdef _WIN32
+NDE_API const char *NDE_ColumnField_GetFieldName(nde_field_t field);
+#else
+ NDE_API CFStringRef NDE_ColumnField_GetFieldName(nde_field_t field);
+#endif
+NDE_API unsigned char NDE_ColumnField_GetDataType(nde_field_t field);
+
+NDE_API __time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp);
+#endif
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/resource.h b/Src/nde/resource.h
new file mode 100644
index 00000000..15e2dc6a
--- /dev/null
+++ b/Src/nde/resource.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by nde.rc
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/nde/version.rc2 b/Src/nde/version.rc2
new file mode 100644
index 00000000..e8961ac0
--- /dev/null
+++ b/Src/nde/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION WINAMP_PRODUCTVER
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Support Library"
+ VALUE "FileVersion", STR_WINAMP_PRODUCTVER
+ VALUE "InternalName", "nde.dll"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "nde.dll"
+ VALUE "ProductName", "Nullsoft Database Engine (NDE) Library"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/Src/nde/win/Binary32Field.cpp b/Src/nde/win/Binary32Field.cpp
new file mode 100644
index 00000000..507682b1
--- /dev/null
+++ b/Src/nde/win/Binary32Field.cpp
@@ -0,0 +1,77 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Binary32Field Class
+Field data layout:
+[4 bytes] length
+[length bytes] binary data
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "Binary32Field.h"
+#include "../ndestring.h"
+//---------------------------------------------------------------------------
+Binary32Field::Binary32Field(const uint8_t *_Data, size_t len) : BinaryField(_Data, (int)len)
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+void Binary32Field::InitField(void)
+{
+ Type = FIELD_BINARY32;
+}
+
+//---------------------------------------------------------------------------
+Binary32Field::Binary32Field()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+void Binary32Field::ReadTypedData(const uint8_t *data, size_t len)
+{
+ size_t pos = 0;
+ CHECK_INT(len); //len-=4;
+ uint32_t c = GET_INT(); pos += 4;
+ if (c && c<=len)
+ {
+ Size = c;
+ ndestring_release((wchar_t *)Data);
+ Data = (uint8_t *)ndestring_malloc(c);
+ GET_BINARY(Data, data, c, pos);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Binary32Field::WriteTypedData(uint8_t *data, size_t len)
+{
+ uint32_t c;
+ size_t pos = 0;
+
+ CHECK_INT(len); //len-=4;
+
+ if (Data && Size<=len)
+ {
+ c = (uint32_t)Size;
+ PUT_INT(c); pos += 4;
+ if (Data)
+ PUT_BINARY(data, (unsigned char*)Data, c, pos);
+ }
+ else
+ {
+ PUT_INT(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+size_t Binary32Field::GetDataSize(void)
+{
+ if (!Data) return 4;
+ return Size + 4;
+} \ No newline at end of file
diff --git a/Src/nde/win/Binary32Field.h b/Src/nde/win/Binary32Field.h
new file mode 100644
index 00000000..0684146b
--- /dev/null
+++ b/Src/nde/win/Binary32Field.h
@@ -0,0 +1,32 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Binary32Field Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __NDE_BINARY32FIELD_H
+#define __NDE_BINARY32FIELD_H
+
+#include "BinaryField.h"
+#include <bfc/platform/types.h>
+
+class Binary32Field : public BinaryField
+{
+protected:
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ void InitField(void);
+
+public:
+ Binary32Field(const uint8_t *Data, size_t len);
+ Binary32Field();
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/BinaryField.cpp b/Src/nde/win/BinaryField.cpp
new file mode 100644
index 00000000..0641fdaf
--- /dev/null
+++ b/Src/nde/win/BinaryField.cpp
@@ -0,0 +1,172 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ BinaryField Class
+Field data layout:
+[2 bytes] length
+[length bytes] binary data
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "BinaryField.h"
+#include "../NDEString.h"
+//---------------------------------------------------------------------------
+BinaryField::BinaryField(const uint8_t *_Data, int len)
+{
+ InitField();
+ Type = FIELD_BINARY;
+ if (_Data && len > 0)
+ {
+ Data = (uint8_t *)ndestring_malloc(len);
+ memcpy(Data, _Data, len);
+ Size = len;
+ }
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::InitField(void)
+{
+ Type = FIELD_BINARY;
+ Data = NULL;
+ Size = 0;
+}
+
+//---------------------------------------------------------------------------
+BinaryField::BinaryField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+BinaryField::~BinaryField()
+{
+ ndestring_release((wchar_t *)Data);
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ unsigned short c;
+ int pos = 0;
+
+ CHECK_SHORT(len);
+
+ c = GET_SHORT(); pos += 2;
+ if (c && c<=len)
+ {
+ Size = c;
+ ndestring_release((wchar_t *)Data);
+ Data = (uint8_t *)ndestring_malloc(c);
+ GET_BINARY(Data, data, c, pos);
+ }
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::WriteTypedData(uint8_t *data, size_t len)
+{
+ size_t pos = 0;
+
+ CHECK_SHORT(len);
+
+ if (Data && Size<=len)
+ {
+ unsigned short c = (unsigned short)Size;
+ PUT_SHORT(c); pos += 2;
+ if (Data)
+ PUT_BINARY(data, (unsigned char*)Data, c, pos);
+ }
+ else
+ {
+ PUT_SHORT(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+const uint8_t *BinaryField::GetData(size_t *len)
+{
+ if (len)
+ *len = Size;
+ return Data;
+}
+
+//---------------------------------------------------------------------------
+void BinaryField::SetData(const uint8_t *_Data, size_t len)
+{
+ if (!_Data || !len) return;
+ ndestring_release((wchar_t *)Data);
+ Size = 0;
+ Data = (uint8_t *)ndestring_malloc(len);
+ memcpy(Data, _Data, len);
+ Size = len;
+}
+
+//---------------------------------------------------------------------------
+size_t BinaryField::GetDataSize(void)
+{
+ if (!Data) return 2;
+ return Size + 2;
+}
+
+//---------------------------------------------------------------------------
+int BinaryField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ size_t compare_length;
+ const uint8_t *compare_data = ((BinaryField*)Entry)->GetData(&compare_length);
+ return memcmp(Data, compare_data, min(compare_length, Size));
+}
+
+//---------------------------------------------------------------------------
+bool BinaryField::ApplyFilter(Field *FilterData, int op)
+{
+ size_t l, s;
+ const uint8_t *p = ((BinaryField *)FilterData)->GetData(&l);
+ const uint8_t *d = GetData(&s);
+ if (!p)
+ p = (const uint8_t *)"";
+ if (!d)
+ d = (const uint8_t *)"";
+ bool r;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ if (l != s)
+ r = false;
+ else
+ r = !memcmp(d, p, min(s, l));
+ break;
+ case FILTER_CONTAINS:
+ if (l > s)
+ r = FALSE;
+ else
+ r = !!memmem(d, p, s, l);
+ break;
+ case FILTER_ABOVE:
+ r = (memcmp(d, p, min(s, l)) > 0);
+ break;
+ case FILTER_BELOW:
+ r = (memcmp(d, p, min(s, l)) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (memcmp(d, p, min(s, l)) <= 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (memcmp(d, p, min(s, l)) >= 0);
+ break;
+ case FILTER_ISEMPTY:
+ r = (s == 0);
+ break;
+ case FILTER_ISNOTEMPTY:
+ r = (s != 0);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/nde/win/BinaryField.h b/Src/nde/win/BinaryField.h
new file mode 100644
index 00000000..d720ee3b
--- /dev/null
+++ b/Src/nde/win/BinaryField.h
@@ -0,0 +1,38 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ BinaryField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __BINARYFIELD_H
+#define __BINARYFIELD_H
+
+#include "Field.h"
+#include <bfc/platform/types.h>
+class BinaryField : public Field
+{
+protected:
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ virtual bool ApplyFilter(Field *Data, int flags);
+ void InitField(void);
+ uint8_t *Data;
+ size_t Size;
+
+public:
+ ~BinaryField();
+ BinaryField(const uint8_t *Data, int len);
+ BinaryField();
+ const uint8_t *GetData(size_t *len);
+ void SetData(const uint8_t *Data, size_t len);
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/ColumnField.cpp b/Src/nde/win/ColumnField.cpp
new file mode 100644
index 00000000..321f5bdb
--- /dev/null
+++ b/Src/nde/win/ColumnField.cpp
@@ -0,0 +1,184 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ColumnField Class
+Windows implementation
+
+Field data layout:
+[1 byte] Field Type
+[1 byte] Unused (maybe convert to 'searchable')
+[1 byte] Name Length
+[Name Length bytes] Name (UTF-8)
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+
+//---------------------------------------------------------------------------
+ColumnField::ColumnField(unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, Table *parentTable)
+{
+ InitField();
+ Type = FIELD_COLUMN;
+ MyType = FieldType;
+ Name = ndestring_wcsdup(FieldName);
+ ID = FieldID;
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::InitField(void)
+{
+ searchable = false;
+ MyType = FIELD_UNKNOWN;
+ Type = FIELD_COLUMN;
+ Name = NULL;
+ ID = 0;
+}
+
+//---------------------------------------------------------------------------
+ColumnField::ColumnField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+ColumnField::~ColumnField()
+{
+ ndestring_release(Name);
+}
+
+void ColumnField::SetSearchable(bool val)
+{
+ searchable=val;
+}
+
+bool ColumnField::IsSearchableField() const
+{
+ return searchable;
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ unsigned char c;
+ int pos=0;
+
+ // [1 byte] Field Type
+ CHECK_CHAR(len);
+ MyType = GET_CHAR();
+ pos++;
+
+ // [1 byte] unused
+ CHECK_CHAR(len);
+//cut: indexUnique = (BOOL)GET_CHAR();
+ pos++;
+
+ // [1 byte] string length
+ CHECK_CHAR(len);
+ c = GET_CHAR();
+ pos++;
+
+ if (c)
+ {
+ CHECK_BIN(len, c);
+
+ int string_length = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, 0, 0);
+ Name = ndestring_malloc((string_length+1)*sizeof(wchar_t));
+ MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, Name, string_length);
+ Name[string_length]=0;
+ }
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::WriteTypedData(uint8_t *data, size_t len)
+{
+ int pos = 0;
+
+ // [1 byte] Field Type
+ CHECK_CHAR(len);
+ PUT_CHAR(MyType);
+ pos++;
+
+ // [1 byte] unused
+ CHECK_CHAR(len);
+ PUT_CHAR(0/*(char)indexUnique*/);
+ pos++;
+
+ CHECK_CHAR(len);
+ if (Name)
+ {
+ size_t string_length = WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0);
+ if (string_length)
+ {
+ PUT_CHAR((uint8_t)string_length-1);
+ pos++;
+
+ CHECK_BIN(len, string_length-1);
+ WideCharToMultiByte(CP_UTF8, 0, Name, -1, (LPSTR)(data+pos), (int)string_length-1, 0, 0);
+ }
+ else
+ {
+ PUT_CHAR(0);
+ }
+ }
+ else
+ {
+ PUT_CHAR(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+unsigned char ColumnField::GetDataType(void)
+{
+ return MyType;
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::SetDataType(unsigned char type)
+{
+ if ((MyType == FIELD_INTEGER || MyType == FIELD_BOOLEAN || MyType == FIELD_DATETIME || MyType == FIELD_LENGTH || MyType == FIELD_INT64) &&
+ (type == FIELD_INTEGER || type == FIELD_BOOLEAN || type == FIELD_DATETIME || type == FIELD_LENGTH || type == FIELD_INT64)) {
+ MyType = type;
+ }
+ // going from string to filename or filename to string is OK
+ if ((MyType == FIELD_FILENAME && type == FIELD_STRING)
+ || (MyType == FIELD_STRING && type == FIELD_FILENAME))
+ {
+ MyType = type;
+ }
+}
+
+//---------------------------------------------------------------------------
+wchar_t *ColumnField::GetFieldName(void)
+{
+ return Name;
+}
+
+//---------------------------------------------------------------------------
+void ColumnField::SetFieldName(wchar_t *name)
+{
+ if (Name) ndestring_release(Name);
+ Name = ndestring_wcsdup(name);
+}
+
+//---------------------------------------------------------------------------
+size_t ColumnField::GetDataSize(void)
+{
+ size_t s=3;
+ if (Name)
+ {
+ int string_length=WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0);
+ if (string_length)
+ s+=string_length-1;
+ }
+ return s;
+}
+
+//---------------------------------------------------------------------------
+int ColumnField::Compare(Field * /*Entry*/)
+{
+ return 0;
+} \ No newline at end of file
diff --git a/Src/nde/win/ColumnField.h b/Src/nde/win/ColumnField.h
new file mode 100644
index 00000000..380b417c
--- /dev/null
+++ b/Src/nde/win/ColumnField.h
@@ -0,0 +1,49 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ColumnField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __COLUMNFIELD_H
+#define __COLUMNFIELD_H
+
+#include "Field.h"
+#include "LinkedList.h"
+#include "Table.h"
+
+#include "Scanner.h"
+
+class ColumnField : public Field
+{
+public:
+ ColumnField(unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, Table *parentTable);
+ ColumnField();
+ ~ColumnField();
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ void InitField(void);
+
+ bool IsSearchableField() const;
+ void SetSearchable(bool val);
+
+ unsigned char GetDataType(void);
+ void SetDataType(unsigned char type);
+
+ wchar_t *GetFieldName(void); // not const because it's an NDE string
+ void SetFieldName(wchar_t *name);
+
+protected:
+ bool searchable;
+ wchar_t *Name;
+ unsigned char MyType;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/FilenameField.cpp b/Src/nde/win/FilenameField.cpp
new file mode 100644
index 00000000..c658c33f
--- /dev/null
+++ b/Src/nde/win/FilenameField.cpp
@@ -0,0 +1,128 @@
+#include "FilenameField.h"
+#include "../nde.h"
+
+
+//---------------------------------------------------------------------------
+FilenameField::FilenameField(const wchar_t *Str, int strkind) : StringField(Str, strkind)
+{
+ Type = FIELD_FILENAME;
+}
+
+//---------------------------------------------------------------------------
+FilenameField::FilenameField()
+{
+ Type = FIELD_FILENAME;
+}
+
+//---------------------------------------------------------------------------
+int FilenameField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (!CompatibleFields(Entry->GetType(), GetType()))
+ return 0;
+ return mywcsicmp_fn(GetStringW(), ((FilenameField*)Entry)->GetStringW());
+}
+
+//---------------------------------------------------------------------------
+int FilenameField::Starts(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (!CompatibleFields(Entry->GetType(), GetType()))
+ return 0;
+ const wchar_t *p = ((StringField*)Entry)->GetStringW();
+ const wchar_t *d = GetStringW();
+ if (!d || !p) return 0;
+ return nde_fnbegins(d, p);
+}
+
+//---------------------------------------------------------------------------
+int FilenameField::Contains(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (!CompatibleFields(Entry->GetType(), GetType()))
+ return 0;
+ const wchar_t *p = ((StringField*)Entry)->GetStringW();
+ const wchar_t *d = GetStringW();
+ if (!d || !p) return 0;
+ return nde_fncontains(GetStringW(), ((StringField*)Entry)->GetStringW());
+}
+
+Field *FilenameField::Clone(Table *pTable)
+{
+ FilenameField *clone = new FilenameField(StringW, STRING_IS_NDESTRING);
+ clone->Pos = FIELD_CLONE;
+ clone->ID = ID;
+ clone->MaxSizeOnDisk = (uint32_t)GetDataSize();
+ return clone;
+}
+
+// TODO: make file system string comparison functions
+//---------------------------------------------------------------------------
+bool FilenameField::ApplyFilter(Field *Data, int op)
+{
+ // TODO: maybe do this?
+ if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
+ {
+ bool r = (op == FILTER_ISEMPTY);
+ if (!StringW)
+ return r;
+
+ if (StringW && StringW[0] == 0)
+ return r;
+
+ return !r;
+ }
+ //
+ bool r;
+ wchar_t *p=0;
+ if (Data->GetType() == FIELD_STRING)
+ p = ((StringField *)Data)->GetStringW();
+ else
+ p = ((FilenameField *)Data)->GetStringW();
+ wchar_t *d = GetStringW();
+ if (!p)
+ p = L"";
+ if (!d)
+ d = L"";
+
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = !nde_wcsicmp_fn(d, p);
+ break;
+ case FILTER_NOTEQUALS:
+ r = !!nde_wcsicmp_fn(d, p);
+ break;
+ case FILTER_CONTAINS:
+ r = nde_fncontains(d, p);
+ break;
+ case FILTER_NOTCONTAINS:
+ r = !nde_fncontains(d, p);
+ break;
+ case FILTER_ABOVE:
+ r = (bool)(nde_wcsicmp_fn(d, p) > 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (bool)(nde_wcsicmp_fn(d, p) >= 0);
+ break;
+ case FILTER_BELOW:
+ r = (bool)(nde_wcsicmp_fn(d, p) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (bool)(nde_wcsicmp_fn(d, p) <= 0);
+ break;
+ case FILTER_BEGINS:
+ r = nde_fnbegins(d, p);
+ break;
+ case FILTER_ENDS:
+ r = nde_fnends(d, p);
+ break;
+ case FILTER_LIKE:
+ r = (bool)(nde_wcsicmp_fn(d, p) == 0);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+} \ No newline at end of file
diff --git a/Src/nde/win/FilenameField.h b/Src/nde/win/FilenameField.h
new file mode 100644
index 00000000..017e8bc4
--- /dev/null
+++ b/Src/nde/win/FilenameField.h
@@ -0,0 +1,26 @@
+#ifndef NDE_FILENAMEFIELD_H
+#define NDE_FILENAMEFIELD_H
+
+/*
+ Mostly the same as StringField
+ but this implements OS-dependent string comparisons that make sense for the file system
+*/
+
+#include "../nde.h"
+#include "../NDEString.h"
+
+class FilenameField : public StringField
+{
+protected:
+ virtual int Compare(Field *Entry);
+ virtual int Starts(Field *Entry);
+ virtual int Contains(Field *Entry);
+ virtual bool ApplyFilter(Field *Data, int op);
+ virtual Field *Clone(Table *pTable);
+
+public:
+ FilenameField(const wchar_t *Str, int strkind=STRING_IS_WCHAR);
+ FilenameField();
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/IndexField.cpp b/Src/nde/win/IndexField.cpp
new file mode 100644
index 00000000..8b619b41
--- /dev/null
+++ b/Src/nde/win/IndexField.cpp
@@ -0,0 +1,145 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+IndexField Class
+Windows implementation
+
+Field data layout
+[4 bytes] Position
+[4 bytes] Data Type
+[1 byte] Name Length
+[Name Length bytes] Name (UTF-8)
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "../ndestring.h"
+//---------------------------------------------------------------------------
+IndexField::IndexField(unsigned char id, int Pos, int type, const wchar_t *FieldName)
+{
+ InitField();
+ Type = FIELD_INDEX;
+ Name = ndestring_wcsdup(FieldName);
+ ID = id;
+ Position = Pos;
+ DataType = type;
+}
+
+//---------------------------------------------------------------------------
+void IndexField::InitField(void)
+{
+ index = 0;
+ Type = FIELD_INDEX;
+ Name = NULL;
+ ID = 0;
+ Position = -1;
+ DataType = FIELD_UNKNOWN;
+}
+
+//---------------------------------------------------------------------------
+IndexField::IndexField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+IndexField::~IndexField()
+{
+ ndestring_release(Name);
+ delete index;
+}
+
+//---------------------------------------------------------------------------
+void IndexField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ unsigned char c;
+ int pos=0;
+ CHECK_INT(len);
+ Position = GET_INT(); pos += 4;
+ CHECK_INT(len);
+ DataType = GET_INT(); pos += 4;
+ CHECK_CHAR(len);
+ c = GET_CHAR(); pos++;
+ if (c)
+ {
+ CHECK_BIN(len, c);
+
+ int string_length = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, 0, 0);
+ Name = ndestring_malloc((string_length+1)*sizeof(wchar_t));
+ MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)(data+pos), c, Name, string_length);
+ Name[string_length]=0;
+ }
+}
+
+//---------------------------------------------------------------------------
+void IndexField::WriteTypedData(uint8_t *data, size_t len)
+{
+ int pos=0;
+ CHECK_INT(len);
+ PUT_INT(Position); pos += 4;
+
+ CHECK_INT(len);
+ PUT_INT(DataType); pos += 4;
+ CHECK_CHAR(len);
+
+ CHECK_CHAR(len);
+
+ if (Name)
+ {
+ size_t string_length = WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0);
+ if (string_length)
+ {
+ PUT_CHAR((uint8_t)string_length-1);
+ pos++;
+
+ CHECK_BIN(len, string_length-1);
+ WideCharToMultiByte(CP_UTF8, 0, Name, -1, (LPSTR)(data+pos), (int)string_length-1, 0, 0);
+ }
+ else
+ {
+ PUT_CHAR(0);
+ }
+ }
+ else
+ {
+ PUT_CHAR(0);
+ }
+}
+
+//---------------------------------------------------------------------------
+wchar_t *IndexField::GetIndexName(void)
+{
+ return Name;
+}
+
+//---------------------------------------------------------------------------
+size_t IndexField::GetDataSize(void)
+{
+ size_t s=9;
+ if (Name)
+ {
+ int string_length=WideCharToMultiByte(CP_UTF8, 0, Name, -1, 0, 0, 0, 0);
+ if (string_length)
+ s+=string_length-1;
+ }
+ s++;
+ return s;
+}
+
+//---------------------------------------------------------------------------
+int IndexField::Compare(Field * /*Entry*/)
+{
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+int IndexField::TranslateToIndex(int Id, IndexField *toindex)
+{
+ if (index && toindex && toindex->index)
+ return index->TranslateIndex(Id, toindex->index);
+ return -1;
+} \ No newline at end of file
diff --git a/Src/nde/win/IndexField.h b/Src/nde/win/IndexField.h
new file mode 100644
index 00000000..88a46aae
--- /dev/null
+++ b/Src/nde/win/IndexField.h
@@ -0,0 +1,37 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ IndexField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __INDEXFIELD_H
+#define __INDEXFIELD_H
+
+class IndexField : public Field
+{
+public:
+ IndexField(unsigned char id, int Pos, int type, const wchar_t *FieldName);
+ IndexField();
+ ~IndexField();
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ void InitField(void);
+
+ wchar_t *GetIndexName(void);
+ int TranslateToIndex(int Id, IndexField *index);
+ Index *index; // TODO: make protected
+protected:
+ int Position;
+ int DataType;
+ wchar_t *Name;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/IndexRecord.cpp b/Src/nde/win/IndexRecord.cpp
new file mode 100644
index 00000000..06405ba1
--- /dev/null
+++ b/Src/nde/win/IndexRecord.cpp
@@ -0,0 +1,169 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+IndexRecord Class
+
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include <stdio.h>
+
+IndexRecord::IndexRecord(int RecordPos, int insertionPoint, VFILE *TableHandle, Table *ParentTable)
+{
+ InsertionPoint = insertionPoint;
+ if (RecordPos != 0)
+ {
+ int n=0;
+ uint32_t ThisPos = RecordPos;
+ while (ThisPos)
+ {
+ if (n >= 128)
+ break;
+ Vfseek(TableHandle, ThisPos, SEEK_SET);
+ Field Entry (ThisPos);
+ Field *TypedEntry = Entry.ReadField(ParentTable, ThisPos, &ThisPos);
+
+ if (!TypedEntry) break; // some db error?
+
+ AddField(TypedEntry);
+ // ThisPos = TypedEntry->GetNextFieldPos();
+ n++;
+ }
+ }
+}
+
+void IndexRecord::BuildCollaboration()
+{
+ for (FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++)
+ {
+ IndexField *p = (IndexField *)(* itr);
+ if ((itr + 1) != Fields.end())
+ p->index->Colaborate((IndexField *)(*(itr+1)));
+ else
+ p->index->Colaborate((IndexField *)*(Fields.begin()));
+ }
+}
+
+bool IndexRecord::NeedFix()
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ if (p->index->NeedFix())
+ return true;
+ }
+ return false;
+}
+
+IndexField *IndexRecord::GetIndexByName(const wchar_t *name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ if (!_wcsicmp(p->GetIndexName(), name))
+ return p;
+ }
+ return NULL;
+}
+
+bool IndexRecord::CheckIndexing(int v)
+{
+ int i = v;
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ IndexField *p = (IndexField *)*itr;
+ v = p->index->GetCooperative(v);
+ }
+ return v == i;
+}
+
+Field *RecordBase::GetField( unsigned char ID )
+{
+ for ( FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++ )
+ {
+ IndexField *p = (IndexField *)*itr;
+ if ( p->GetFieldId() == ID )
+ return p;
+ }
+
+ return NULL;
+}
+
+void RecordBase::AddField(Field *field)
+{
+ if (!field)
+ return;
+
+ if (GetField(field->ID))
+ return;
+
+ Fields.push_back(field);
+}
+
+int IndexRecord::WriteFields( Table *ParentTable )
+{
+ Field *l_previous_field = NULL;
+ IndexField *l_index_field = NULL;
+
+ for ( FieldList::iterator itr = Fields.begin(); itr != Fields.end(); itr++ )
+ {
+ l_index_field = (IndexField *)(* itr);
+
+ Field* nextField = (itr + 1) != Fields.end() ? *(itr + 1) : nullptr;
+
+ //l_index_field->WriteField(ParentTable, l_previous_field, (Field*)l_index_field->next);
+ l_index_field->WriteField(ParentTable, l_previous_field, nextField);
+
+ l_previous_field = l_index_field;
+ }
+
+ return WriteIndex( ParentTable );
+}
+
+
+int IndexRecord::WriteIndex( Table *ParentTable )
+{
+ int P = 0;
+
+ if ( !Fields.empty() )
+ P = ( *Fields.begin() )->GetFieldPos();
+
+ return ParentTable->index->Update( INDEX_RECORD_NUM, P, this, FALSE );
+}
+
+void RecordBase::RemoveField(Field *field)
+{
+ if (!field)
+ return;
+
+ //Fields.erase(field);
+ //delete field;
+
+ for (auto it = Fields.begin(); it != Fields.end(); it++)
+ {
+ Field* f = *it;
+ if (f == field)
+ {
+ Fields.erase(it);
+ delete f;
+ break;
+ }
+ }
+}
+
+void IndexRecord::WalkFields(FieldsWalker callback, void *context)
+{
+ if (callback)
+ {
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ if (!callback(this, *itr, context))
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/nde/win/IndexRecord.h b/Src/nde/win/IndexRecord.h
new file mode 100644
index 00000000..97a73bd5
--- /dev/null
+++ b/Src/nde/win/IndexRecord.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <stdio.h>
+#include "Record.h"
+
+class IndexRecord : public RecordBase
+{
+public:
+ IndexRecord(int RecordPos, int insertionPoint, VFILE *FileHandle,Table *p);
+ void BuildCollaboration();
+ bool NeedFix();
+ IndexField *GetIndexByName(const wchar_t *name);
+ int GetColumnCount() { return (int)(Fields.size() - 1); }
+ bool CheckIndexing(int v);
+ int WriteFields(Table *ParentTable);
+ int WriteIndex(Table *ParentTable);
+ typedef bool (*FieldsWalker)(IndexRecord *record, Field *entry, void *context);
+ void WalkFields(FieldsWalker callback, void *context);
+}; \ No newline at end of file
diff --git a/Src/nde/win/IntegerField.cpp b/Src/nde/win/IntegerField.cpp
new file mode 100644
index 00000000..9a9830d3
--- /dev/null
+++ b/Src/nde/win/IntegerField.cpp
@@ -0,0 +1,1011 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ IntegerField Class
+ Windows implementation
+
+Field data layout:
+[4 bytes] value
+ --------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "Query.h"
+#include <time.h>
+#include <malloc.h>
+
+//---------------------------------------------------------------------------
+IntegerField::IntegerField(int Val)
+{
+ InitField();
+ Type = FIELD_INTEGER;
+ Value = Val;
+}
+
+//---------------------------------------------------------------------------
+void IntegerField::InitField(void)
+{
+ Type = FIELD_INTEGER;
+ Value=0;
+}
+
+//---------------------------------------------------------------------------
+IntegerField::IntegerField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+IntegerField::~IntegerField()
+{
+}
+
+//---------------------------------------------------------------------------
+void IntegerField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ CHECK_INT(len);
+ Value = *((int *)data);
+}
+
+//---------------------------------------------------------------------------
+void IntegerField::WriteTypedData(uint8_t *data, size_t len)
+{
+ CHECK_INT(len);
+ *((int *)data) = Value;
+}
+
+//---------------------------------------------------------------------------
+int IntegerField::GetValue(void)
+{
+ return Value;
+}
+
+//---------------------------------------------------------------------------
+void IntegerField::SetValue(int Val)
+{
+ Value = Val;
+}
+
+#include <limits.h>
+//---------------------------------------------------------------------------
+size_t IntegerField::GetDataSize(void)
+{
+ return 4;
+}
+
+//---------------------------------------------------------------------------
+int IntegerField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ return GetValue() < ((IntegerField*)Entry)->GetValue() ? -1 : (GetValue() > ((IntegerField*)Entry)->GetValue() ? 1 : 0);
+}
+
+//---------------------------------------------------------------------------
+bool IntegerField::ApplyFilter(Field *Data, int op)
+{
+ bool r;
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = Value == ((IntegerField *)Data)->GetValue();
+ break;
+ case FILTER_NOTEQUALS:
+ r = Value != ((IntegerField *)Data)->GetValue();
+ break;
+ case FILTER_NOTCONTAINS:
+ r = (bool)!(Value & ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_CONTAINS:
+ r = !!(Value & ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_ABOVE:
+ r = (bool)(Value > ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_BELOW:
+ r = (bool)(Value < ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (bool)(Value <= ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (bool)(Value >= ((IntegerField *)Data)->GetValue());
+ break;
+ case FILTER_ISEMPTY:
+ r = (Value == 0 || Value == -1);
+ break;
+ case FILTER_ISNOTEMPTY:
+ r = !(Value == 0 || Value == -1);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+}
+
+//---------------------------------------------------------------------------
+typedef struct {
+ const wchar_t *token;
+ int tid;
+} tokenstruct;
+
+enum {
+ TOKEN_AGO = 128,
+ TOKEN_NOW,
+ TOKEN_YESTERDAY,
+ TOKEN_TOMORROW,
+ TOKEN_TODAY,
+ TOKEN_OF,
+ TOKEN_THE,
+ TOKEN_DATE,
+ TOKEN_FROM,
+ TOKEN_BEFORE,
+ TOKEN_AFTER,
+ TOKEN_THIS,
+ TOKEN_SUNDAY,
+ TOKEN_MONDAY,
+ TOKEN_TUESDAY,
+ TOKEN_WEDNESDAY,
+ TOKEN_THURSDAY,
+ TOKEN_FRIDAY,
+ TOKEN_SATURDAY,
+
+ TOKEN_MIDNIGHT,
+ TOKEN_NOON,
+
+ TOKEN_AM,
+ TOKEN_PM,
+
+ TOKEN_JANUARY,
+ TOKEN_FEBRUARY,
+ TOKEN_MARCH,
+ TOKEN_APRIL,
+ TOKEN_MAY,
+ TOKEN_JUNE,
+ TOKEN_JULY,
+ TOKEN_AUGUST,
+ TOKEN_SEPTEMBER,
+ TOKEN_OCTOBER,
+ TOKEN_NOVEMBER,
+ TOKEN_DECEMBER,
+
+ TOKEN_TIME,
+ TOKEN_SECOND,
+ TOKEN_MINUTE,
+ TOKEN_HOUR,
+ TOKEN_DAY,
+ TOKEN_WEEK,
+ TOKEN_MONTH,
+ TOKEN_YEAR,
+ TOKEN_AT,
+};
+
+tokenstruct Int_Tokens[] = { // Feel free to add more...
+ {L"ago", TOKEN_AGO},
+ {L"now", TOKEN_NOW},
+ {L"am", TOKEN_AM},
+ {L"pm", TOKEN_PM},
+ {L"this", TOKEN_THIS},
+ {L"date", TOKEN_DATE},
+ {L"time", TOKEN_TIME},
+ {L"of", TOKEN_OF},
+ {L"at", TOKEN_AT},
+ {L"the", TOKEN_THE},
+ {L"yesterday", TOKEN_YESTERDAY},
+ {L"tomorrow", TOKEN_TOMORROW},
+ {L"today", TOKEN_TODAY},
+ {L"from", TOKEN_FROM},
+ {L"before", TOKEN_BEFORE},
+ {L"after", TOKEN_AFTER},
+ {L"past", TOKEN_AFTER},
+ {L"monday", TOKEN_MONDAY},
+ {L"mon", TOKEN_MONDAY},
+ {L"tuesday", TOKEN_TUESDAY},
+ {L"tue", TOKEN_TUESDAY},
+ {L"wednesday", TOKEN_WEDNESDAY},
+ {L"wed", TOKEN_WEDNESDAY},
+ {L"thursday", TOKEN_THURSDAY},
+ {L"thu", TOKEN_THURSDAY},
+ {L"friday", TOKEN_FRIDAY},
+ {L"fri", TOKEN_FRIDAY},
+ {L"saturday", TOKEN_SATURDAY},
+ {L"sat", TOKEN_SATURDAY},
+ {L"sunday", TOKEN_SUNDAY},
+ {L"sun", TOKEN_SUNDAY},
+ {L"midnight", TOKEN_MIDNIGHT},
+ {L"noon", TOKEN_NOON},
+ {L"second", TOKEN_SECOND},
+ {L"seconds", TOKEN_SECOND},
+ {L"sec", TOKEN_SECOND},
+ {L"s", TOKEN_SECOND},
+ {L"minute", TOKEN_MINUTE},
+ {L"minutes", TOKEN_MINUTE},
+ {L"min", TOKEN_MINUTE},
+ {L"mn", TOKEN_MINUTE},
+ {L"m", TOKEN_MINUTE},
+ {L"hour", TOKEN_HOUR},
+ {L"hours", TOKEN_HOUR},
+ {L"h", TOKEN_HOUR},
+ {L"day", TOKEN_DAY},
+ {L"days", TOKEN_DAY},
+ {L"d", TOKEN_DAY},
+ {L"week", TOKEN_WEEK},
+ {L"weeks", TOKEN_WEEK},
+ {L"w", TOKEN_WEEK},
+ {L"month", TOKEN_MONTH},
+ {L"months", TOKEN_MONTH},
+ {L"year", TOKEN_YEAR},
+ {L"years", TOKEN_YEAR},
+ {L"y", TOKEN_YEAR},
+ {L"january", TOKEN_JANUARY},
+ {L"jan", TOKEN_JANUARY},
+ {L"february", TOKEN_FEBRUARY},
+ {L"feb", TOKEN_FEBRUARY},
+ {L"march", TOKEN_MARCH},
+ {L"mar", TOKEN_MARCH},
+ {L"april", TOKEN_APRIL},
+ {L"apr", TOKEN_APRIL},
+ {L"may", TOKEN_MAY},
+ {L"june", TOKEN_JUNE},
+ {L"jun", TOKEN_JUNE},
+ {L"july", TOKEN_JULY},
+ {L"jul", TOKEN_JULY},
+ {L"august", TOKEN_AUGUST},
+ {L"aug", TOKEN_AUGUST},
+ {L"september", TOKEN_SEPTEMBER},
+ {L"sep", TOKEN_SEPTEMBER},
+ {L"october", TOKEN_OCTOBER},
+ {L"oct", TOKEN_OCTOBER},
+ {L"november", TOKEN_NOVEMBER},
+ {L"nov", TOKEN_NOVEMBER},
+ {L"december", TOKEN_DECEMBER},
+ {L"dec", TOKEN_DECEMBER},
+};
+
+//---------------------------------------------------------------------------
+int IntegerField::LookupToken(const wchar_t *t) {
+ for (int i=0;i<sizeof(Int_Tokens)/sizeof(tokenstruct);i++) {
+ if (!_wcsicmp(Int_Tokens[i].token, t))
+ return Int_Tokens[i].tid;
+ }
+ return TOKEN_IDENTIFIER;
+}
+
+static int myatoi(const wchar_t *p, int len) {
+ wchar_t *w = (wchar_t *)_malloca((len+1)*sizeof(wchar_t));
+ wcsncpy(w, p, len);
+ w[len] = 0;
+ int a = (w ? wcstol(w, 0, 10) : 0);
+ _freea(w);
+ return a;
+}
+
+static int isallnum(const wchar_t *p)
+{
+ while (p && *p) {
+ if (*p < L'0' || *p > L'9') return 0;
+ p++;
+ }
+ return 1;
+}
+
+//---------------------------------------------------------------------------
+int IntegerField::ApplyConversion(const wchar_t *format, TimeParse *tp) {
+ int size;
+
+ int value = GetValue();
+ wchar_t *token = 0;
+ bool ago = false;
+ bool from = false;
+ bool kthis = false;
+ int what = TOKEN_MINUTE;
+
+ int lastnumber = value;
+
+ if (tp) {
+ tp->is_relative = 0;
+ tp->offset_value = 0;
+ tp->offset_whence = -1;
+ tp->offset_what = -1;
+ tp->offset_used = 0;
+ tp->relative_year = -1;
+ tp->relative_month = -1;
+ tp->relative_day = -1;
+ tp->relative_hour = -1;
+ tp->relative_min = -1;
+ tp->relative_sec = -1;
+ tp->relative_kwday = -1;
+ tp->absolute_hastime = 0;
+ tp->absolute_hasdate = 0;
+ }
+
+ time_t now;
+ time(&now);
+
+ struct tm *o = localtime(&now);
+ struct tm origin = *o;
+ struct tm origin_flags = {0,0,0,0,0,0,0,0,0};
+
+ struct tm onow = *o;
+
+ const wchar_t *p = format;
+ int t = -1;
+ int lastt = -1;
+
+ origin.tm_isdst = -1;
+
+ while (1) {
+ int save_lastt = lastt;
+ lastt = t;
+ t = Scanner::Query_GetNextToken(p, &size, &token, 1);
+ if (t == TOKEN_EOQ) break;
+
+ switch (t) {
+ case TOKEN_THIS:
+ kthis = true;
+ break;
+ case TOKEN_AGO:
+ case TOKEN_BEFORE: // before defaults to before now (= ago)
+ ago = true;
+ if (tp) {
+ tp->is_relative = 1;
+ tp->offset_whence = 1;
+ tp->offset_used = 1;
+ }
+ break;
+ case TOKEN_AFTER: // if after, ago is discarded, coz 5 mn ago after x has no meaning, so we get it as 5 mn after x
+ ago = false;
+ // no break
+ case TOKEN_FROM:
+ from = true;
+ if (tp) {
+ tp->is_relative = 1;
+ tp->offset_whence = 0;
+ tp->offset_used = 1;
+ }
+ break;
+ case TOKEN_DATE: {
+ if (!kthis) break;
+ kthis = false;
+ origin.tm_year = onow.tm_year;
+ origin_flags.tm_year = 1;
+ origin.tm_mon = onow.tm_mon;
+ origin_flags.tm_mon = 1;
+ origin.tm_mday = onow.tm_mday - onow.tm_wday;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) {
+ tp->relative_year = -1;
+ tp->relative_month = -1;
+ tp->relative_day = -1;
+ }
+ break;
+ }
+ case TOKEN_TIME: {
+ if (!kthis) break;
+ kthis = false;
+ origin.tm_hour = onow.tm_hour;
+ origin_flags.tm_hour = 1;
+ origin.tm_min = onow.tm_min;
+ origin_flags.tm_min = 1;
+ origin.tm_sec = onow.tm_sec;
+ origin_flags.tm_sec = 1;
+ if (tp) {
+ tp->relative_sec = -1;
+ tp->relative_min = -1;
+ tp->relative_hour = -1;
+ }
+ break;
+ }
+ case TOKEN_SECOND:
+ case TOKEN_MINUTE:
+ case TOKEN_HOUR:
+ case TOKEN_DAY:
+ case TOKEN_WEEK:
+ case TOKEN_MONTH:
+ case TOKEN_YEAR:
+ if (kthis) {
+ kthis = false;
+ switch (t) {
+ case TOKEN_SECOND:
+ origin.tm_sec = onow.tm_sec;
+ origin_flags.tm_sec = 1;
+ if (tp) tp->relative_sec = -1;
+ break;
+ case TOKEN_MINUTE:
+ origin.tm_min = onow.tm_min;
+ origin_flags.tm_min = 1;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_min = -1;
+ break;
+ case TOKEN_HOUR:
+ origin.tm_hour = onow.tm_hour;
+ origin_flags.tm_hour = 1;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_hour = -1;
+ break;
+ case TOKEN_DAY:
+ origin.tm_mday = onow.tm_mday;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_day = -1;
+ break;
+ case TOKEN_WEEK:
+ origin.tm_mday = onow.tm_mday - onow.tm_wday;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_day = -2;
+ break;
+ case TOKEN_MONTH:
+ origin.tm_mon = onow.tm_mon;
+ origin_flags.tm_mon = 1;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_month = -1;
+ break;
+ case TOKEN_YEAR:
+ origin.tm_year = onow.tm_year;
+ origin_flags.tm_year = 1;
+ if (!origin_flags.tm_mon)
+ origin.tm_mon = 0;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_year = -1;
+ break;
+ }
+ break;
+ }
+ if (lastnumber > 0) {
+ value = lastnumber;
+ lastnumber = 0;
+ if (tp) tp->offset_value = value;
+ }
+ what = t;
+ if (tp) {
+ switch (what) {
+ case TOKEN_SECOND:
+ tp->offset_what = 6; break;
+ case TOKEN_MINUTE:
+ tp->offset_what = 5; break;
+ case TOKEN_HOUR:
+ tp->offset_what = 4; break;
+ case TOKEN_DAY:
+ tp->offset_what = 3; break;
+ case TOKEN_WEEK:
+ tp->offset_what = 2; break;
+ case TOKEN_MONTH:
+ tp->offset_what = 1; break;
+ case TOKEN_YEAR:
+ tp->offset_what = 0; break;
+ }
+ }
+ break;
+ case TOKEN_SUNDAY:
+ case TOKEN_MONDAY:
+ case TOKEN_TUESDAY:
+ case TOKEN_WEDNESDAY:
+ case TOKEN_THURSDAY:
+ case TOKEN_FRIDAY:
+ case TOKEN_SATURDAY: {
+ kthis = false;
+ int dow = t-TOKEN_MONDAY;
+ if (dow > onow.tm_mday)
+ origin.tm_mday = 7 - (dow - onow.tm_mday);
+ else
+ origin.tm_mday = dow;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ }
+ if (tp) tp->relative_kwday = t-TOKEN_SUNDAY;
+ break;
+ case TOKEN_MIDNIGHT:
+ kthis = false;
+ origin.tm_hour = 0;
+ origin_flags.tm_hour = 1;
+ if (!origin_flags.tm_min) {
+ if (tp) tp->relative_min = 0;
+ origin.tm_min = 0;
+ origin_flags.tm_min = 1;
+ }
+ if (!origin_flags.tm_sec) {
+ if (tp) tp->relative_sec = 0;
+ origin.tm_sec = 0;
+ origin_flags.tm_sec = 1;
+ }
+ if (tp) tp->relative_hour = 0;
+ break;
+ case TOKEN_NOON:
+ kthis = false;
+ origin.tm_hour = 12;
+ origin_flags.tm_hour = 1;
+ if (!origin_flags.tm_min) {
+ if (tp) tp->relative_min = 0;
+ origin.tm_min = 0;
+ origin_flags.tm_min = 1;
+ }
+ if (!origin_flags.tm_sec) {
+ if (tp) tp->relative_sec = 0;
+ origin.tm_sec = 0;
+ origin_flags.tm_sec = 1;
+ }
+ if (tp) tp->relative_hour = 12;
+ break;
+ case TOKEN_AM:
+ kthis = false;
+ if (lastnumber > 0) {
+ origin.tm_hour = lastnumber;
+ if (!origin_flags.tm_min) {
+ if (tp) tp->relative_min = 0;
+ origin.tm_min = 0;
+ origin_flags.tm_min = 1;
+ }
+ if (!origin_flags.tm_sec) {
+ if (tp) tp->relative_sec = 0;
+ origin.tm_sec = 0;
+ origin_flags.tm_sec = 1;
+ }
+ if (tp) tp->relative_hour = lastnumber;
+ lastnumber = 0;
+ } else {
+ if (origin.tm_hour > 12) origin.tm_hour -= 12;
+ if (tp) tp->relative_hour = origin.tm_hour;
+ }
+ origin_flags.tm_hour = 1;
+ break;
+ case TOKEN_PM:
+ kthis = false;
+ if (lastnumber > 0) {
+ origin.tm_hour = lastnumber > 12 ? lastnumber : lastnumber + 12;
+ if (!origin_flags.tm_min) {
+ if (tp) tp->relative_min = 0;
+ origin.tm_min = 0;
+ origin_flags.tm_min = 1;
+ }
+ if (!origin_flags.tm_sec) {
+ if (tp) tp->relative_sec = 0;
+ origin.tm_sec = 0;
+ origin_flags.tm_sec = 1;
+ }
+ if (tp) tp->relative_hour = lastnumber;
+ lastnumber = 0;
+ } else {
+ if (origin.tm_hour <= 12) origin.tm_hour += 12;
+ if (tp) tp->relative_hour = origin.tm_hour;
+ }
+ origin_flags.tm_hour = 1;
+ break;
+ case TOKEN_NOW:
+ kthis = false;
+ if (!origin_flags.tm_year) {
+ if (tp) tp->relative_year = -1;
+ origin.tm_year = onow.tm_year;
+ }
+ origin_flags.tm_year = 1;
+ if (!origin_flags.tm_mon) {
+ if (tp) tp->relative_month = -1;
+ origin.tm_mon = onow.tm_mon;
+ }
+ origin_flags.tm_mon = 1;
+ if (!origin_flags.tm_mday) {
+ if (tp) tp->relative_day = -1;
+ origin.tm_mday = onow.tm_mday;
+ }
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour) {
+ if (tp) tp->relative_hour = -1;
+ origin.tm_hour = onow.tm_hour;
+ }
+ origin_flags.tm_hour = 1;
+ if (!origin_flags.tm_min) {
+ if (tp) tp->relative_min = -1;
+ origin.tm_min = onow.tm_min;
+ }
+ origin_flags.tm_min = 1;
+ if (!origin_flags.tm_sec) {
+ if (tp) tp->relative_sec = -1;
+ origin.tm_sec = onow.tm_sec;
+ }
+ break;
+ case TOKEN_YESTERDAY:
+ kthis = false;
+ origin.tm_mday = onow.tm_mday - 1;
+ origin_flags.tm_mday = 1;
+ if (tp) tp->relative_kwday = 7;
+ break;
+ case TOKEN_TODAY:
+ origin.tm_mday = onow.tm_mday;
+ origin_flags.tm_mday = 1;
+ if (tp) tp->relative_kwday = 8;
+ break;
+ case TOKEN_TOMORROW:
+ kthis = false;
+ origin.tm_mday = onow.tm_mday + 1;
+ origin_flags.tm_mday = 1;
+ if (tp) tp->relative_kwday = 9;
+ break;
+ case TOKEN_JANUARY:
+ case TOKEN_FEBRUARY:
+ case TOKEN_MARCH:
+ case TOKEN_APRIL:
+ case TOKEN_MAY:
+ case TOKEN_JUNE:
+ case TOKEN_JULY:
+ case TOKEN_AUGUST:
+ case TOKEN_SEPTEMBER:
+ case TOKEN_OCTOBER:
+ case TOKEN_NOVEMBER:
+ case TOKEN_DECEMBER:
+ kthis = false;
+ if (lastnumber > 0) {
+ origin.tm_mday = lastnumber;
+ origin_flags.tm_mday = 1;
+ lastnumber = 0;
+ }
+ origin.tm_mon = t-TOKEN_JANUARY;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ origin_flags.tm_mon = 1;
+ if (tp) tp->relative_month = t-TOKEN_JANUARY;
+ break;
+ case TOKEN_IDENTIFIER:
+ {
+ kthis = false;
+
+ // check for a year value
+ int i = wcstol(token,0,10);
+ if (i > 1970 && i < 2038 && isallnum(token)) { // max time_t range
+ origin.tm_year = i-1900;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_mon)
+ origin.tm_mon = 0;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_year = i;
+ break;
+ }
+
+ // check for 1st, 2nd, 3rd, 4th, etc.
+ wchar_t *z;
+ int tokenLen=(int)wcslen(token);
+ if (tokenLen>=2)
+ {
+ z = token+tokenLen-2;
+ if (!_wcsicmp(z, L"st") || !_wcsicmp(z, L"nd") || !_wcsicmp(z, L"rd") || !_wcsicmp(z, L"th")) {
+ int j = myatoi(token, (int)(z-token));
+ if (j >= 1 && j <= 31) {
+ origin.tm_mday = j;
+ origin_flags.tm_mday = 1;
+ if (tp) tp->relative_day = j;
+ break;
+ }
+ }
+ }
+
+ // check for a time string (##:##:##)
+ z = wcschr(token, L':');
+ if (z)
+ {
+ if (tp) tp->absolute_hastime = 1;
+ wchar_t *zz = wcschr(z+1, L':');
+ int a, b = 0, c = 0;
+ a = myatoi(token, (int)(z-token));
+ if (zz && *(zz+1) == 0) zz = NULL;
+ if (zz && !isallnum(zz+1)) zz = NULL;
+ if (zz) { b = myatoi(z+1, (int)(zz-(z+1))); c = wcstol(zz+1,0,10); }
+ else b = wcstol(z+1,0,10);
+ origin.tm_hour = a;
+ origin.tm_min = b;
+ if (tp) {
+ tp->relative_hour = a;
+ tp->relative_min = b;
+ }
+ if (zz && !origin_flags.tm_sec) {
+ origin.tm_sec = c;
+ if (tp) tp->relative_sec = c;
+ } else if (!origin_flags.tm_sec) {
+ origin.tm_sec = 0;
+ }
+ origin_flags.tm_sec = 1;
+ origin_flags.tm_hour = 1;
+ origin_flags.tm_min = 1;
+ break;
+ }
+
+ // check for a date string in the format ##/##/##
+ z = wcschr(token, L'/');
+ if (z) {
+ if (tp) tp->absolute_hasdate = 1;
+ wchar_t *zz = wcschr(z+1, L'/');
+ int a, b = 0, c = onow.tm_year;
+ a = myatoi(token, (int)(z-token));
+ if (zz && !isallnum(zz+1)) zz = NULL;
+ if (zz && *(zz+1) == 0) zz = NULL;
+ if (zz) { b = myatoi(z+1, (int)(zz-(z+1))); c = wcstol(zz+1,0,10); }
+ else b = _wtoi(z+1);
+ if (b > 1969 && b < 2038) {
+ // mm/yyyy
+ origin.tm_year = b-1900;
+ origin_flags.tm_year = 1;
+ origin.tm_mon = a-1;
+ origin_flags.tm_mon = 1;
+ if (!origin_flags.tm_mday)
+ origin.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) {
+ tp->relative_year = b;
+ tp->relative_month = a-1;
+ }
+ } else {
+ // mm/dd(/yy[yy])
+ if (c < 70) c += 100;
+ if (c > 138) c -= 1900;
+ origin.tm_year = c;
+ origin.tm_mon = a-1;
+ origin.tm_mday = b == 0 ? 1 : b;
+ origin_flags.tm_year = 1;
+ origin_flags.tm_mon = 1;
+ origin_flags.tm_mday = 1;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) {
+ tp->relative_year = c+1900;
+ tp->relative_month = a-1;
+ tp->relative_day = b;
+ }
+ }
+ origin_flags.tm_year = 1;
+ origin_flags.tm_mon = 1;
+ origin_flags.tm_mday = 1;
+ break;
+ }
+
+ if (isallnum(token))
+ {
+ lastnumber = i;
+ switch (lastt) {
+ case TOKEN_JANUARY:
+ case TOKEN_FEBRUARY:
+ case TOKEN_MARCH:
+ case TOKEN_APRIL:
+ case TOKEN_MAY:
+ case TOKEN_JUNE:
+ case TOKEN_JULY:
+ case TOKEN_AUGUST:
+ case TOKEN_SEPTEMBER:
+ case TOKEN_OCTOBER:
+ case TOKEN_NOVEMBER:
+ case TOKEN_DECEMBER:
+ origin.tm_mday = lastnumber;
+ origin_flags.tm_mday = 1;
+ lastnumber = 0;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_day = lastnumber;
+ break;
+ case TOKEN_AT: {
+ origin.tm_hour = lastnumber;
+ origin.tm_min = 0;
+ origin.tm_sec = 0;
+ origin_flags.tm_hour = 1;
+ origin_flags.tm_min = 1;
+ origin_flags.tm_sec = 1;
+ if (tp) {
+ tp->relative_hour = lastnumber;
+ tp->relative_min = 0;
+ tp->relative_sec = 0;
+ }
+ lastnumber = 0;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ lastt = save_lastt;
+ break;
+ }
+ p += size;
+ }
+
+ if (lastnumber) {
+ switch (lastt) {
+ case TOKEN_JANUARY:
+ case TOKEN_FEBRUARY:
+ case TOKEN_MARCH:
+ case TOKEN_APRIL:
+ case TOKEN_MAY:
+ case TOKEN_JUNE:
+ case TOKEN_JULY:
+ case TOKEN_AUGUST:
+ case TOKEN_SEPTEMBER:
+ case TOKEN_OCTOBER:
+ case TOKEN_NOVEMBER:
+ case TOKEN_DECEMBER:
+ origin.tm_mday = lastnumber;
+ lastnumber = 0;
+ if (!origin_flags.tm_hour)
+ origin.tm_hour = 0;
+ if (!origin_flags.tm_min)
+ origin.tm_min = 0;
+ if (!origin_flags.tm_sec)
+ origin.tm_sec = 0;
+ if (tp) tp->relative_day = lastnumber;
+ break;
+ }
+ }
+
+ if (ago) { // if ago (or before), from is optional since if it wasn't specified we use now
+ switch (what) {
+ case TOKEN_SECOND:
+ origin.tm_sec -= value;
+ break;
+ case TOKEN_MINUTE:
+ origin.tm_min -= value;
+ break;
+ case TOKEN_HOUR:
+ origin.tm_hour -= value;
+ break;
+ case TOKEN_DAY:
+ origin.tm_mday -= value;
+ break;
+ case TOKEN_WEEK:
+ origin.tm_mday -= value*7;
+ break;
+ case TOKEN_MONTH:
+ origin.tm_mon -= value;
+ break;
+ case TOKEN_YEAR:
+ origin.tm_year -= value;
+ break;
+ }
+ time_t o = mktime(&origin);
+ SetValue((int)o);
+ ndestring_release(token);
+
+ if (tp) tp->absolute_datetime = GetValue();
+ return 1;
+ } else if (from) { // from (or after) was specified, but not ago, 5 mn from x is x + 5 mn
+ switch (what) {
+ case TOKEN_SECOND:
+ origin.tm_sec += value;
+ break;
+ case TOKEN_MINUTE:
+ origin.tm_min += value;
+ break;
+ case TOKEN_HOUR:
+ origin.tm_hour += value;
+ break;
+ case TOKEN_DAY:
+ origin.tm_mday += value;
+ break;
+ case TOKEN_WEEK:
+ origin.tm_mday += value*7;
+ break;
+ case TOKEN_MONTH:
+ origin.tm_mon += value;
+ break;
+ case TOKEN_YEAR:
+ origin.tm_year += value;
+ break;
+ }
+ time_t o = mktime(&origin);
+ SetValue((int)o);
+
+ ndestring_release(token);
+
+ if (tp) tp->absolute_datetime = GetValue();
+ return 1;
+ } else { // none of ago/from/before/after were specified, just make a date/time with what we got and ignore our old value
+ time_t o = mktime(&origin);
+ SetValue((int)o);
+
+ ndestring_release(token);
+
+ if (tp) tp->absolute_datetime = GetValue();
+ return 1;
+ }
+ ndestring_release(token);
+
+ if (tp) tp->absolute_datetime = GetValue();
+
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+DateTimeField::DateTimeField(int Val) : IntegerField(Val)
+{
+ Type = FIELD_DATETIME;
+}
+
+//---------------------------------------------------------------------------
+DateTimeField::DateTimeField()
+{
+ Type = FIELD_DATETIME;
+}
+
+//---------------------------------------------------------------------------
+DateTimeField::~DateTimeField()
+{
+}
+
+//---------------------------------------------------------------------------
+LengthField::LengthField(int Val) : IntegerField(Val)
+{
+ Type = FIELD_LENGTH;
+}
+
+//---------------------------------------------------------------------------
+LengthField::LengthField()
+{
+ Type = FIELD_LENGTH;
+}
+
+//---------------------------------------------------------------------------
+LengthField::~LengthField()
+{
+}
diff --git a/Src/nde/win/IntegerField.h b/Src/nde/win/IntegerField.h
new file mode 100644
index 00000000..022932ab
--- /dev/null
+++ b/Src/nde/win/IntegerField.h
@@ -0,0 +1,95 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ IntegerField Class Prototypes
+
+--------------------------------------------------------------------------- */
+
+#ifndef __INTEGERFIELD_H
+#define __INTEGERFIELD_H
+
+#include <bfc/platform/types.h>
+
+class TimeParse {
+public:
+ int is_relative; // ago/after/before used
+ int absolute_datetime; // if not is_relative, this is the date/time, otherwise it's the resulting date/time if the query was ran now
+ int absolute_hasdate; // if not, use only the time portion of absolute_datetime, the rest is now
+ int absolute_hastime; // if not, use only the date portion of absolute_datetime, the rest is now
+ int relative_year; // -1 = this year
+ int relative_month; // -1 = this month
+ int relative_day; // -1 = this day, -2 = this week
+ int relative_hour; // -1 = this hour
+ int relative_min; // -1 = this minute
+ int relative_sec; // -1 = this second
+ int relative_kwday; // not used(-1), 0 to 6 for sunday to saturday, yesterday(7), today(8), tomorrow(9)
+ int offset_value; // timeago/offsetby numerical edit field
+ int offset_what; // not used(-1) years(0), months(1), weeks(2), days(3), hours(4), minutes(5), seconds(6)
+ int offset_whence; // not used(-1), after(0), ago/before(1)
+ int offset_used; // if 1, 'time ago' / 'offset by' should be checked
+};
+
+class IntegerField : public Field
+ {
+ protected:
+
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ virtual bool ApplyFilter(Field *Data, int op);
+ void InitField(void);
+ int Value;
+
+ enum {
+ WHAT_YEARS,
+ WHAT_MONTHS,
+ WHAT_WEEKS,
+ WHAT_DAYS,
+ WHAT_HOURS,
+ WHAT_MINUTES,
+ WHAT_SECONDS,
+ };
+
+ enum {
+ FROM_BARE,
+ FROM_AGO,
+ FROM_SINCE,
+ };
+
+ public:
+
+ ~IntegerField();
+ IntegerField(int);
+ IntegerField();
+ int GetValue(void);
+ void SetValue(int);
+ int ApplyConversion(const wchar_t *format, TimeParse *tp=NULL);
+#ifdef _WIN32
+ static int LookupToken(const wchar_t *t);
+#else
+ static int LookupToken(CFStringRef t);
+#endif
+ };
+
+
+class DateTimeField : public IntegerField {
+ public:
+ DateTimeField();
+ DateTimeField(int Val);
+ virtual ~DateTimeField();
+};
+
+class LengthField : public IntegerField {
+ public:
+ LengthField();
+ LengthField(int Val);
+ virtual ~LengthField();
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/Query.cpp b/Src/nde/win/Query.cpp
new file mode 100644
index 00000000..54c9e6dd
--- /dev/null
+++ b/Src/nde/win/Query.cpp
@@ -0,0 +1,961 @@
+#include "../nde.h"
+#include "../NDEString.h"
+#include "Query.h"
+
+//---------------------------------------------------------------------------
+
+BOOL Scanner::Query(const wchar_t *query)
+{
+ if (!query) return FALSE;
+ ndestring_release(last_query);
+ last_query = ndestring_wcsdup(query);
+ RemoveFilters();
+ in_query_parser = 1;
+
+ BOOL r = Query_Parse(query);
+ if (r == FALSE)
+ {
+ if (!disable_date_resolution) RemoveFilters();
+ last_query_failed = TRUE;
+ }
+ in_query_parser = 0;
+ Query_CleanUp();
+ return r & CheckFilters();
+}
+
+const wchar_t *Scanner::GetLastQuery()
+{
+ return last_query;
+}
+
+typedef struct
+{
+ const wchar_t *token;
+ int tid;
+} tokenstruct;
+
+tokenstruct Tokens[] = // Feel free to add more...
+{
+ {L"AND", TOKEN_AND },
+ {L"OR", TOKEN_OR },
+ {L"HAS", TOKEN_CONTAINS },
+ {L"NOTHAS",TOKEN_NOTCONTAINS},
+ {L"BEGINS", TOKEN_BEGINS },
+ {L"ENDS", TOKEN_ENDS },
+ {L"ISEMPTY", TOKEN_ISEMPTY},
+ {L"ISNOTEMPTY",TOKEN_ISNOTEMPTY},
+ {L"LIKE", TOKEN_LIKE},
+ {L"BEGINSLIKE", TOKEN_BEGINSLIKE},
+};
+
+typedef struct
+{
+ int Op;
+ int Level;
+} OpLevel;
+
+static int Query_ParseLength(const wchar_t *str)
+{
+ int i = (str ? _wtoi(str) : 0);
+
+ const wchar_t *p;
+ if ((p=wcsstr(str,L":")))
+ {
+ i*=60;
+ i+=_wtoi(++p);
+ if ((p=wcsstr(p,L":")))
+ {
+ i*=60;
+ i+=_wtoi(++p);
+ }
+ }
+ return i;
+}
+
+/*
+ our state machine
+
+ &, |
+ ----------<-------------------------<----------------------<-----------
+ | |
+ v ID (Col) =, >, <... ID / Data / [f] ) |
+ ---->(0) ----->-----> (1) ------>-----> (2) ------>------> (3) ------>-----> (4) <--
+ | |^ \isempty------------->------------/ |^ | |
+ | !( || ||---- | ) |
+ --<-- ---------<---------------------------<-------------<-| | -->--
+ &, | v [f] |
+ -->--
+*/
+
+//---------------------------------------------------------------------------
+BOOL Scanner::Query_Parse(const wchar_t *query)
+{
+ const wchar_t *p = query; // pointer on next token to read
+ int state = 0;
+ int pcount = 0;
+ VListEntry<OpLevel> *entry = 0;
+
+ if (pstack.GetNElements() > 0)
+ Query_CleanUp();
+
+ while (1)
+ {
+ p = Query_EatSpace(p);
+ int size = 0, t = Query_GetNextToken(p, &size, &token);
+ if (t == TOKEN_UNKNOWN)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ if (t == TOKEN_EOQ)
+ break;
+ switch (state)
+ {
+ case 0:
+ switch (t)
+ {
+ case TOKEN_PAROPEN:
+ state = 0;
+ // check too many parenthesis open
+ if (pcount == 255)
+ {
+ Query_SyntaxError((int)(p-query)); // should not be _syntax_ error
+ return FALSE;
+ }
+ // up one level
+ pcount++;
+ break;
+ case TOKEN_NOT:
+ // push not in this level
+ OpLevel o;
+ o.Op = FILTER_NOT;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, TRUE);
+ state = 0;
+ break;
+ case TOKEN_IDENTIFIER:
+ state = 1;
+ // create filter column
+
+ if (AddFilterByName(token, NULL, FILTER_NONE) == ADDFILTER_FAILED)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ break;
+ case 1:
+ switch (t)
+ {
+ case TOKEN_EQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_EQUALS);
+ break;
+ }
+ case TOKEN_ABOVE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ABOVE);
+ break;
+ }
+ case TOKEN_BELOW:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BELOW);
+ break;
+ }
+ case TOKEN_CONTAINS:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_CONTAINS);
+ break;
+ }
+ case TOKEN_NOTCONTAINS:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_NOTCONTAINS);
+ break;
+ }
+ case TOKEN_AOREQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ABOVEOREQUAL);
+ break;
+ }
+ case TOKEN_BOREQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BELOWOREQUAL);
+ break;
+ }
+ case TOKEN_NOTEQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_NOTEQUALS);
+ break;
+ }
+ case TOKEN_BEGINS:
+ {
+ state = 2;
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BEGINS);
+ }
+ break;
+ case TOKEN_ENDS:
+ {
+ state = 2;
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ENDS);
+ }
+ break;
+ case TOKEN_LIKE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_LIKE);
+ break;
+ }
+ case TOKEN_BEGINSLIKE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BEGINSLIKE);
+ break;
+ }
+ case TOKEN_ISNOTEMPTY:
+ case TOKEN_ISEMPTY:
+ {
+ state = 3;
+ Filter *f = GetLastFilter();
+ f->SetOp(t==TOKEN_ISEMPTY ? FILTER_ISEMPTY : FILTER_ISNOTEMPTY);
+ // pop all operators in this level beginning by the last inserted
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ break;
+ case 2:
+ if (t == TOKEN_SQBRACKETOPEN)
+ {
+ state = 3;
+ const wchar_t *s = wcschr(p, L']');
+ if (!s)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ p = Query_EatSpace(p);
+ if (p && *p == L'[') p++;
+
+ wchar_t *format = ndestring_wcsndup(p, s-p);
+
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ int tt = c ? c->GetDataType() : -1;
+ if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN))
+ {
+ if (disable_date_resolution)
+ {
+ StringField *field = (StringField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new StringField(L""));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (StringField*)f->Data();
+ }
+
+ field->SetNDEString(format);
+ ndestring_release(format);
+ p = s+1;
+ continue;
+ }
+
+ ndestring_release(format);
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+
+ IntegerField *field = (IntegerField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new IntegerField(0));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (IntegerField *)f->Data();
+ }
+ int r = field->ApplyConversion(format);
+ ndestring_release(format);
+ if (!r)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ p = s+1;
+ continue;
+ }
+ // switch (t) {
+ // case TOKEN_IDENTIFIER:
+ else // JF> we make this relaxed, so anything is valid as a value
+ {
+ state = 3;
+ // set filter data
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ switch (c ? c->GetDataType() : -1)
+ {
+ case FIELD_DATETIME:
+ if (disable_date_resolution)
+ goto field_string_override;
+ case FIELD_LENGTH:
+ {
+ IntegerField *i_f = new IntegerField();
+ int i = Query_ParseLength(token);
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_BOOLEAN:
+ case FIELD_INTEGER:
+ {
+ IntegerField *i_f = new IntegerField();
+ int i = _wtoi(token);
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_INT64:
+ {
+ Int64Field *i_f = new Int64Field();
+ int64_t i = _wtoi64(token); // todo: Replace with own conversion and error checking
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_FILENAME:
+ {
+ FilenameField *s_f = new FilenameField();
+ s_f->SetNDEString(token);
+ f->SetData(s_f);
+ }
+ break;
+ case FIELD_STRING:
+field_string_override:
+ {
+ StringField *s_f = new StringField();
+ s_f->SetNDEString(token);
+ f->SetData(s_f);
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ break;
+ }
+ // pop all operators in this level beginning by the last inserted
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ }
+ // default:
+ // Query_SyntaxError(p-query);
+ // return FALSE;
+ // }
+ break;
+ case 3:
+ switch (t)
+ {
+ case TOKEN_SQBRACKETOPEN:
+ {
+ const wchar_t *s = wcschr(p, L']');
+ if (!s)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ p = Query_EatSpace(p);
+ if (p && *p == L'[') p++;
+ wchar_t *format = ndestring_wcsndup(p, s-p);
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ int tt = c ? c->GetDataType() : -1;
+ if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN))
+ {
+ if (disable_date_resolution)
+ {
+ StringField *field = (StringField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new StringField(L""));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (StringField *)f->Data();
+ }
+
+ field->SetNDEString(format);
+ ndestring_release(format);
+ p = s+1;
+ continue;
+ }
+ ndestring_release(format);
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+
+ IntegerField *field = (IntegerField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new IntegerField(0));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (IntegerField *)f->Data();
+ }
+ int r = field->ApplyConversion(format);
+ ndestring_release(format);
+ if (!r)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ p = s+1;
+ continue;
+ }
+ break;
+
+ case TOKEN_PARCLOSE:
+ state = 4;
+ // check parenthesis count
+ if (pcount == 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ // down one level
+ pcount--;
+ // pop all operators in this level, beginning by the last inserted
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ case TOKEN_AND:
+ {
+ state = 0;
+ // push and
+ OpLevel o;
+ o.Op = FILTER_AND;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, TRUE);
+ break;
+ }
+ case TOKEN_OR:
+ {
+ state = 0;
+ // push or
+ OpLevel o;
+ o.Op = FILTER_OR;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, TRUE);
+ break;
+ }
+ default:
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ break;
+ case 4:
+ switch (t)
+ {
+ case TOKEN_AND:
+ {
+ state = 0;
+ // push and
+ OpLevel o;
+ o.Op = FILTER_AND;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, TRUE);
+ break;
+ }
+ case TOKEN_OR:
+ {
+ state = 0;
+ // push or
+ OpLevel o;
+ o.Op = FILTER_OR;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, TRUE);
+ break;
+ }
+ case TOKEN_PARCLOSE:
+ state = 4;
+ // check parenthesis count
+ if (pcount == 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ // down one level
+ pcount--;
+ // pop all operators in this level, beginning by the last inserted
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ break;
+ default:
+ // Ahem... :/
+ break;
+ }
+ p += size;
+ }
+ if (pcount > 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------
+
+void Scanner::Query_SyntaxError(int c)
+{
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Query_CleanUp()
+{
+ while (pstack.GetNElements() > 0)
+ {
+ VListEntry<int> *e;
+ e = (VListEntry<int> *)pstack.GetHead();
+ pstack.RemoveEntry(e);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+const wchar_t *Scanner::Query_EatSpace(const wchar_t *p)
+{
+ while (p && *p && *p == L' ') p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+const wchar_t *Scanner::Query_ProbeNonAlphaNum(const wchar_t *p)
+{
+ int inquote=0;
+ while (p && *p && (!Query_isControlChar(*p) || (inquote)))
+ {
+ if (*p == L'\"')
+ {
+ if (!inquote)
+ inquote = 1;
+ else
+ return p+1;
+ }
+ p++;
+ }
+ return p;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Query_isControlChar(wchar_t p)
+{
+ switch (p)
+ {
+ case L'&':
+ case L'|':
+ case L'!':
+ case L'(':
+ case L'[':
+ case L')':
+ case L']':
+ case L'>':
+ case L'<':
+ case L'=':
+ case L',':
+ case L' ':
+ return TRUE;
+ }
+ return FALSE;
+}
+
+//---------------------------------------------------------------------------
+wchar_t *Scanner::Query_ProbeAlphaNum(wchar_t *p)
+{
+ while (p && *p && Query_isControlChar(*p)) p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+wchar_t *Scanner::Query_ProbeSpace(wchar_t *p)
+{
+ while (p && *p && *p != ' ') p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Query_LookupToken(const wchar_t *t)
+{
+ for (int i=0;i<sizeof(Tokens)/sizeof(tokenstruct);i++)
+ {
+ if (!_wcsicmp(Tokens[i].token, t))
+ return Tokens[i].tid;
+ }
+ return TOKEN_IDENTIFIER;
+}
+
+//---------------------------------------------------------------------------
+
+int Scanner::Query_GetNextToken(const wchar_t *p, int *size, wchar_t **_token, int tokentable)
+{
+ int t = TOKEN_EOQ;
+ const wchar_t *startptr = p;
+
+ if (!p || !*p) return TOKEN_EOQ;
+
+ p = Query_EatSpace(p);
+
+ const wchar_t *e = Query_ProbeNonAlphaNum(p);
+
+ if (e != p) // We have a word
+ {
+ size_t token_length = e-p;
+ if (*_token) ndestring_release(*_token);
+ *_token = ndestring_wcsndup(p, token_length);
+ if (*(*_token) == L'\"' && (*_token)[token_length-1] == L'\"') // check for quoted string
+ {
+ int l=(int)token_length-2;
+ if (l>0)
+ {
+ memcpy(*_token,(*_token)+1,l*sizeof(wchar_t));
+ (*_token)[l]=0;
+ Query_Unescape(*_token);
+ }
+ else
+ (*_token)[0]=0;// we have an empty string
+ }
+
+ switch (tokentable)
+ {
+ case -1:
+ t = TOKEN_IDENTIFIER;
+ break;
+ case 0:
+ t = Query_LookupToken(*_token);
+ break;
+ case 1:
+ t = IntegerField::LookupToken(*_token);
+ }
+ p = e;
+ }
+ else // We have a symbol
+ {
+ switch (*p)
+ {
+ case L'&':
+ if (*(p+1) == L'&') p++;
+ t = TOKEN_AND;
+ break;
+ case L'|':
+ if (*(p+1) == L'|') p++;
+ t = TOKEN_OR;
+ break;
+ case L'!':
+ if (*(p+1) == L'=')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_NOT;
+ break;
+ case L'(':
+ t = TOKEN_PAROPEN;
+ break;
+ case L')':
+ t = TOKEN_PARCLOSE;
+ break;
+ case L'[':
+ t = TOKEN_SQBRACKETOPEN;
+ break;
+ case L']':
+ t = TOKEN_SQBRACKETCLOSE;
+ break;
+ case L',':
+ t = TOKEN_COMMA;
+ break;
+ case L'>':
+ if (*(p+1) == L'=')
+ {
+ p++;
+ t = TOKEN_AOREQUAL;
+ break;
+ }
+ if (*(p+1) == L'<')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_ABOVE;
+ break;
+ case L'<':
+ if (*(p+1) == L'=')
+ {
+ p++;
+ t = TOKEN_BOREQUAL;
+ break;
+ }
+ if (*(p+1) == L'>')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_BELOW;
+ break;
+ case L'=':
+ if (*(p+1) == L'>')
+ {
+ p++;
+ t = TOKEN_AOREQUAL;
+ break;
+ }
+ if (*(p+1) == L'<')
+ {
+ p++;
+ t = TOKEN_BOREQUAL;
+ break;
+ }
+ if (*(p+1) == L'!')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ if (*(p+1) == L'=') p++;
+ t = TOKEN_EQUAL;
+ break;
+ default:
+ t = TOKEN_UNKNOWN;
+ break;
+ }
+ p++;
+ }
+
+ *size = (int)(p - startptr);
+ return t;
+}
+
+static uint8_t quickhex(wchar_t c)
+{
+ int hexvalue = c;
+ if (hexvalue & 0x10)
+ hexvalue &= ~0x30;
+ else
+ {
+ hexvalue &= 0xF;
+ hexvalue += 9;
+ }
+ return hexvalue;
+}
+
+static uint8_t DecodeEscape(const wchar_t *&str)
+{
+ uint8_t a = quickhex(*++str);
+ uint8_t b = quickhex(*++str);
+ str++;
+ return a * 16 + b;
+}
+
+static void DecodeEscapedUTF8(wchar_t *&output, const wchar_t *&input)
+{
+ uint8_t utf8_data[1024] = {0}; // hopefully big enough!!
+ int num_utf8_words=0;
+ bool error=false;
+
+ while (input && *input == '%' && num_utf8_words < sizeof(utf8_data))
+ {
+ if (iswxdigit(input[1]) && iswxdigit(input[2]))
+ {
+ utf8_data[num_utf8_words++]=DecodeEscape(input);
+ }
+ else if (input[1] == '%')
+ {
+ input+=2;
+ utf8_data[num_utf8_words++]='%';
+ }
+ else
+ {
+ error = true;
+ break;
+ }
+ }
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, 0, 0);
+ MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, output, len);
+ output += len;
+
+ if (error)
+ {
+ *output++ = *input++;
+ }
+}
+
+// benski> We have the luxury of knowing that decoding will ALWAYS produce smaller strings
+// so we can do it in-place
+void Query_Unescape(wchar_t *str)
+{
+ const wchar_t *itr = str;
+ while (itr && *itr)
+ {
+ switch (*itr)
+ {
+ case '%':
+ DecodeEscapedUTF8(str, itr);
+ break;
+ default:
+ *str++ = *itr++;
+ break;
+ }
+ }
+ *str = 0;
+} \ No newline at end of file
diff --git a/Src/nde/win/Query.h b/Src/nde/win/Query.h
new file mode 100644
index 00000000..1c9e04a4
--- /dev/null
+++ b/Src/nde/win/Query.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#define TOKEN_UNKNOWN -1 // BLAAAAA!!!!!!!!
+#define TOKEN_EOQ 0 // End of Query
+#define TOKEN_IDENTIFIER 1 // Column name or data to match against
+#define TOKEN_EQUAL 2 // =, ==, IS
+#define TOKEN_NOTEQUAL 3 // !=, =!, <>, ><
+#define TOKEN_BELOW 4 // <
+#define TOKEN_ABOVE 5 // >
+#define TOKEN_BOREQUAL 6 // <=, =<
+#define TOKEN_AOREQUAL 7 // >=, =>
+#define TOKEN_NOT 8 // !, NOT
+#define TOKEN_AND 9 // &, &&, AND
+#define TOKEN_OR 10 // |, ||, OR
+#define TOKEN_PAROPEN 11 // (
+#define TOKEN_PARCLOSE 12 // )
+#define TOKEN_CONTAINS 13 // HAS
+#define TOKEN_BEGINS 14 // string starts with...
+#define TOKEN_ENDS 15 // string ends with...
+#define TOKEN_LIKE 16 // string is nearly (excluding "the " and whitespace etc)
+#define TOKEN_ISEMPTY 17 // field does not exists
+#define TOKEN_SQBRACKETOPEN 18 // [
+#define TOKEN_SQBRACKETCLOSE 19 // ]
+#define TOKEN_COMMA 20 // ,
+#define TOKEN_NOTCONTAINS 21 // NOTHAS
+#define TOKEN_ISNOTEMPTY 22 // field does not exists
+#define TOKEN_BEGINSLIKE 23 // string is nearly starts with (excluding "the " and whitespace etc)
+
+// in-place
+void Query_Unescape(wchar_t *p); \ No newline at end of file
diff --git a/Src/nde/win/Record.cpp b/Src/nde/win/Record.cpp
new file mode 100644
index 00000000..f54d2798
--- /dev/null
+++ b/Src/nde/win/Record.cpp
@@ -0,0 +1,133 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Record Class
+
+--------------------------------------------------------------------------- */
+
+//#include "record.h"
+#include "../nde.h"
+#include <stdio.h>
+
+void RecordBase::Retain()
+{
+ ref_count++;
+}
+
+void RecordBase::Release()
+{
+ if (--ref_count == 0)
+ delete this;
+}
+
+RecordBase::RecordBase()
+{
+ ref_count = 1;
+ InsertionPoint = 0;
+}
+
+//---------------------------------------------------------------------------
+Record::Record(int RecordPos, int insertionPoint, VFILE *TableHandle, Table *ParentTable)
+{
+ InsertionPoint = insertionPoint;
+ Record *columns = ParentTable->GetColumns();
+ int max=columns ? (int)columns->Fields.size() : 128;
+ if (RecordPos != 0)
+ {
+ int n=0;
+ uint32_t ThisPos = RecordPos;
+ while (ThisPos)
+ {
+ if (n >= max)
+ break;
+ Vfseek(TableHandle, ThisPos, SEEK_SET);
+ Field Entry (ThisPos);
+ Field *TypedEntry = Entry.ReadField(ParentTable, ThisPos, &ThisPos);
+
+ if (!TypedEntry) break; // some db error?
+
+ AddField(TypedEntry);
+ n++;
+ }
+ }
+}
+
+//---------------------------------------------------------------------------
+RecordBase::~RecordBase()
+{
+ //Fields.deleteAll();
+ for (Field* field : Fields)
+ {
+ if (field)
+ {
+ delete field;
+ }
+ }
+ Fields.clear();
+}
+
+ColumnField *Record::GetColumnByName(const wchar_t *name)
+{
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ ColumnField *p = (ColumnField *)*itr;
+ if (!_wcsicmp(p->GetFieldName(), name))
+ return p;
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+int Record::WriteFields(Table *ParentTable, int RecordIndex)
+{
+ Field *previous = 0;
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ Field *p = *itr;
+
+ Field* nextField = (itr + 1) != Fields.end() ? *(itr + 1) : nullptr;
+
+ //p->WriteField(ParentTable, previous, (Field*)p->next);
+ p->WriteField(ParentTable, previous, nextField);
+
+ previous = p;
+ }
+ return WriteIndex(ParentTable, RecordIndex);
+}
+
+//---------------------------------------------------------------------------
+int Record::WriteIndex(Table *ParentTable, int RecordIndex)
+{
+ int P=0;
+ if (RecordIndex == NEW_RECORD)
+ RecordIndex = ParentTable->index->Insert(InsertionPoint);
+ if (!Fields.empty())
+ {
+ Field *f = *Fields.begin();
+ P=f->GetFieldPos();
+ }
+ return ParentTable->index->Update(RecordIndex, P, this, FALSE);
+}
+
+//---------------------------------------------------------------------------
+void Record::Delete(Table *ParentTable, int RecordIndex)
+{
+ ParentTable->index->Delete(RecordIndex, ParentTable->index->Get(RecordIndex), this);
+}
+
+void Record::WalkFields(FieldsWalker callback, void *context)
+{
+ if (callback)
+ {
+ for (FieldList::iterator itr=Fields.begin();itr!=Fields.end();itr++)
+ {
+ if (!callback(this, *itr, context))
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/nde/win/Record.h b/Src/nde/win/Record.h
new file mode 100644
index 00000000..0c92acef
--- /dev/null
+++ b/Src/nde/win/Record.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "../nde.h"
+#include "../Field.h"
+#include "Vfs.h"
+#include <stdio.h>
+#include <deque>
+
+class ColumnField;
+
+class RecordBase
+{
+public:
+ RecordBase();
+ virtual ~RecordBase();
+ Field *GetField(unsigned char ID);
+ void Retain();
+ void Release();
+ void RemoveField(Field *field);
+ void AddField(Field *field);
+ Field* GetLastField()
+ {
+ if (Fields.empty())
+ {
+ return nullptr;
+ }
+ return Fields[Fields.size() - 1];
+ }
+protected:
+ void Undelete(void);
+
+ int InsertionPoint; // TODO: benski> might be able to pass this into WriteFields/WriteIndex
+ int ref_count;
+ typedef std::deque<Field*> FieldList;
+ FieldList Fields;
+};
+
+class Record : public RecordBase
+{
+public:
+ Record(int RecordPos, int insertionPoint, VFILE *FileHandle,Table *p);
+ bool InCache() { return ref_count > 1; }
+
+ ColumnField *GetColumnByName(const wchar_t *name);
+ int WriteFields(Table *ParentTable, int RecordIndex);
+ int WriteIndex(Table *ParentTable, int RecordIndex);
+ void Delete(Table *ParentTable, int RecordIndex);
+ typedef bool (__cdecl *FieldsWalker)(Record *record, Field *entry, void *context);
+ NDE_API void WalkFields(FieldsWalker callback, void *context);
+}; \ No newline at end of file
diff --git a/Src/nde/win/Scanner.cpp b/Src/nde/win/Scanner.cpp
new file mode 100644
index 00000000..8b0886ef
--- /dev/null
+++ b/Src/nde/win/Scanner.cpp
@@ -0,0 +1,1238 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Scanner Class
+
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoWide.h"
+#include "BinaryField.h"
+#include "Binary32Field.h"
+#include <assert.h>
+//---------------------------------------------------------------------------
+Scanner::Scanner(Table *parentTable)
+{
+ disable_date_resolution=0;
+ index = NULL;
+ pTable = parentTable;
+ Edition=FALSE;
+
+ lastLocateIndex = NULL;
+ lastLocateId = -1;
+ lastLocateIdx = -1;
+ lastLocateFrom = -128;
+ lastLocateFieldClone = NULL;
+ CurrentRecord=NULL;
+ CurrentRecordIdx=0;
+ iModified = FALSE;
+ FiltersOK = FALSE;
+
+ search_any = FALSE;
+ ResultPtr = 0;
+
+ /*inMatchJoins = 0;
+ lastJoinCache = 0;
+ invalidJoinCache = 1;*/
+ last_query = NULL;
+ last_query_failed = FALSE;
+ token = NULL;
+ in_query_parser = 0;
+}
+
+//---------------------------------------------------------------------------
+Scanner::~Scanner()
+{
+ for ( StringField *l_string_field : search_strings )
+ delete l_string_field;
+
+ search_strings.clear();
+
+ if ( CurrentRecord )
+ CurrentRecord->Release();
+
+ if ( lastLocateFieldClone )
+ {
+ delete lastLocateFieldClone;
+ }
+
+ Query_CleanUp();
+
+ if ( token )
+ ndestring_release( token );
+
+ ndestring_release( last_query );
+}
+
+//---------------------------------------------------------------------------
+Record *Scanner::GetRecord(int Idx)
+{
+ int Ptr;
+ Ptr = index->Get(Idx);
+
+ Record *r = pTable->RowCache_Get(Ptr);
+ if (r)
+ {
+ return r;
+ }
+
+ r = new Record(Ptr, Idx, pTable->Handle, pTable);
+ pTable->RowCache_Add(r, Ptr);
+ return r;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::GetCurrentRecord(void)
+{
+ if (CurrentRecord) CurrentRecord->Release();
+ CurrentRecord = NULL;
+
+ //invalidJoinCache = 1;
+ if (Eof() || Bof()) return;
+ CurrentRecord = GetRecord(CurrentRecordIdx);
+
+}
+
+//---------------------------------------------------------------------------
+void Scanner::GetRecordById(int Id, BOOL checkFilters)
+{
+ CurrentRecordIdx=max(min(index->NEntries, Id+NUM_SPECIAL_RECORDS), 0);
+ GetCurrentRecord();
+ if (!checkFilters || MatchFilters())
+ return;
+ Next();
+}
+
+//---------------------------------------------------------------------------
+void Scanner::First(int *killswitch)
+{
+ if (last_query_failed) return;
+ GetRecordById(0);
+ if (!MatchFilters() && !Eof())
+ Next(killswitch);
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Next(int *killswitch)
+{
+ if (last_query_failed) return 0;
+
+ while (!Eof() && !Bof())
+ {
+ CurrentRecordIdx++;
+ GetCurrentRecord();
+ if (MatchFilters())
+ break;
+ else
+ {
+ if ((killswitch && *killswitch))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Previous(int *killswitch)
+{
+ if (last_query_failed) return 0;
+
+ while (CurrentRecordIdx >= NUM_SPECIAL_RECORDS)
+ {
+ CurrentRecordIdx--;
+ GetCurrentRecord();
+ if (MatchFilters())
+ break;
+ else
+ {
+ if ((killswitch && *killswitch))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Last(int *killswitch)
+{
+ if (last_query_failed) return;
+ GetRecordById(index->NEntries-NUM_SPECIAL_RECORDS-1); // -3 here because 1)GetRecordById is public, so -NUM_SPECIAL_RECORDS, and 2)last entry is nentries-1, so -1
+ if (!MatchFilters() && !Bof())
+ Previous(killswitch);
+ if (CurrentRecordIdx < NUM_SPECIAL_RECORDS)
+ {
+ CurrentRecordIdx = index->NEntries;
+ GetCurrentRecord(); // will only delete current record if it exists
+ }
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::Eof(void)
+{
+ if (last_query_failed) return TRUE;
+ return CurrentRecordIdx >= index->NEntries;
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::Bof(void)
+{
+ if (last_query_failed) return TRUE;
+ return CurrentRecordIdx < NUM_SPECIAL_RECORDS;
+}
+
+//---------------------------------------------------------------------------
+Field *Scanner::GetFieldByName(const wchar_t *FieldName)
+{
+ ColumnField *header = pTable->GetColumnByName(FieldName);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ return GetFieldById(Idx);
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+Field *Scanner::GetFieldById(unsigned char Id)
+{
+ if (!CurrentRecord)
+ return NULL;
+ Field *field = CurrentRecord->GetField(Id);
+ if (field)
+ {
+ int column_type = pTable->GetColumnType(Id);
+ if (!CompatibleFields(field->GetType(), column_type))
+ {
+ return NULL;
+ }
+ }
+ return field;
+}
+
+//---------------------------------------------------------------------------
+Field *Scanner::NewFieldByName(const wchar_t *fieldName, unsigned char Perm)
+{
+ ColumnField *header = pTable->GetColumnByName(fieldName);
+ if (header)
+ {
+ unsigned char Id = header->ID;
+ Field *field = NewFieldById(Id, Perm);
+ return field;
+ }
+ return NULL;
+}
+
+//---------------------------------------------------------------------------
+Field *Scanner::NewFieldById(unsigned char Id, unsigned char Perm)
+{
+ ColumnField *field = GetColumnById(Id);
+ if (!field)
+ return NULL;
+ Field *O=GetFieldById(Id);
+ if (O) return O;
+ switch (field->GetDataType())
+ {
+ case FIELD_STRING:
+ O = new StringField();
+ break;
+ case FIELD_INTEGER:
+ O = new IntegerField();
+ break;
+ case FIELD_INT64:
+ O = new Int64Field();
+ break;
+ case FIELD_INT128:
+ O = new Int128Field();
+ break;
+ case FIELD_DATETIME:
+ if (disable_date_resolution)
+ O= new StringField();
+ else
+ O = new DateTimeField();
+ break;
+ case FIELD_LENGTH:
+ O = new LengthField();
+ break;
+ case FIELD_FILENAME:
+ O = new FilenameField();
+ break;
+ case FIELD_BINARY:
+ O = new BinaryField();
+ break;
+ case FIELD_BINARY32:
+ O = new Binary32Field();
+ break;
+ default:
+ MessageBox(NULL, L"unknown field type for id", L"debug", 0);
+ O = new Field();
+ break;
+ }
+ O->Type = field->GetDataType();
+ O->ID = Id;
+ CurrentRecord->AddField(O);
+ return O;
+}
+
+Field *Scanner::NewFieldByType(unsigned char Type, unsigned char Id, unsigned char Perm)
+{
+ Field *O = NULL;
+ switch (Type)
+ {
+ case FIELD_STRING:
+ O = new StringField();
+ break;
+ case FIELD_INTEGER:
+ O = new IntegerField();
+ break;
+ case FIELD_INT64:
+ O = new Int64Field();
+ break;
+ case FIELD_INT128:
+ O = new Int128Field();
+ break;
+ case FIELD_DATETIME:
+ if (disable_date_resolution)
+ O= new StringField();
+ else
+ O = new DateTimeField();
+ break;
+ case FIELD_LENGTH:
+ O = new LengthField();
+ break;
+ case FIELD_FILENAME:
+ O = new FilenameField();
+ break;
+ case FIELD_BINARY:
+ O = new BinaryField();
+ break;
+ case FIELD_BINARY32:
+ O = new Binary32Field();
+ break;
+ default:
+ MessageBox(NULL, L"unknown field type for id", L"debug", 0);
+ O = new Field();
+ break;
+ }
+ O->Type = Type;
+ O->ID = Id;
+ CurrentRecord->AddField(O);
+ return O;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Post(void)
+{
+ if (!CurrentRecord) return;
+ /*if (CurrentRecord->RecordIndex == NEW_RECORD)
+ NEntries++;*/
+ if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD)
+ {
+ int Ptr = index->Get(CurrentRecordIdx);
+ pTable->RowCache_Remove(Ptr);
+ }
+ CurrentRecordIdx = CurrentRecord->WriteFields(pTable, CurrentRecordIdx);
+ Edition=FALSE;
+ if (pTable->use_row_cache)
+ {
+ int Ptr = index->Get(CurrentRecordIdx);
+ pTable->RowCache_Add(CurrentRecord, Ptr);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Scanner::New(void)
+{
+ if (CurrentRecord) CurrentRecord->Release();
+ CurrentRecord = NULL;
+
+ CurrentRecord = new Record(0, index->NEntries, pTable->Handle, pTable);
+ CurrentRecordIdx = NEW_RECORD;
+ Edition = TRUE;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Insert(void)
+{
+ if (CurrentRecord) CurrentRecord->Release();
+ CurrentRecord = new Record(0, CurrentRecordIdx, pTable->Handle, pTable);
+ CurrentRecordIdx = NEW_RECORD;
+ Edition=TRUE;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Delete(void)
+{
+ if (CurrentRecord)
+ {
+ if (pTable->use_row_cache && CurrentRecordIdx != NEW_RECORD)
+ {
+ int Ptr = index->Get(CurrentRecordIdx);
+ pTable->RowCache_Delete(Ptr);
+ }
+ CurrentRecord->Delete(pTable, CurrentRecordIdx);
+ }
+ if (Eof())
+ Previous();
+ GetRecordById(CurrentRecordIdx-NUM_SPECIAL_RECORDS);
+}
+
+//---------------------------------------------------------------------------
+int Scanner::GetRecordId(void)
+{
+ return CurrentRecordIdx != NEW_RECORD ? CurrentRecordIdx-NUM_SPECIAL_RECORDS : CurrentRecordIdx;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Edit(void)
+{
+ if (Edition) return;
+ if (!CurrentRecord)
+ return;
+ /*Field *f = (Field *)CurrentRecord->Fields->GetHead();
+ while (f)
+ {
+ f->SubtableRecord = INVALID_RECORD;
+ f = (Field *)f->GetNext();
+ }*/
+
+ if (CurrentRecord->InCache()) // if it's in the cache
+ {
+ // benski> make copy of CurrentRecord, outside the cache
+ int Ptr = index->Get(CurrentRecordIdx);
+ pTable->RowCache_Remove(Ptr);
+ Record *r = new Record(Ptr, CurrentRecordIdx, pTable->Handle, pTable);
+ CurrentRecord->Release();
+ CurrentRecord = r;
+ }
+
+ Edition = TRUE;
+}
+
+
+//---------------------------------------------------------------------------
+void Scanner::Cancel(void)
+{
+ Edition = FALSE;
+ GetCurrentRecord();
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::LocateByName(const wchar_t *col, int From, Field *field, int *nskip)
+{
+ ColumnField *f = pTable->GetColumnByName(col);
+ if (!f)
+ return NULL;
+ return LocateById(f->GetFieldId(), From, field, nskip);
+}
+
+//---------------------------------------------------------------------------
+void Scanner::CacheLastLocate(int Id, int From, Field *field, Index *i, int j)
+{
+ lastLocateId = Id;
+ lastLocateFrom = From;
+ if (lastLocateFieldClone)
+ {
+ delete lastLocateFieldClone;
+ lastLocateFieldClone = NULL;
+ }
+ lastLocateFieldClone = field->Clone(pTable);
+ lastLocateIndex = i;
+ i->locateUpToDate = TRUE;
+ pTable->SetGlobalLocateUpToDate(TRUE);
+ lastLocateIdx = j;
+}
+
+
+//---------------------------------------------------------------------------
+BOOL Scanner::LocateById(int Id, int From, Field *field, int *nskip)
+{
+ return LocateByIdEx(Id, From, field, nskip, COMPARE_MODE_EXACT);
+}
+//---------------------------------------------------------------------------
+BOOL Scanner::LocateByIdEx(int Id, int From, Field *field, int *nskip, int comp_mode)
+{
+ IndexField *i = pTable->GetIndexById(Id);
+ Field *compField=NULL;
+ int j=0;
+ int n=0;
+ Field *cfV=NULL;
+
+ if (index->NEntries == NUM_SPECIAL_RECORDS)
+ return FALSE;
+
+ int success = FALSE;
+
+ if (nskip) *nskip=0;
+ // I know this is stupid but.... May be do something later
+ switch (comp_mode)
+ {
+ case COMPARE_MODE_CONTAINS:
+ while (1)
+ {
+ success = FALSE;
+ if (!i)
+ {
+ // No index for this column. Using slow locate, enumerates the database, still faster than what the user
+ // can do since we have access to QuickFindField which only read field headers
+ // in order to locate the field we have to compare. user could only read the entire record.
+ if (From == FIRST_RECORD)
+ From = NUM_SPECIAL_RECORDS;
+ else
+ From+=(NUM_SPECIAL_RECORDS+1);
+ if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_1;
+ }
+ for (j=From;j<index->NEntries;j++)
+ {
+ compField = index->QuickFindField(Id, index->Get(j));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (!field->Contains(cfV))
+ {
+ if (compField)
+ {
+ delete compField;
+ }
+ if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, index, j);
+ success = TRUE;
+ goto nextiter_1;
+ }
+ delete compField;
+ }
+ success = FALSE;
+ goto nextiter_1;
+ }
+ else
+ {
+ // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
+ int p = 0;
+ if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
+ else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
+ if (From == lastLocateFrom && Id == lastLocateId && field->Contains(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_1;
+ }
+ if (From >= index->NEntries)
+ {
+ return FALSE;
+ }
+ compField = i->index->QuickFindField(Id, i->index->Get(From));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (field->Contains(cfV) == 0)
+ {
+ delete compField;
+ compField = NULL;
+ n = i->index->TranslateIndex(From, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_1;
+ }
+ delete compField;
+ compField = NULL;
+ p = i->index->QuickFindEx(Id, field, From, comp_mode);
+ if (p != FIELD_NOT_FOUND)
+ {
+ n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_1;
+ }
+ }
+nextiter_1: // eek
+ if (success)
+ {
+ if (!MatchFilters() && !Eof())
+ {
+ From = GetRecordId();
+ if (nskip) (*nskip)++;
+ }
+ else break;
+ }
+ else break;
+ }
+
+ break;
+ case COMPARE_MODE_EXACT:
+ while (1)
+ {
+ success = FALSE;
+ if (!i)
+ {
+ // No index for this column. Using slow locate, enumerates the database, still faster than what the user
+ // can do since we have access to QuickFindField which only read field headers
+ // in order to locate the field we have to compare. user could only read the entire record.
+ if (From == FIRST_RECORD)
+ From = NUM_SPECIAL_RECORDS;
+ else
+ From+=(NUM_SPECIAL_RECORDS+1);
+ if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_2;
+ }
+ for (j=From;j<index->NEntries;j++)
+ {
+ compField = index->QuickFindField(Id, index->Get(j));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (!field->Compare(cfV))
+ {
+ delete compField;
+ compField = NULL;
+ if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, index, j);
+ success = TRUE;
+ goto nextiter_2;
+ }
+ delete compField;
+ compField = NULL;
+ }
+ success = FALSE;
+ goto nextiter_2;
+ }
+ else
+ {
+ // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
+ int p;
+ if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
+ else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
+ if (From == lastLocateFrom && Id == lastLocateId && field->Compare(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_2;
+ }
+ if (From >= index->NEntries)
+ {
+ return FALSE;
+ }
+ compField = i->index->QuickFindField(Id, i->index->Get(From));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (field->Compare(cfV) == 0)
+ {
+ if (compField)
+ {
+ delete compField;
+ compField = NULL;
+ }
+ n = i->index->TranslateIndex(From, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_2;
+ }
+ delete compField;
+ p = i->index->QuickFindEx(Id, field, From, comp_mode);
+ if (p != FIELD_NOT_FOUND)
+ {
+ n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_2;
+ }
+ }
+nextiter_2: // eek
+ if (success)
+ {
+ if (!MatchFilters() && !Eof())
+ {
+ From = GetRecordId();
+ if (nskip) (*nskip)++;
+ }
+ else break;
+ }
+ else break;
+ }
+ break;
+ case COMPARE_MODE_STARTS:
+ while (1)
+ {
+ success = FALSE;
+ if (!i)
+ {
+ // No index for this column. Using slow locate, enumerates the database, still faster than what the user
+ // can do since we have access to QuickFindField which only read field headers
+ // in order to locate the field we have to compare. user could only read the entire record.
+ if (From == FIRST_RECORD)
+ From = NUM_SPECIAL_RECORDS;
+ else
+ From+=(NUM_SPECIAL_RECORDS+1);
+ if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_3;
+ }
+ for (j=From;j<index->NEntries;j++)
+ {
+ compField = index->QuickFindField(Id, index->Get(j));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (!field->Starts(cfV))
+ {
+ if (compField)
+ {
+ delete compField;
+ compField = NULL;
+ }
+ if (CurrentRecordIdx != j) GetRecordById(j-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, index, j);
+ success = TRUE;
+ goto nextiter_3;
+ }
+ if (compField)
+ {
+ delete compField;
+ compField = NULL;
+ }
+ }
+ success = FALSE;
+ goto nextiter_3;
+ }
+ else
+ {
+ // Index available. Using fast locate. nfetched=log2(nrecords) for first locate, 1 more fetch per locate on same criteria
+ int p = 0;
+ if (From == FIRST_RECORD) From = NUM_SPECIAL_RECORDS;
+ else From = index->TranslateIndex(From+NUM_SPECIAL_RECORDS, i->index)+1;
+ if (From == lastLocateFrom && Id == lastLocateId && field->Starts(lastLocateFieldClone)==0 && index == lastLocateIndex && (i->index->locateUpToDate || pTable->GLocateUpToDate))
+ {
+ if (CurrentRecordIdx != lastLocateIdx) GetRecordById(lastLocateIdx-NUM_SPECIAL_RECORDS, FALSE);
+ success = TRUE;
+ goto nextiter_3;
+ }
+ if (From >= index->NEntries)
+ {
+ return FALSE;
+ }
+ compField = i->index->QuickFindField(Id, i->index->Get(From));
+ cfV = /*(compField && compField->Type == FIELD_PRIVATE) ? ((PrivateField *)compField)->myField :*/ compField;
+ if (field->Starts(cfV) == 0)
+ {
+ delete compField;
+ compField = NULL;
+ n = i->index->TranslateIndex(From, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_3;
+ }
+ delete compField;
+ compField = NULL;
+ p = i->index->QuickFindEx(Id, field, From, comp_mode);
+ if (p != FIELD_NOT_FOUND)
+ {
+ n = (index->GetId() == Id) ? p : i->index->TranslateIndex(p, index);
+ if (CurrentRecordIdx != n) GetRecordById(n-NUM_SPECIAL_RECORDS, FALSE);
+ CacheLastLocate(Id, From, field, i->index, n);
+ success = TRUE;
+ goto nextiter_3;
+ }
+ }
+nextiter_3: // eek
+ if (success)
+ {
+ if (!MatchFilters() && !Eof())
+ {
+ From = GetRecordId();
+ if (nskip) (*nskip)++;
+ }
+ else break;
+ }
+ else break;
+ }
+ break;
+ }
+
+ return success;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::DeleteFieldByName(const wchar_t *name)
+{
+ ColumnField *header = pTable->GetColumnByName(name);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ DeleteFieldById(Idx);
+ }
+ return;
+}
+
+
+//---------------------------------------------------------------------------
+void Scanner::DeleteFieldById(unsigned char Id)
+{
+ Field *field = CurrentRecord->GetField(Id);
+ if (!field) return;
+ CurrentRecord->RemoveField(field);
+}
+
+//---------------------------------------------------------------------------
+void Scanner::DeleteField(Field *field)
+{
+ if (!field) return;
+ CurrentRecord->RemoveField(field);
+}
+
+static bool TotalSizeCalculator(Record *record, Field *f, void *context)
+{
+ size_t *totalSize = (size_t *)context;
+ *totalSize += f->GetTotalSize();
+ return true;
+}
+
+//---------------------------------------------------------------------------
+float Scanner::FragmentationLevel(void)
+{
+ int oldP = GetRecordId();
+ size_t totalSize=0;
+
+ if (CurrentRecord)
+ {
+ if (CurrentRecord) CurrentRecord->Release();
+ CurrentRecord = NULL;
+ CurrentRecordIdx = 0;
+ }
+
+ for (int i=0;i<index->NEntries;i++)
+ {
+ Record *r = GetRecord(i);
+ if (r)
+ {
+ r->WalkFields(TotalSizeCalculator, &totalSize);
+ r->Release();
+ }
+ }
+ GetRecordById(oldP);
+ Vfseek(pTable->Handle, 0, SEEK_END);
+ return (((float)(Vftell(pTable->Handle)-strlen(__TABLE_SIGNATURE__)) / (float)totalSize) - 1) * 100;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::GetRecordsCount(void)
+{
+ if (index)
+ return index->NEntries-NUM_SPECIAL_RECORDS;
+ else
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::SetWorkingIndexById(unsigned char Id)
+{
+ IndexField *indx = pTable->GetIndexById(Id);
+ int v = CurrentRecordIdx;
+ if (indx)
+ {
+ if (!Eof() && !Bof())
+ {
+ IndexField *f = index->SecIndex;
+ v = index->GetCooperative(CurrentRecordIdx);
+ while (f != indx)
+ {
+ v = f->index->GetCooperative(v);
+ f = f->index->SecIndex;
+ }
+ }
+ index = indx->index;
+ CurrentRecordIdx = v;
+ GetCurrentRecord();
+ }
+ return (indx != NULL);
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::SetWorkingIndexByName(const wchar_t *desc)
+{
+ IndexField *indx = pTable->GetIndexByName(desc);
+ if (indx)
+ return SetWorkingIndexById(indx->ID);
+ else
+ return SetWorkingIndexById(-1);
+}
+
+//---------------------------------------------------------------------------
+void Scanner::IndexModified(void)
+{
+ iModified = TRUE;
+}
+
+//---------------------------------------------------------------------------
+void Scanner::ClearDirtyBit(void)
+{
+ iModified = FALSE;
+}
+
+//---------------------------------------------------------------------------
+Table *Scanner::GetTable(void)
+{
+ return pTable;
+}
+
+//---------------------------------------------------------------------------
+ColumnField *Scanner::GetColumnByName(const wchar_t *FieldName)
+{
+ return pTable->GetColumnByName(FieldName);
+}
+//---------------------------------------------------------------------------
+ColumnField *Scanner::GetColumnById(unsigned char Idx)
+{
+ return pTable->GetColumnById(Idx);
+}
+
+//---------------------------------------------------------------------------
+int Scanner::AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op)
+{
+ ColumnField *f = pTable->GetColumnByName(name);
+ if (f)
+ return AddFilterById(f->GetFieldId(), Data, Op);
+ return ADDFILTER_FAILED;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::AddFilterById(unsigned char Id, Field *Data, unsigned char Op)
+{
+ ColumnField *f = pTable->GetColumnById(Id);
+ if (f)
+ {
+ Filter *filter = new Filter(Data, f->GetFieldId(), Op);
+ FilterList.AddEntry(filter, TRUE);
+ }
+ else
+ return ADDFILTER_FAILED;
+
+ if (in_query_parser) return 1;
+ return CheckFilters();
+}
+
+//---------------------------------------------------------------------------
+int Scanner::AddFilterOp(unsigned char Op)
+{
+ Filter *filter = new Filter(Op);
+ FilterList.AddEntry(filter, TRUE);
+ if (in_query_parser) return 1;
+ return CheckFilters();
+}
+
+//---------------------------------------------------------------------------
+Filter *Scanner::GetLastFilter(void)
+{
+ if (FilterList.GetNElements() == 0) return NULL;
+ return (Filter *)FilterList.GetFoot();
+}
+
+//---------------------------------------------------------------------------
+void Scanner::RemoveFilters(void)
+{
+ last_query_failed = FALSE;
+ while (FilterList.GetNElements() > 0)
+ FilterList.RemoveEntry(FilterList.GetHead());
+ FiltersOK = FALSE;
+}
+
+//---------------------------------------------------------------------------
+BOOL Scanner::CheckFilters(void)
+{
+ int f=0;
+ FiltersOK = FALSE;
+ if (FilterList.GetNElements() == 0) // Should never happen
+ return FILTERS_INVALID;
+ Filter *filter = (Filter *)FilterList.GetHead();
+ while (filter)
+ {
+ if (f == 256) return FILTERS_INVALID;
+ int op = filter->GetOp();
+ if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
+ f++;
+ else
+ {
+ if (op != FILTER_NOT)
+ f--;
+ }
+ if (f == 0) return FILTERS_INVALID;
+ filter = (Filter *)filter->GetNext();
+ }
+
+ if (f == 1)
+ {
+ FiltersOK = TRUE;
+ return FILTERS_COMPLETE;
+ }
+ return FILTERS_INCOMPLETE;
+}
+
+//---------------------------------------------------------------------------
+static bool EmptyMeansTrue(int op)
+{
+ return op == FILTER_ISEMPTY
+ || op == FILTER_NOTEQUALS
+ || op == FILTER_NOTCONTAINS;
+}
+
+//---------------------------------------------------------------------------
+bool Scanner::MatchFilter(Filter *filter)
+{
+ Field *field = GetFieldById(filter->GetId());
+ int op = filter->GetOp();
+ Field * f = filter->Data();
+ /* old behaviour
+ if (!field)
+ return EmptyMeansTrue(op);
+ else if (op == FILTER_ISEMPTY)
+ return FALSE;
+ else if (op == FILTER_ISNOTEMPTY)
+ return TRUE;
+ */
+
+ // new behaviour
+ if (!field)
+ {
+ // if field is empty and we're doing an equals op, match if f is also empty
+ if (op == FILTER_EQUALS && f) return f->ApplyFilter(f,FILTER_ISEMPTY);
+ if (op == FILTER_NOTEQUALS && f) return f->ApplyFilter(f,FILTER_ISNOTEMPTY);
+ return EmptyMeansTrue(op);
+ }
+ // no need to check for op == FILTER_ISEMPTY, the fields now handle that
+
+ return field->ApplyFilter(f, op);
+}
+
+struct Results
+{
+ Results(){
+ value = false;
+ calculated = false;
+ filter = 0;
+ }
+
+ void operator=(bool _val)
+ {
+ calculated=true;
+ value=_val;
+ }
+
+ bool Calc(Scanner *scanner)
+ {
+ if (!calculated)
+ {
+#if 0 /* if we want to do field-to-field comparisons */
+ Field *compare_column = filter->Data();
+ if (compare_column && compare_column->Type == FIELD_COLUMN)
+ {
+ Field *field = scanner->GetFieldById(filter->GetId());
+ Field *compare_field = scanner->GetFieldById(compare_column->ID);
+ int op = filter->GetOp();
+ value = field->ApplyFilter(compare_field, op);
+ calculated=true;
+ return value;
+ }
+#endif
+ value = scanner->MatchFilter(filter);
+ calculated=true;
+ }
+ return value;
+ }
+
+ void SetFilter(Filter *_filter)
+ {
+ calculated=false;
+ filter=_filter;
+ }
+private:
+ bool value;
+ bool calculated;
+ Filter *filter;
+};
+
+bool Scanner::MatchSearch(const SearchFields &fields, StringField *search_field)
+{
+ for (SearchFields::const_iterator itr = fields.begin(); itr != fields.end();itr++)
+ {
+ Field *f = GetFieldById(*itr);
+ if (f && f->Contains(search_field))
+ {
+ return true;
+ }
+ }
+ return false; // if none of the fields matched the search strings, then bail out
+}
+
+bool Scanner::MatchSearches()
+{
+ // no search means always match
+ if (search_strings.empty())
+ return true;
+
+ Scanner *s = pTable->GetDefaultScanner(); // kind of a hack but gets around private member variables
+ const SearchFields &fields = s->search_fields;
+ if (search_any)
+ {
+ for ( StringField *l_string_field : search_strings )
+ {
+ if ( MatchSearch( fields, l_string_field ) == true )
+ return true;
+ }
+
+ return false; // we'll only get here if no search strings matched
+ }
+ else
+ { // normal search (subsequent terms do further filtering
+ for ( StringField *l_string_field : search_strings )
+ {
+ if ( MatchSearch( fields, l_string_field ) == false )
+ return false;
+ }
+
+ return true; // we'll only get here if all search strings have a match
+ }
+}
+
+//---------------------------------------------------------------------------
+bool Scanner::MatchFilters(void)
+{
+ if (!FiltersOK || FilterList.GetNElements() == 0)
+ {
+ // return MatchJoins();
+ return true;
+ }
+
+ //if (!MatchSearches())
+ //return false;
+
+ ResultPtr = 0;
+
+ Results resultTable[256];
+
+ Filter *filter = (Filter *)FilterList.GetHead();
+ while (filter)
+ {
+ int op = filter->GetOp();
+ if (filter->Data() || op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
+ resultTable[ResultPtr++].SetFilter(filter);
+ else
+ switch (op)
+ {
+ case FILTER_AND:
+ if (ResultPtr > 1)
+ resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) && resultTable[ResultPtr-1].Calc(this);
+ ResultPtr--;
+ break;
+ case FILTER_OR:
+ if (ResultPtr > 1)
+ resultTable[ResultPtr-2] = resultTable[ResultPtr-2].Calc(this) || resultTable[ResultPtr-1].Calc(this);
+ ResultPtr--;
+ break;
+ case FILTER_NOT:
+ if (ResultPtr > 0)
+ resultTable[ResultPtr-1] = !resultTable[ResultPtr-1].Calc(this);
+ break;
+ }
+ filter = (Filter *)filter->GetNext();
+ }
+
+ if (ResultPtr != 1) // Should never happen, case already discarded by CheckFilters
+ {
+ FiltersOK = FALSE;
+ return TRUE;
+ }
+
+ if (!resultTable[0].Calc(this)) return 0;
+ // return MatchJoins();
+ //return FALSE;
+ return MatchSearches();
+}
+
+void Scanner::WalkFilters(FilterWalker callback, void *context)
+{
+ if (callback)
+ {
+ LinkedListEntry *entry = FilterList.GetHead();
+ while (entry)
+ {
+ if (!callback(this, (Filter *)entry, context))
+ break;
+ entry = entry->Next;
+ }
+ }
+}
+
+void Scanner::Search(const wchar_t *search_string)
+{
+ // first, clear existing search terms
+ for ( StringField *l_string_field : search_strings )
+ delete l_string_field;
+
+ search_strings.clear();
+
+ if (search_string && *search_string == L'*' && search_string[1] == L' ')
+ {
+ search_any=true;
+ search_string += 2;
+ }
+ else
+ search_any=false;
+
+ if (search_string)
+ {
+ while (search_string && *search_string)
+ {
+ while (search_string && (*search_string && (*search_string == ' ' || *search_string == '\t')))
+ search_string++;
+
+ const wchar_t *end=search_string;
+
+ wchar_t c = *search_string;
+ if (c == L'\"') // a quoted string
+ {
+ end++;
+ search_string++;
+ while (end && *end && *end != '\"')
+ end++;
+
+ if (search_string && *search_string) // make sure it's not just a quote by itself
+ {
+ if (*end == 0) // no terminating quotes
+ {
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ }
+ else if (end > (search_string+1)) // at least one character in the quotes
+ {
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ end++;
+ }
+ }
+ search_string=end;
+ }
+ else if (c)
+ {
+ while (end && *end && *end != ' ' && *end != '\t')
+ end++;
+
+ wchar_t *search_term = ndestring_wcsndup(search_string, end-search_string-1);
+ search_strings.push_back(new StringField(search_term, STRING_IS_NDESTRING));
+ }
+ }
+ }
+}
+
+void Scanner::WalkFields(Record::FieldsWalker callback, void *context)
+{
+ if (CurrentRecord)
+ CurrentRecord->WalkFields(callback, context);
+} \ No newline at end of file
diff --git a/Src/nde/win/Scanner.h b/Src/nde/win/Scanner.h
new file mode 100644
index 00000000..edd4dc64
--- /dev/null
+++ b/Src/nde/win/Scanner.h
@@ -0,0 +1,154 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+ --------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Scanner Class Prototypes
+
+ --------------------------------------------------------------------------- */
+
+#ifndef __SCANNER_H
+#define __SCANNER_H
+
+#include <vector>
+#include "record.h"
+#include "Table.h"
+#include "index.h"
+#include <vector>
+#include <set>
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+class Table;
+class Index;
+#pragma warning( disable: 4251 )
+class Scanner;
+class Record;
+class Scanner : public LinkedListEntry
+{
+public:
+ Record *GetRecord(int Idx);
+ Scanner(Table *parentTable);
+ void IndexModified(void);
+ Index *GetIndex() { return index; }
+ Index *index; // TODO: make protected
+
+protected:
+ ~Scanner();
+
+ Table *pTable;
+ BOOL iModified;
+ typedef std::vector<StringField *> SearchStrings;
+ SearchStrings search_strings;
+ typedef std::set<unsigned char> SearchFields;
+ SearchFields search_fields;
+ bool search_any;
+
+ void GetCurrentRecord(void);
+ bool MatchFilters(void);
+ bool MatchSearches();
+ bool MatchSearch(const SearchFields &fields, StringField *search_field);
+ //BOOL MatchJoins(void);
+ int CheckFilters(void);
+ void CacheLastLocate(int Id, int From, Field *field, Index *i, int j);
+
+ static int Query_LookupToken(const wchar_t *token);
+ void Query_CleanUp(void);
+ void Query_SyntaxError(int c);
+public:
+ static int Query_GetNextToken(const wchar_t *p, int *size, wchar_t **token, int tokentable=0);
+ static const wchar_t *Query_EatSpace(const wchar_t *p);
+ static wchar_t *Query_ProbeSpace(wchar_t *p);
+ static const wchar_t *Query_ProbeNonAlphaNum(const wchar_t *p);
+ static wchar_t *Query_ProbeAlphaNum(wchar_t *p);
+ static int Query_isControlChar(wchar_t p);
+
+ BOOL Query(const wchar_t *query);
+ BOOL Query_Parse(const wchar_t *query);
+
+ const wchar_t *GetLastQuery();
+
+public://fucko: protected
+ LinkedList pstack;
+ wchar_t *token;
+ wchar_t *last_query;
+ int last_query_failed;
+
+protected:
+ Record *CurrentRecord;
+ int CurrentRecordIdx;
+ LinkedList FilterList;
+ Index *lastLocateIndex;
+ int lastLocateIdx;
+ Field *lastLocateFieldClone;
+ int lastLocateFrom;
+ int lastLocateId;
+ BOOL Edition;
+ int ResultPtr;
+ BOOL FiltersOK;
+
+public:
+ bool MatchFilter(Filter *filter);
+ typedef bool (*FilterWalker)(Scanner *scanner, Filter *filter, void *context);
+ void WalkFilters(FilterWalker walker, void *context);
+
+ ColumnField *GetColumnByName(const wchar_t *FieldName);
+ ColumnField *GetColumnById(unsigned char id);
+
+ Field *NewFieldByName(const wchar_t *fieldName, unsigned char Perm);
+ Field *NewFieldByType(unsigned char Type, unsigned char Id, unsigned char Perm);
+ Field *NewFieldById(unsigned char Id, unsigned char Perm);
+ void DeleteField(Field *field);
+ void DeleteFieldByName(const wchar_t *name);
+ void DeleteFieldById(unsigned char Id);
+
+ void Cancel(void);
+ void Insert(void);
+ void Edit(void);
+ void Post(void);
+ void Delete(void);
+
+ Field *GetFieldByName(const wchar_t *FieldName);
+ Field *GetFieldById(unsigned char Id);
+
+ void First(int *killswitch=0);
+ void Last(int *killswitch=0);
+ int Next(int *killswitch=0);
+ int Previous(int *killswitch=0);
+ BOOL Eof(void);
+ BOOL Bof(void);
+ void New(void);
+ int GetRecordsCount(void);
+ void GetRecordById(int Id, BOOL checkFilters=TRUE);
+ int GetRecordId(void);
+ void Sync(void);
+ BOOL LocateByName(const wchar_t *column, int From, Field *field, int *nskip=NULL);
+ BOOL LocateById(int Id, int From, Field *field, int *nskip=NULL);
+ BOOL LocateByIdEx(int Id, int From, Field *field, int *nskip, int comp_mode);
+
+ // Filters
+ int AddFilterByName(const wchar_t *name, Field *Data, unsigned char Op);
+ int AddFilterById(unsigned char Id, Field *Data, unsigned char Op);
+ int AddFilterOp(unsigned char Op);
+ void RemoveFilters(void);
+ Filter *GetLastFilter(void);
+
+ BOOL SetWorkingIndexByName(const wchar_t *desc);
+ BOOL SetWorkingIndexById(unsigned char Id);
+
+ void Search(const wchar_t *search_string);
+ BOOL HasIndexChanged(void) { return iModified; }
+ void ClearDirtyBit(void);
+ float FragmentationLevel(void);
+
+ void WalkFields(Record::FieldsWalker callback, void *context);
+
+ Table *GetTable();
+ int in_query_parser;
+ int disable_date_resolution;
+};
+
+#endif
diff --git a/Src/nde/win/StringField.cpp b/Src/nde/win/StringField.cpp
new file mode 100644
index 00000000..b6ae3eed
--- /dev/null
+++ b/Src/nde/win/StringField.cpp
@@ -0,0 +1,341 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+StringField Class
+Windows specific version
+
+Field data layout:
+[2 bytes] string length (bytes)
+[length bytes] String data. UTF-16 data will start with a BOM
+--------------------------------------------------------------------------- */
+
+#include "../nde.h"
+#include "StringField.h"
+#include "../../nu/AutoChar.h"
+#include "../../nu/AutoWide.h"
+
+static wchar_t CharSwap(wchar_t value)
+{
+ return (value >> 8) | (value << 8);
+}
+
+//---------------------------------------------------------------------------
+StringField::StringField(const wchar_t *Str, int strkind)
+{
+ InitField();
+ Type = FIELD_STRING;
+ if (Str)
+ {
+ if (strkind == STRING_IS_WCHAR)
+ StringW = ndestring_wcsdup(Str);
+ else
+ {
+ StringW = const_cast<wchar_t *>(Str);
+ ndestring_retain(StringW);
+ }
+ }
+}
+
+//---------------------------------------------------------------------------
+void StringField::InitField(void)
+{
+ Type = FIELD_STRING;
+ StringW = NULL;
+ optimized_the = 0;
+}
+
+//---------------------------------------------------------------------------
+StringField::StringField()
+{
+ InitField();
+}
+
+//---------------------------------------------------------------------------
+StringField::~StringField()
+{
+ ndestring_release(StringW);
+ StringW=0;
+}
+
+//---------------------------------------------------------------------------
+void StringField::ReadTypedData(const uint8_t *data, size_t len)
+{
+ unsigned short c;
+
+ CHECK_SHORT(len);
+ c = (unsigned short)(data[0]|(data[1]<<8));
+ data+=2;
+ if (c)
+ {
+ bool unicode=false;
+ bool reverseEndian=false;
+ if (c >= 2 // enough room for BOM
+ && (c % 2) == 0) // can't be unicode if it's not an even multiple of 2
+ {
+ wchar_t BOM=0;
+ memcpy(&BOM, data, 2);
+ if (BOM == 0xFEFF)
+ {
+ data+=2;
+ c-=2;
+ unicode=true;
+ }
+ else if (BOM == 0xFFFE)
+ {
+ data+=2;
+ c-=2;
+ unicode=true;
+ reverseEndian=true;
+ }
+ }
+
+ CHECK_BIN(len, c);
+ if (unicode)
+ {
+ ndestring_release(StringW);
+ StringW = ndestring_malloc(c+sizeof(wchar_t));
+
+ memcpy(StringW, data, c);
+ StringW[c/2]=0;
+ if (reverseEndian)
+ {
+ for (unsigned short i=0;i<c;i++)
+ StringW[i]=CharSwap(StringW[i]);
+ }
+ }
+ else
+ {
+ int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)data, c, 0, 0);
+ StringW = ndestring_malloc((len+1)*sizeof(wchar_t));
+ MultiByteToWideChar(CP_ACP, 0, (LPCSTR)data, c, StringW, len);
+ StringW[len]=0;
+ }
+ }
+}
+
+//---------------------------------------------------------------------------
+void StringField::WriteTypedData(uint8_t *data, size_t len)
+{
+ int pos=0;
+
+ if (StringW)
+ {
+ unsigned short c = (unsigned short)wcslen(StringW) * sizeof(wchar_t) + 2 /* for BOM */;
+ // write size
+ CHECK_SHORT(len);
+ PUT_SHORT(c); pos+=2;
+
+ // write byte order mark
+ CHECK_BIN(len, 2);
+ wchar_t BOM = 0xFEFF;
+ PUT_BINARY(data, (uint8_t *)&BOM, 2, pos);
+ pos+=2;
+ c-=2;
+
+ // write string
+ CHECK_BIN(len, c);
+ PUT_BINARY(data, (uint8_t *)StringW, c, pos);
+ }
+ else
+ {
+ CHECK_SHORT(len);
+ PUT_SHORT(0); /*pos+=2;*/
+ }
+}
+
+//---------------------------------------------------------------------------
+wchar_t *StringField::GetStringW(void)
+{
+ return StringW;
+}
+
+//---------------------------------------------------------------------------
+void StringField::SetStringW(const wchar_t *Str)
+{
+ if (!Str) return;
+
+ ndestring_release(StringW);
+ StringW = NULL;
+ StringW = ndestring_wcsdup(Str);
+ optimized_the=0;
+}
+
+//---------------------------------------------------------------------------
+void StringField::SetNDEString(wchar_t *Str)
+{
+ if (!Str) return;
+
+ // copy and then release, just in case we're copying into ourselves
+ wchar_t *oldStr = StringW;
+ StringW = Str;
+ ndestring_retain(StringW);
+ ndestring_release(oldStr);
+ optimized_the=0;
+}
+
+//---------------------------------------------------------------------------
+size_t StringField::GetDataSize(void)
+{
+ if (StringW)
+ {
+ return wcslen(StringW)*2 +2 /*for BOM*/ + 2 /*for byte length*/;
+ }
+ else
+ {
+ return 2;
+ }
+}
+
+//---------------------------------------------------------------------------
+int StringField::Compare(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+ return mywcsicmp(GetStringW(), ((StringField*)Entry)->GetStringW());
+}
+
+//---------------------------------------------------------------------------
+int StringField::Starts(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+ const wchar_t *p = ((StringField*)Entry)->GetStringW();
+ const wchar_t *d = GetStringW();
+ if (!d || !p) return 0;
+ return nde_wcsbegins(d, p);
+}
+
+//---------------------------------------------------------------------------
+int StringField::Contains(Field *Entry)
+{
+ if (!Entry) return -1;
+ if (Entry->GetType() != GetType()) return 0;
+ const wchar_t *p = ((StringField*)Entry)->GetStringW();
+ const wchar_t *d = GetStringW();
+ if (!d || !p) return 0;
+ return nde_wcscontains(GetStringW(), ((StringField*)Entry)->GetStringW());
+}
+
+Field *StringField::Clone(Table *pTable)
+{
+ StringField *clone = new StringField(StringW, STRING_IS_NDESTRING);
+ clone->Pos = FIELD_CLONE;
+ clone->ID = ID;
+ clone->MaxSizeOnDisk = (uint32_t)GetDataSize();
+ return clone;
+}
+
+// todo: make configurable words to skip, as well as trailing whitespace removal
+#define IsCharSpaceW(c) (c == L' ' || c == L'\t')
+inline bool IsTheW(const wchar_t *str) { if (str && (str[0] == L't' || str[0] == L'T') && (str[1] == L'h' || str[1] == L'H') && (str[2] == L'e' || str[2] == L'E') && (str[3] == L' ')) return true; else return false; }
+#define SKIP_THE_AND_WHITESPACEW(x) { wchar_t *save##x=(wchar_t*)x; while (IsCharSpaceW(*x) && *x) x++; if (IsTheW(x)) x+=4; while (IsCharSpaceW(*x)) x++; if (!*x) x=save##x; }
+
+bool StringField::ApplyFilter(Field *Data, int op)
+{
+ // TODO: maybe do this?
+
+ if (op == FILTER_ISEMPTY || op == FILTER_ISNOTEMPTY)
+ {
+ bool r = (op == FILTER_ISEMPTY);
+ if (!StringW)
+ return r;
+
+ if (StringW && StringW[0] == 0)
+ return r;
+
+ return !r;
+ }
+ //
+ bool r;
+ StringField *compField = (StringField *)Data;
+
+ const wchar_t *p = compField->GetStringW();
+ const wchar_t *d = GetStringW();
+ if (!p)
+ p = L"";
+ if (!d)
+ d = L"";
+
+ switch (op)
+ {
+ case FILTER_EQUALS:
+ r = !nde_wcsicmp(d, p);
+ break;
+ case FILTER_NOTEQUALS:
+ r = !!nde_wcsicmp(d, p);
+ break;
+ case FILTER_CONTAINS:
+ r = nde_wcscontains(d, p);
+ break;
+ case FILTER_NOTCONTAINS:
+ r = !nde_wcscontains(d, p);
+ break;
+ case FILTER_ABOVE:
+ r = (bool)(nde_wcsicmp(d, p) > 0);
+ break;
+ case FILTER_ABOVEOREQUAL:
+ r = (bool)(nde_wcsicmp(d, p) >= 0);
+ break;
+ case FILTER_BELOW:
+ r = (bool)(nde_wcsicmp(d, p) < 0);
+ break;
+ case FILTER_BELOWOREQUAL:
+ r = (bool)(nde_wcsicmp(d, p) <= 0);
+ break;
+ case FILTER_BEGINS:
+ r = nde_wcsbegins(d, p);
+ break;
+ case FILTER_ENDS:
+ r = nde_wcsends(d, p);
+ break;
+ case FILTER_LIKE:
+
+ if (compField->optimized_the)
+ p = compField->optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(p);
+ compField->optimized_the = p;
+ }
+
+ if (optimized_the)
+ d = optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(d);
+ optimized_the=d;
+ }
+
+ r = (bool)(nde_wcsicmp(d, p) == 0);
+ break;
+ case FILTER_BEGINSLIKE:
+
+ if (compField->optimized_the)
+ p = compField->optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(p);
+ compField->optimized_the = p;
+ }
+
+ if (optimized_the)
+ d = optimized_the;
+ else
+ {
+ SKIP_THE_AND_WHITESPACEW(d);
+ optimized_the=d;
+ }
+
+ r = nde_wcsbegins(d, p);
+ break;
+ default:
+ r = true;
+ break;
+ }
+ return r;
+}
diff --git a/Src/nde/win/StringField.h b/Src/nde/win/StringField.h
new file mode 100644
index 00000000..722ba0c2
--- /dev/null
+++ b/Src/nde/win/StringField.h
@@ -0,0 +1,50 @@
+/*
+---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+---------------------------------------------------------------------------
+*/
+
+/*
+---------------------------------------------------------------------------
+
+StringField Class Prototypes
+
+---------------------------------------------------------------------------
+*/
+
+#ifndef __STRINGFIELD_H
+#define __STRINGFIELD_H
+#include "../NDEString.h"
+
+class StringField : public Field
+{
+public:
+ StringField();
+ ~StringField();
+
+
+ StringField(const wchar_t *Str, int strkind=STRING_IS_WCHAR);
+ wchar_t *GetStringW(void);
+ void SetStringW(const wchar_t *Str);
+ void SetNDEString(wchar_t *Str);
+
+protected:
+ virtual void ReadTypedData(const uint8_t *, size_t len);
+ virtual void WriteTypedData(uint8_t *, size_t len);
+ virtual size_t GetDataSize(void);
+ virtual int Compare(Field *Entry);
+ virtual int Starts(Field *Entry);
+ virtual int Contains(Field *Entry);
+
+ virtual bool ApplyFilter(Field *Data, int op);
+ virtual Field *Clone(Table *pTable);
+ void InitField(void);
+
+ wchar_t *StringW;
+ const wchar_t *optimized_the;
+};
+
+#endif
+
diff --git a/Src/nde/win/Table.cpp b/Src/nde/win/Table.cpp
new file mode 100644
index 00000000..0631ec27
--- /dev/null
+++ b/Src/nde/win/Table.cpp
@@ -0,0 +1,886 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Table Class
+
+--------------------------------------------------------------------------- */
+#include "../nde.h"
+//#include <direct.h>
+#include <stdio.h>
+#include <string.h>
+#include "../CRC.H"
+#include <strsafe.h>
+
+const char *tSign="NDETABLE";
+
+//---------------------------------------------------------------------------
+Table::Table( const wchar_t *TableName, const wchar_t *Idx, BOOL Create, Database *_db, BOOL _Cached ) : Scanner( this )
+{
+ memset( column_ids, FIELD_UNKNOWN, 255 );
+
+ Cached = _Cached;
+ db = _db;
+ AutoCreate = Create;
+ Name = ndestring_wcsdup( TableName );
+ IdxName = ndestring_wcsdup( Idx );
+
+ Init();
+}
+
+//---------------------------------------------------------------------------
+void Table::Init()
+{
+ numErrors = 0;
+ Scanners = new LinkedList();
+ // benski> cut: Handle=NULL;
+ IdxHandle = NULL;
+ FieldsRecord = NULL;
+ IndexList = NULL;
+ GLocateUpToDate = FALSE;
+}
+
+//---------------------------------------------------------------------------
+Table::~Table()
+{
+ Reset();
+ if (Handle) // Reset doesn'l_data_type completely destroy Handle
+ Vfdestroy(Handle);
+ Handle = 0;
+
+ ndestring_release(Name);
+ ndestring_release(IdxName);
+}
+
+//---------------------------------------------------------------------------
+void Table::Reset()
+{
+ if ( IndexList )
+ IndexList->Release();
+
+ IndexList = 0;
+
+ if ( FieldsRecord )
+ FieldsRecord->Release();
+
+ FieldsRecord = 0;
+
+ delete Scanners;
+ Scanners = NULL;
+
+ if ( Handle )
+ Vfclose( Handle ); // close (but don'l_data_type destroy) to keep mutex open.
+ if ( IdxHandle )
+ Vfdestroy( IdxHandle );
+
+ IdxHandle = NULL;
+
+ for ( RowCache::iterator itr = rowCache.begin(); itr != rowCache.end(); ++itr )
+ {
+ if ( itr->second )
+ itr->second->Release();
+ }
+
+ rowCache.clear();
+
+ memset( column_ids, FIELD_UNKNOWN, 255 );
+ columns_cached = false;
+}
+
+struct IndexNewWalkerContext
+{
+ IndexNewWalkerContext(Table *_table)
+ {
+ N = -1;
+ table = _table;
+ }
+ int N;
+ Table *table;
+};
+
+bool Table::IndexNewWalker(IndexRecord *record, Field *entry, void *context_in)
+{
+ IndexNewWalkerContext *context = (IndexNewWalkerContext *)context_in;
+ IndexField *p = (IndexField *)entry;
+ p->index = new Index(context->table->IdxHandle, p->ID, context->N++, p->Type, FALSE, 0, context->table);
+ return true;
+}
+
+//---------------------------------------------------------------------------
+BOOL Table::Open(void)
+{
+ int justcreated = 0;
+
+ if (!Handle)
+ Handle = Vfnew(Name, "r+b", Cached);
+ if (!Handle) return FALSE;
+ if (!Vflock(Handle))
+ {
+ Vfdestroy(Handle);
+ Handle = 0;
+ return FALSE;
+ }
+
+ Handle = Vfopen(Handle, Name, "r+b", Cached);
+ IdxHandle = Vfopen(0, IdxName, "r+b", TRUE);
+ BOOL Valid = (Handle && IdxHandle);
+
+ // unlock
+ if (Valid || !AutoCreate)
+ {
+ //if (Handle)
+ //Vfunlock(Handle);
+ }
+ else
+ {
+ if (Handle)
+ {
+ Vfclose(Handle);
+ if (IdxHandle)
+ Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ }
+ else
+ {
+ if (IdxHandle)
+ Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ Handle = Vfnew(Name, "w+b", Cached);
+ if (!Vflock(Handle))
+ {
+ Vfdestroy(Handle);
+ return FALSE;
+ }
+ }
+
+ Handle = Vfopen(Handle, Name, "w+b", Cached);
+ IdxHandle = Vfopen(0, IdxName, "w+b", TRUE);
+ Valid = (Handle && IdxHandle);
+
+ if (Valid)
+ {
+ Vfwrite(__TABLE_SIGNATURE__, strlen(__TABLE_SIGNATURE__), Handle);
+ Vfwrite(__INDEX_SIGNATURE__, strlen(__TABLE_SIGNATURE__), IdxHandle);
+ // TODO bensk> change if NUM_SPECIAL_RECORDS ever increases
+ int v=NUM_SPECIAL_RECORDS;//strlen(__TABLE_SIGNATURE__);
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ // v = 0; fwrite(&v, sizeof(v), 1, IdxHandle);
+ v = -1; Vfwrite(&v, sizeof(v), IdxHandle); // write ID
+ v = 0;
+ for (int i=0;i<NUM_SPECIAL_RECORDS;i++)
+ {
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ Vfwrite(&v, sizeof(v), IdxHandle);
+ }
+ Sync();
+ justcreated = 1;
+ }
+ }
+
+ if (!Valid)
+ {
+ if (Handle) Vfdestroy(Handle);
+ if (IdxHandle) Vfdestroy(IdxHandle);
+ Handle = NULL;
+ IdxHandle = NULL;
+ }
+ else
+ {
+ char test1[9]={0};
+ char test2[9]={0};
+
+ Vfseek(Handle, 0, SEEK_SET);
+ Vfread(test1, strlen(__TABLE_SIGNATURE__), Handle);
+ Vfseek(IdxHandle, 0, SEEK_SET);
+ Vfread(test2, strlen(__INDEX_SIGNATURE__), IdxHandle);
+ test1[8]=0;
+ test2[8]=0;
+ if (strcmp(test1, __TABLE_SIGNATURE__) || strcmp(test2, __INDEX_SIGNATURE__))
+ {
+ if (Handle) Vfdestroy(Handle);
+ Handle = 0;
+ if (IdxHandle) Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ return FALSE;
+ }
+
+ // Load default index
+ IndexField *l_index_field = new IndexField(PRIMARY_INDEX, -1, -1, L"None");
+ l_index_field->index = new Index(IdxHandle, PRIMARY_INDEX, -1, -1, FALSE, 0, this);
+
+ // Get indexes
+ int Ptr = l_index_field->index->Get(INDEX_RECORD_NUM);
+ IndexList = new IndexRecord(Ptr, INDEX_RECORD_NUM, Handle, this);
+ if (!IndexList)
+ {
+ delete l_index_field;
+ if (Handle) Vfdestroy(Handle);
+ Handle = 0;
+ if (IdxHandle) Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ return FALSE;
+ }
+
+ // Init them
+ IndexNewWalkerContext newContext(this);
+ IndexList->WalkFields(IndexNewWalker, &newContext);
+
+ // Add default in case its not there (if it is it won'l_data_type be added by addfield)
+ IndexList->AddField(l_index_field);
+
+ // Get the default index (whether loaded or preloaded)
+ Scanner::index = ((IndexField*)IndexList->GetField(PRIMARY_INDEX))->index;
+
+ // If it's different from preloaded, delete preloaded
+ if (l_index_field->index != Scanner::index)
+ {
+ delete l_index_field;
+ l_index_field = NULL;
+ }
+
+ // Set up colaboration
+ IndexList->BuildCollaboration();
+
+ // Get columns
+ Ptr = Scanner::index->Get(FIELDS_RECORD_NUM);
+ FieldsRecord = new Record(Ptr, FIELDS_RECORD_NUM, Handle, this);
+ if (!FieldsRecord)
+ {
+ IndexList->Release();
+ IndexList=0;
+ if (Handle) Vfdestroy(Handle);
+ Handle = 0;
+ if (IdxHandle) Vfdestroy(IdxHandle);
+ IdxHandle = 0;
+ return FALSE;
+ }
+
+ // update the column cache
+ FieldsRecord->WalkFields(BuildColumnCache, this);
+ columns_cached=true;
+ }
+
+ if (Valid && !justcreated)
+ {
+ if (IndexList->NeedFix())
+ Compact();
+ }
+
+ if (Valid) First();
+ if (Handle)
+ Vfunlock(Handle);
+ return Valid;
+}
+
+//---------------------------------------------------------------------------
+void Table::Close(void)
+{
+ int v=0;
+
+ if (Handle && IndexList && Vflock(Handle, 0))
+ {
+ IndexList->WalkFields(IndexWriteWalker, 0);
+ }
+
+ delete Scanners;
+ Scanners = NULL;
+
+ Vsync(Handle);
+ if (IdxHandle)
+ {
+ Vfdestroy(IdxHandle);
+ IdxHandle = NULL;
+ v |= 2;
+ }
+ if (Handle)
+ {
+ Vfdestroy(Handle);
+ Handle = NULL;
+ v |= 1;
+ }
+
+ if (v != 3)
+ return;
+}
+
+bool Table::IndexWriteWalker(IndexRecord *record, Field *entry, void *context)
+{
+ IndexField *field = (IndexField *)entry;
+ field->index->WriteIndex();
+ return true;
+}
+
+//---------------------------------------------------------------------------
+void Table::Sync(void)
+{
+ if (!Vflock(Handle))
+ return;
+
+ if (IndexList)
+ IndexList->WalkFields(IndexWriteWalker, 0);
+
+ int err=0;
+ if (!err && Handle) err|=Vsync(Handle);
+ if (!err && IdxHandle) Vsync(IdxHandle);
+
+ Vfunlock(Handle);
+}
+
+//---------------------------------------------------------------------------
+ColumnField *Table::NewColumn( unsigned char FieldID, const wchar_t *FieldName, unsigned char FieldType, BOOL indexUnique )
+{
+ columns_cached = false; // if they start writing new columns, kill the columns cache until they PostColumns()
+ ColumnField *l_column_field = GetColumnById( FieldID );
+ if ( l_column_field )
+ {
+ int l_data_type = l_column_field->GetDataType();
+ if ( l_data_type != FieldType )
+ {
+ OutputDebugStringW( L"column " );
+ OutputDebugStringW( FieldName );
+ OutputDebugStringW( L" already exists but is of the wrong type\n" );
+
+ if ( CompatibleFields( l_data_type, FieldType ) )
+ {
+ OutputDebugStringW( L"going from one equivalent type to another, converting column\n" );
+ l_column_field->SetDataType( FieldType );
+ goto aok;
+ }
+ }
+
+ l_column_field->SetFieldName( (wchar_t *)FieldName );
+ return NULL;
+ }
+aok:
+ if ( GetColumnByName( FieldName ) )
+ return NULL;
+
+ ColumnField *l_new_column_field = new ColumnField( FieldID, FieldName, FieldType, this );
+ column_ids[ FieldID ] = FieldType;
+ FieldsRecord->AddField( l_new_column_field );
+
+ return l_new_column_field;
+}
+
+void Table::SetFieldSearchableById(unsigned char field_id, bool searchable)
+{
+ ColumnField *column = GetColumnById(field_id);
+ if (column)
+ column->SetSearchable(searchable);
+ if (searchable)
+ {
+ search_fields.insert(field_id);
+ }
+ else
+ {
+ search_fields.erase(field_id);
+ }
+}
+
+//---------------------------------------------------------------------------
+bool Table::BuildColumnCache(Record *record, Field *entry, void *context)
+{
+ Table *table = (Table *)context;
+ ColumnField *column = (ColumnField *)entry;
+ unsigned char field_id=column->GetFieldId();
+ table->column_ids[field_id] = column->GetDataType();
+
+ if (column->IsSearchableField())
+ {
+ table->search_fields.insert(field_id);
+ }
+ else
+ {
+ table->search_fields.erase(field_id);
+ }
+ return true;
+}
+
+//---------------------------------------------------------------------------
+void Table::PostColumns(void)
+{
+ FieldsRecord->WriteFields(this, FIELDS_RECORD_NUM);
+ memset(column_ids, FIELD_UNKNOWN, 255);
+ FieldsRecord->WalkFields(BuildColumnCache, this);
+ columns_cached=true;
+}
+
+unsigned char Table::GetColumnType(unsigned char Id)
+{
+ if (columns_cached)
+ {
+ return column_ids[Id];
+ }
+ else
+ {
+ return GetColumnById(Id)->GetDataType();
+ }
+}
+
+//---------------------------------------------------------------------------
+IndexField *Table::GetIndexByName(const wchar_t *name)
+{
+ if (!IndexList)
+ return NULL;
+ return IndexList->GetIndexByName(name);
+}
+
+//---------------------------------------------------------------------------
+IndexField *Table::GetIndexById(unsigned char Id)
+{
+ if (!IndexList)
+ return NULL;
+ return (IndexField *)IndexList->GetField(Id);
+}
+
+//---------------------------------------------------------------------------
+void Table::AddIndexByName(const wchar_t *name, const wchar_t *desc)
+{
+ ColumnField *header = GetColumnByName(name);
+ if (header)
+ {
+ unsigned char Idx = header->ID;
+ AddIndexById(Idx, desc);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Table::AddIndexById( unsigned char Id, const wchar_t *desc )
+{
+ if ( GetIndexById( Id ) )
+ return;
+
+ ColumnField *l_column_field = GetColumnById( Id );
+ if ( !l_column_field )
+ return;
+
+ IndexField *l_new_index_field = new IndexField( Id, IndexList->GetColumnCount(), l_column_field->GetDataType(), desc );
+ l_new_index_field->index = new Index( IdxHandle, Id, IndexList->GetColumnCount(), l_column_field->GetDataType(), TRUE, Scanner::index->NEntries, this );
+
+ IndexField* l_previous_index_field = dynamic_cast<IndexField*>(IndexList->GetLastField());
+
+ IndexList->AddField( l_new_index_field );
+
+ //IndexField* l_previous_index_field = (IndexField*)l_new_index_field->prev;
+ if (l_previous_index_field)
+ {
+ l_previous_index_field->index->Colaborate(l_new_index_field);
+ }
+
+ IndexField *l_primary_index_field = (IndexField *)IndexList->GetField( PRIMARY_INDEX );
+ if (l_primary_index_field)
+ {
+ l_new_index_field->index->Colaborate(l_primary_index_field);
+ }
+
+ if (l_previous_index_field)
+ {
+ l_previous_index_field->index->Propagate();
+ }
+
+ IndexList->WriteFields( this );
+}
+
+//---------------------------------------------------------------------------
+BOOL Table::CheckIndexing(void)
+{
+ if (IndexList->GetColumnCount() == 0) return TRUE;
+
+ for (int i=0;i<Scanner::index->NEntries;i++)
+ {
+ if (!IndexList->CheckIndexing(i))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+struct IndexWalkerThunkContext
+{
+ void *context;
+ Table *_this;
+ Table::IndexWalker callback;
+};
+
+bool Table::IndexWalkerThunk(IndexRecord *record, Field *entry, void *context_in)
+{
+ IndexWalkerThunkContext *context = (IndexWalkerThunkContext *)context_in;
+ return context->callback(context->_this, (IndexField *)entry, context->context);
+}
+
+//---------------------------------------------------------------------------
+void Table::WalkIndices(IndexWalker callback, void *context)
+{
+ if (IndexList && callback)
+ {
+ IndexWalkerThunkContext walkerContext = { context, this, callback };
+ IndexList->WalkFields(IndexWalkerThunk, &walkerContext);
+ }
+}
+
+//---------------------------------------------------------------------------
+void Table::DropIndex(IndexField *Ptr)
+{
+ if (!Ptr || Ptr->Type != FIELD_INDEX) return;
+ if (Scanner::index == Ptr->index)
+ {
+ Scanner::index = ((IndexField*)IndexList->GetField(PRIMARY_INDEX))->index;
+ IndexList->BuildCollaboration();
+ }
+ IndexList->RemoveField(Ptr);
+ if (Scanner::index->SecIndex == Ptr)
+ Scanner::index->SecIndex = 0;
+ IndexList->WriteFields(this);
+}
+
+//---------------------------------------------------------------------------
+void Table::DropIndexByName(const wchar_t *desc)
+{
+ IndexField *indx = GetIndexByName(desc);
+ if (!_wcsicmp(desc, L"None")) return;
+
+ if (indx)
+ DropIndex(indx);
+}
+
+//---------------------------------------------------------------------------
+void Table::DropIndexById(unsigned char Id)
+{
+ if (!IndexList)
+ return;
+ if (Id == (unsigned char)PRIMARY_INDEX) return;
+ IndexField *indx=(IndexField *)IndexList->GetField(Id);
+ if (indx)
+ DropIndex(indx);
+}
+
+//---------------------------------------------------------------------------
+BOOL Table::LocateByIdEx(int Id, int From, Field *field, int comp_mode)
+{
+ return Scanner::LocateByIdEx(Id, From, field, NULL, comp_mode);
+}
+
+//---------------------------------------------------------------------------
+Record *Table::GetColumns(void)
+{
+ if (!FieldsRecord)
+ return NULL;
+ return FieldsRecord;
+}
+
+//---------------------------------------------------------------------------
+Scanner *Table::NewScanner()
+{
+ Scanner *s = new Scanner(this);
+ /*if (Scanners->GetNElements() > 0)*/
+ s->index = Scanner::index;
+ Scanners->AddEntry(s, TRUE);
+ return s;
+}
+
+//---------------------------------------------------------------------------
+Scanner *Table::GetDefaultScanner()
+{
+ return this;
+}
+
+//---------------------------------------------------------------------------
+void Table::DeleteScanner(Scanner *scan)
+{
+ if (!scan) return;
+ Scanners->RemoveEntry(scan);
+}
+
+//---------------------------------------------------------------------------
+void Table::IndexModified(void)
+{
+ Scanner *s = (Scanner *)Scanners->GetHead();
+ while (s)
+ {
+ s->IndexModified();
+ s = (Scanner *)s->GetNext();
+ }
+}
+
+//---------------------------------------------------------------------------
+void Table::SetGlobalLocateUpToDate(BOOL is) {
+ GLocateUpToDate = is;
+}
+
+struct ColumnWalkContext
+{
+ Table *ctable;
+};
+
+bool Table::Compact_ColumnWalk(Record *record, Field *entry, void *context_in)
+{
+ ColumnField *field = static_cast<ColumnField *>(entry);
+ ColumnWalkContext *context = (ColumnWalkContext *)context_in;
+ Table *ctable = context->ctable;
+
+ ctable->NewColumn(field->GetFieldId(), field->GetFieldName(), field->GetDataType(), FALSE);
+ return true;
+}
+
+struct ColumnWalk2Context
+{
+ Table *ctable;
+ Table *thisTable;
+ uint8_t *data;
+ size_t data_size;
+ int gotstuff;
+};
+
+bool Table::Compact_ColumnWalk2(Record *record, Field *entry, void *context_in)
+{
+ ColumnField *colfield = (ColumnField *)entry;
+ ColumnWalk2Context *context = (ColumnWalk2Context *)context_in;
+
+ unsigned char fieldid = colfield->GetFieldId();
+ //char *fieldname = colfield->GetFieldName();
+ Field *mfield = context->thisTable->GetFieldById(fieldid);
+ //Field *mfield = GetFieldByName(fieldname);
+ if (mfield != NULL) {
+ if (!context->gotstuff) {
+ context->ctable->New();
+ context->gotstuff = 1;
+ }
+ Field *cfield = context->ctable->NewFieldById(fieldid, 0);
+ //Field *cfield = ctable->NewFieldByName(fieldname, mfield->GetPerm());
+ size_t len = mfield->GetDataSize();
+ if (len > context->data_size)
+ {
+ size_t old_data_size = context->data_size;
+ context->data_size = len;
+ uint8_t *new_data = (uint8_t *)realloc(context->data, context->data_size);
+ if (new_data)
+ {
+ context->data = new_data;
+ }
+ else
+ {
+ new_data = (uint8_t *)malloc(context->data_size);
+ if (new_data)
+ {
+ memcpy(new_data, context->data, old_data_size);
+ free(context->data);
+ context->data = new_data;
+ }
+ else
+ {
+ context->data_size = old_data_size;
+ return false;
+ }
+ }
+ }
+ mfield->WriteTypedData(context->data, len);
+ cfield->ReadTypedData(context->data, len);
+ }
+
+ return true;
+}
+
+bool Table::Compact_IndexWalk(Table *table, IndexField *field, void *context)
+{
+ Table *ctable = (Table *)context;
+
+ if (_wcsicmp(field->GetIndexName(), L"None"))
+ ctable->AddIndexById(field->GetFieldId(), field->GetIndexName());
+ return true;
+}
+//---------------------------------------------------------------------------
+void Table::Compact(int *progress) {
+ // ok so we're gonna be cheating a bit, instead of figuring out how to safely modify all those
+ // nifty indexes that cross reference themselves and blablabla, we're just gonna duplicate the
+ // whole table from scratch, overwrite ourselves, and reopen the table. duh.
+
+ if (!Vflock(Handle))
+ {
+ if (progress != NULL) *progress = 100;
+ return;
+ }
+ // create a temporary table in windows temp dir
+ wchar_t temp_table[MAX_PATH+12] = {0};
+ wchar_t temp_index[MAX_PATH+12] = {0};
+ wchar_t old_table[MAX_PATH+12] = {0};
+ wchar_t old_index[MAX_PATH+12] = {0};
+ DWORD pid=GetCurrentProcessId();
+
+ StringCbPrintfW(temp_table, sizeof(temp_table), L"%s.new%08X", Name,pid);
+ StringCbPrintfW(temp_index, sizeof(temp_index),L"%s.new%08X", IdxName,pid);
+ StringCbPrintfW(old_table, sizeof(old_table),L"%s.old%08X", Name,pid);
+ StringCbPrintfW(old_index, sizeof(old_index),L"%s.old%08X", IdxName,pid);
+
+ // delete them, in case we crashed while packing
+
+ DeleteFileW(temp_table);
+ DeleteFileW(temp_index);
+ DeleteFileW(old_table);
+ DeleteFileW(old_index);
+
+ // create a brand new db and a brand new table
+ Table *ctable = db->OpenTable(temp_table, temp_index, NDE_OPEN_ALWAYS, Cached);
+
+ // duplicate the columns
+ Record *record = GetColumns();
+ if (record != NULL)
+ {
+ ColumnWalkContext context;
+ context.ctable = ctable;
+ record->WalkFields(Compact_ColumnWalk, &context);
+ }
+ ctable->PostColumns();
+
+ // duplicate the indexes
+ WalkIndices(Compact_IndexWalk, (void *)ctable);
+
+ // duplicate the data
+ int reccount = GetRecordsCount();
+
+ int count = 0;
+ First();
+ ColumnWalk2Context context;
+ context.data_size = 65536;
+ context.data = (uint8_t *)calloc(65536, sizeof(uint8_t));
+ context.ctable = ctable;
+ context.thisTable = this;
+
+ while (1) {
+ int lasterr = NumErrors();
+ GetDefaultScanner()->GetRecordById(count, FALSE);
+ count++;
+
+ if (Eof() || count > reccount) break;
+
+ if (NumErrors() > lasterr)
+ continue;
+
+ Index *idx = GetDefaultScanner()->GetIndex();
+ int pos = idx->Get(GetDefaultScanner()->GetRecordId());
+
+ if (pos == 0) continue;
+
+ int pr = (int)((float)GetRecordId()/(float)reccount*100.0f);
+ if (progress != NULL) *progress = pr;
+ context.gotstuff = 0;
+
+ if (record != NULL)
+ record->WalkFields(Compact_ColumnWalk2, &context);
+
+ if (context.gotstuff)
+ ctable->Post();
+ }
+ free(context.data);
+
+ // done creating temp table
+ db->CloseTable(ctable);
+
+ // reset the data structures and close underlying file handles
+ Reset();
+
+ if (MoveFileW(Name,old_table))
+ {
+ if (MoveFileW(IdxName,old_index))
+ {
+ if (!MoveFileW(temp_table,Name) || !MoveFileW(temp_index,IdxName))
+ {
+ // failed, try to copy back
+ DeleteFileW(Name);
+ DeleteFileW(IdxName);
+ MoveFileW(old_table,Name); // restore old file
+ MoveFileW(old_index,IdxName); // restore old file
+ }
+ }
+ else
+ {
+ MoveFileW(old_table,Name); // restore old file
+ }
+ }
+
+ // clean up our temp files
+ DeleteFileW(temp_table);
+ DeleteFileW(temp_index);
+ DeleteFileW(old_table);
+ DeleteFileW(old_index);
+
+ if (progress != NULL) *progress = 100;
+
+ // reopen our table
+ Init();
+ Open();
+}
+
+ColumnField *Table::GetColumnById(unsigned char Idx)
+{
+ if (!FieldsRecord)
+ return NULL;
+ return (ColumnField *)FieldsRecord->GetField(Idx);
+}
+
+ColumnField *Table::GetColumnByName(const wchar_t *FieldName)
+{
+ return FieldsRecord->GetColumnByName(FieldName);
+}
+
+void Table::RowCache_Delete(int position)
+{
+ if (use_row_cache)
+ {
+ RowCache::iterator found = rowCache.find(position);
+ if (found != rowCache.end())
+ {
+ if (found->second)
+ found->second->Release();
+ rowCache.erase(found);
+ }
+ }
+}
+
+void Table::RowCache_Remove(int position)
+{
+ if (use_row_cache)
+ {
+ Record *&row = rowCache[position];
+ if (row)
+ {
+ row->Release();
+ }
+
+ rowCache[position] = 0;
+ }
+}
+
+void Table::RowCache_Add(Record *record, int position)
+{
+ if (use_row_cache)
+ {
+ record->Retain();
+
+ Record *&row = rowCache[position];
+ if (row)
+ {
+ row->Release();
+ }
+
+ rowCache[position] = record;
+ }
+}
+
+Record *Table::RowCache_Get(int position)
+{
+ if (!use_row_cache || 0 == rowCache.count(position))
+ return 0;
+
+ Record *row = rowCache[position];
+ if (row)
+ row->Retain();
+ return row;
+}
+
+void Table::EnableRowCache()
+{
+ use_row_cache=true;
+} \ No newline at end of file
diff --git a/Src/nde/win/Table.h b/Src/nde/win/Table.h
new file mode 100644
index 00000000..02dc6990
--- /dev/null
+++ b/Src/nde/win/Table.h
@@ -0,0 +1,170 @@
+/* ---------------------------------------------------------------------------
+ Nullsoft Database Engine
+ --------------------
+ codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+ Table Class Prototypes
+ Windows implementation
+--------------------------------------------------------------------------- */
+
+#ifndef __TABLE_H
+#define __TABLE_H
+
+#include <stdio.h>
+//#include <io.h>
+#include "../Scanner.h"
+#include <map>
+#include "../IndexRecord.h"
+#include <assert.h>
+
+class Table : private Scanner
+{
+public:
+ // TODO: move these back to protected
+ VFILE *Handle = NULL;
+ using Scanner::index;
+ bool use_row_cache = false;
+ BOOL GLocateUpToDate = FALSE;
+
+private:
+ void Init();
+ void Reset();
+
+private:
+ LinkedList *Scanners;
+
+protected:
+ wchar_t *Name;
+ wchar_t *IdxName;
+
+ VFILE *IdxHandle = NULL;
+ BOOL AutoCreate;
+ Record *FieldsRecord = NULL;
+ IndexRecord *IndexList = NULL;
+ Database *db;
+ BOOL Cached;
+ int numErrors = 0;
+ using Scanner::Edition;
+ bool columns_cached = false;
+ unsigned char column_ids[256];
+ typedef std::map<int, Record*> RowCache;
+ RowCache rowCache;
+
+ // Tables
+ static bool Compact_ColumnWalk(Record *record, Field *entry, void *context_in);
+ static bool Compact_ColumnWalk2(Record *record, Field *entry, void *context_in);
+ static bool Compact_IndexWalk(Table *table, IndexField *entry, void *context);
+ static bool IndexWriteWalker(IndexRecord *record, Field *entry, void *context);
+ static bool IndexWalkerThunk(IndexRecord *record, Field *entry, void *context);
+ static bool IndexNewWalker(IndexRecord *record, Field *entry, void *context);
+ static bool BuildColumnCache(Record *record, Field *entry, void *context);
+
+public:
+ typedef bool (*IndexWalker)(Table *table, IndexField *entry, void *context);
+ Table(const wchar_t *TableName, const wchar_t *IdxName, BOOL Create, Database *db, BOOL Cached);
+ ~Table();
+ BOOL Open(void);
+ void Close(void);
+
+ // Columns
+ ColumnField *NewColumn(unsigned char Id, const wchar_t *name, unsigned char type, BOOL indexUniques);
+
+ void DeleteColumn(ColumnField *field); // todo
+ void DeleteColumnByName(const wchar_t *name); // todo
+ void DeleteColumnById(unsigned char Id); // todo
+ void PostColumns(void);
+ NDE_API Record *GetColumns(void);
+ ColumnField *GetColumnByName(const wchar_t *FieldName);
+ ColumnField *GetColumnById(unsigned char Idx);
+ unsigned char GetColumnType(unsigned char Id);
+
+ // Fields
+ using Scanner::NewFieldByName;
+ using Scanner::NewFieldById;
+ using Scanner::GetFieldByName;
+ using Scanner::GetFieldById;
+ using Scanner::DeleteField;
+ using Scanner::DeleteFieldByName;
+ using Scanner::DeleteFieldById;
+
+ // Records
+ using Scanner::First;
+ using Scanner::Last;
+ using Scanner::Next;
+ using Scanner::Previous;
+ using Scanner::Eof;
+ using Scanner::Bof;
+ using Scanner::New;
+ using Scanner::Insert;
+ using Scanner::Edit;
+ using Scanner::Cancel;
+ using Scanner::Post;
+ using Scanner::Delete;
+ using Scanner::GetRecordsCount;
+ using Scanner::GetRecordById;
+ using Scanner::GetRecordId;
+ void Sync(void);
+ using Scanner::LocateByName;
+ using Scanner::LocateById;
+ BOOL LocateByIdEx(int Id, int From, Field *field, int comp_mode);
+
+ // Indexes
+ void AddIndexByName(const wchar_t *FieldName, const wchar_t *KeyName);
+ void AddIndexById(unsigned char Id, const wchar_t *KeyName);
+
+ void WalkIndices(IndexWalker callback, void *context);
+
+ IndexField *GetIndexByName(const wchar_t *name);
+ IndexField *GetIndexById(unsigned char Id);
+ using Scanner::SetWorkingIndexByName;
+ using Scanner::SetWorkingIndexById;
+ NDE_API BOOL CheckIndexing(void);
+ void DropIndexByName(const wchar_t *desc);
+ void DropIndexById(unsigned char Id);
+ void DropIndex(IndexField *Ptr);
+ void IndexModified(void);
+
+ // Filters
+ using Scanner::AddFilterByName;
+ using Scanner::AddFilterById;
+ using Scanner::AddFilterOp;
+ using Scanner::RemoveFilters;
+
+ // Scanners
+ Scanner *NewScanner();
+ Scanner *GetDefaultScanner();
+ void DeleteScanner(Scanner *scan);
+
+ // Misc
+ using Scanner::FragmentationLevel;
+ void Compact(int *progress = NULL);
+ void SetGlobalLocateUpToDate(BOOL is);
+
+ // Row Cache
+ void RowCache_Delete(int position);
+ void RowCache_Remove(int position);
+ void RowCache_Add(Record *record, int position);
+ Record *RowCache_Get(int position);
+ NDE_API void EnableRowCache();
+
+ // Searching
+ void SetFieldSearchableById(unsigned char field_id, bool searchable);
+
+ int HasErrors()
+ {
+ return numErrors > 0;
+ }
+ int NumErrors()
+ {
+ return numErrors;
+ }
+ void IncErrorCount()
+ {
+ numErrors++;
+ }
+};
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/Vfs.cpp b/Src/nde/win/Vfs.cpp
new file mode 100644
index 00000000..3c5daf81
--- /dev/null
+++ b/Src/nde/win/Vfs.cpp
@@ -0,0 +1,599 @@
+/* ---------------------------------------------------------------------------
+Nullsoft Database Engine
+--------------------
+codename: Near Death Experience
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+
+Virtual File System
+
+--------------------------------------------------------------------------- */
+#include "../nde.h"
+
+#include "vfs.h"
+#include <malloc.h>
+#ifndef EOF
+#define EOF -1
+#endif
+#include <Sddl.h>
+#include <strsafe.h>
+
+#if defined(NDE_ALLOW_NONCACHED)
+size_t ReadFileN(void *buffer, size_t size, VFILE *f)
+{
+ uint8_t *b = (uint8_t *) buffer;
+ size_t total_size=0;
+ while (size)
+ {
+ DWORD bytesRead = 0;
+ DWORD toRead = min(0xffffffffUL, size);
+ ReadFile(f->hfile, b, toRead, &bytesRead, NULL);
+ if (bytesRead != toRead)
+ {
+ f->endoffile=true;
+ // TODO: rewind
+ return total_size+bytesRead;
+ }
+ size-=toRead;
+ b+=toRead;
+ total_size+=toRead;
+ }
+ return total_size;
+}
+
+size_t WriteFileN(void *buffer, size_t size, VFILE *f)
+{
+ uint8_t *b = (uint8_t *) buffer;
+ size_t total_size=0;
+ while (size)
+ {
+ DWORD bytesRead = 0;
+ DWORD toRead = min(0xffffffffUL, size);
+ WriteFile(f->hfile, b, toRead, &bytesRead, NULL);
+ if (bytesRead != toRead)
+ {
+ f->endoffile=true;
+ // TODO: rewind
+ return total_size+bytesRead;
+ }
+ size-=toRead;
+ b+=toRead;
+ total_size+=toRead;
+ }
+
+ return total_size;
+}
+#endif
+
+// benski> i havn't the slightest fucking clue why this works, it's copypasta code from the internets
+static LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
+static bool GetLowIntegrity(SECURITY_ATTRIBUTES *attributes)
+{
+ PSECURITY_DESCRIPTOR pSD = NULL;
+
+ if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
+ LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
+ {
+ attributes->nLength = sizeof(SECURITY_ATTRIBUTES);
+ attributes->lpSecurityDescriptor = pSD;
+ attributes->bInheritHandle = FALSE;
+
+ //LocalFree ( pSD );
+ return true;
+ }
+
+ return false;
+}
+
+VFILE *Vfnew(const wchar_t *fl, const char *mode, BOOL Cached)
+{
+ if (!fl) return NULL;
+ VFILE *f = (VFILE *)calloc(1, sizeof(VFILE));
+ if (!f) return NULL;
+
+#ifdef NDE_ALLOW_NONCACHED
+ if (!Cached)
+ {
+ f->r.reserve(256); // heuristically determined
+ }
+ f->hfile = INVALID_HANDLE_VALUE;
+#endif
+
+#ifndef NO_TABLE_WIN32_LOCKING
+ // TODO: should we retrieve a better filename, e.g. GetLongPathName, or GetFinalPathNameByHandle on vista+
+ wchar_t mutex_name[1024] = {0};
+ StringCbPrintfW(mutex_name, sizeof(mutex_name), L"Global\\nde-%s", fl);
+
+ CharLowerW(mutex_name+7);
+ wchar_t *sw = mutex_name+7;
+ wchar_t *has_extension=0;
+ while (sw && *sw)
+ {
+ if (*sw == L'\\')
+ {
+ has_extension=0;
+ *sw = L'/';
+ }
+ else if (*sw == L'.')
+ has_extension=sw;
+ sw++;
+ }
+ if (has_extension)
+ *has_extension = 0;
+
+ SECURITY_ATTRIBUTES attr = {0};
+ if (GetLowIntegrity(&attr))
+ {
+ f->mutex = CreateMutexW(&attr, FALSE, mutex_name);
+ LocalFree(attr.lpSecurityDescriptor);
+ }
+ else
+ f->mutex = CreateMutexW(0, FALSE, mutex_name);
+
+#endif
+ return f;
+}
+
+//----------------------------------------------------------------------------
+VFILE *Vfopen(VFILE *f, wchar_t *fl, const char *mode, BOOL Cached)
+{
+ if (!fl) return NULL;
+ if (!f)
+ {
+ f = Vfnew(fl, mode, Cached);
+ if (!f)
+ return NULL;
+ }
+
+#ifdef NDE_ALLOW_NONCACHED
+ f->cached = Cached;
+#else
+ f->cached = TRUE;
+#endif
+
+ if (!strchr(mode, '+'))
+ {
+ if (strchr(mode, 'r'))
+ f->mode = VFS_READ | VFS_MUSTEXIST;
+ if (strchr(mode, 'w'))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
+ if (strchr(mode, 'a'))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
+ }
+ else
+ {
+ if (strstr(mode, "r+"))
+ f->mode = VFS_WRITE | VFS_MUSTEXIST;
+ if (strstr(mode, "w+"))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_NEWCONTENT;
+ if (strstr(mode, "a+"))
+ f->mode = VFS_WRITE | VFS_CREATE | VFS_SEEKEOF;
+ }
+
+ if (f->mode == 0 || ((f->mode & VFS_READ) && (f->mode & VFS_WRITE)))
+ {
+ Vfdestroy(f);
+ return NULL;
+ }
+
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ f->endoffile=false;
+ int readFlags=GENERIC_READ, openFlags=0;
+ if (f->mode & VFS_WRITE) readFlags|=GENERIC_WRITE;
+ if (f->mode & VFS_MUSTEXIST) openFlags=OPEN_EXISTING;
+ if (f->mode & VFS_CREATE) openFlags = OPEN_ALWAYS;
+ if (f->mode & VFS_NEWCONTENT) openFlags = CREATE_ALWAYS;
+ f->hfile=CreateFile(fl,readFlags,FILE_SHARE_READ,0,openFlags,0,0);
+ if (f->hfile!=INVALID_HANDLE_VALUE)
+ f->filename = _strdup(fl);
+ else
+ {
+ Vfdestroy(f);
+ return NULL;
+ }
+ return f;
+ }
+#endif
+
+ if (f->mode & VFS_MUSTEXIST)
+ {
+ if (GetFileAttributesW(fl) == INVALID_FILE_ATTRIBUTES)
+ {
+ Vfdestroy(f);
+ return NULL;
+ }
+ }
+
+ if (!(f->mode & VFS_NEWCONTENT))
+ {
+ int attempts=0;
+ HANDLE hFile=INVALID_HANDLE_VALUE;
+again:
+ if (attempts<100) // we'll try for 10 seconds
+ {
+ hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ/*|FILE_SHARE_WRITE*/,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
+ if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
+ {
+ Sleep(100); // let's try again
+ goto again;
+ }
+ }
+ else if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION)
+ {
+ // screwed up STILL? eeergh I bet it's another program locking it, let's try with more sharing flags
+ hFile=CreateFileW(fl,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
+ }
+
+ if (hFile==INVALID_HANDLE_VALUE)
+ {
+ f->data = (uint8_t *)calloc(VFILE_INC, 1);
+ if (f->data == NULL)
+ {
+ Vfdestroy(f);
+ return NULL;
+ }
+ f->filesize = 0;
+ f->maxsize = VFILE_INC;
+ }
+ else
+ {
+ size_t fsize_ret_value=GetFileSize(hFile,NULL);
+ if (fsize_ret_value==INVALID_FILE_SIZE)
+ {
+ Vfdestroy(f);
+ return NULL;
+ }
+ f->filesize = (uint32_t)fsize_ret_value;
+ f->data = (uint8_t *)calloc(f->filesize, 1);
+ if (f->data == NULL)
+ {
+ CloseHandle(hFile);
+ Vfdestroy(f);
+ return NULL;
+ }
+ f->maxsize = f->filesize;
+ DWORD r = 0;
+ // TODO: benski> I think we should switch this to overlapped I/O (to allow I/O to happen as we're parsing)
+ // or switch to a memory mapped file... but we'll need to check with the profiler
+ if (!ReadFile(hFile,f->data,f->filesize,&r,NULL) || r != f->filesize)
+ {
+ CloseHandle(hFile);
+ Vfdestroy(f);
+ return NULL;
+ }
+ CloseHandle(hFile);
+ }
+ }
+
+ if (f->mode & VFS_SEEKEOF)
+ f->ptr = f->filesize;
+
+ f->filename = fl;
+ ndestring_retain(f->filename);
+ return f;
+}
+
+//----------------------------------------------------------------------------
+void Vfclose(VFILE *f)
+{
+ if (!f) return;
+
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ if (f->hfile!=INVALID_HANDLE_VALUE)
+ CloseHandle(f->hfile);
+ f->hfile=INVALID_HANDLE_VALUE;
+ }
+ else
+#endif
+ {
+ if (f->mode & VFS_WRITE)
+ {
+ Vsync(f);
+ }
+ }
+
+ ndestring_release(f->filename);
+ f->filename=0;
+ free(f->data);
+ f->data = 0;
+ f->ptr=0;
+ f->filesize=0;
+ f->maxsize=0;
+ f->dirty=0;
+}
+
+void Vfdestroy(VFILE *f)
+{
+ // benski> TODO:
+ if (f)
+ {
+ Vfclose(f);
+
+ while (f->locks)
+ Vfunlock(f, 1);
+
+ if (f->mutex) CloseHandle(f->mutex);
+ free(f);
+ }
+}
+
+//----------------------------------------------------------------------------
+size_t Vfread( void *ptr, size_t size, VFILE *f )
+{
+ assert( ptr && f );
+#ifdef NDE_ALLOW_NONCACHED
+ if ( !f->cached )
+ {
+ size_t read = f->r.read( ptr, size );
+ ptr = (uint8_t *)ptr + read;
+ size -= read;
+ if ( size == 0 ) return 1; // yay fully buffered read
+ // if we got here, the ring buffer is empty
+ f->r.clear(); // reset back to normal
+ if ( size > f->r.avail() )
+ {
+ return ReadFileN( ptr, size, f ) == size;
+ }
+ void *data = f->r.LockBuffer();
+ size_t bytes_read = ReadFileN( data, f->r.avail(), f );
+ f->r.UnlockBuffer( bytes_read );
+ read = f->r.read( ptr, size );
+ return read == size;
+ }
+#endif
+ //if (!size) return 0;
+ if ( size + f->ptr > f->filesize )
+ {
+ //FUCKO: remove this
+ if ( !( f->ptr < f->filesize ) )
+ {
+#ifdef _DEBUG
+ char buf[ 128 ] = { 0 };
+ StringCbPrintfA( buf, sizeof( buf ), "NDE/VFS: VFS read at %d/%d (%d bytes) is bad\n", f->ptr, f->filesize, size );
+ OutputDebugStringA( buf );
+#endif
+
+ // if (!f->flushtable) // this would be ideal, if we could figure out f->flushtable
+ // f->flushtable=MessageBox(g_hwnd,"DB read failed, DB may be corrupted.\r\n\r\n"
+ // "Hit Retry to continue, or Cancel to clear the DB and start over","Winamp Library Error",MB_RETRYCANCEL) == IDCANCEL; //fucko
+ //MessageBox(g_hwnd,"DB read failed, DB may be corrupted. If this error persists, remove all files from the library.",
+ // "Winamp Library Error",MB_OK);
+ return 0;
+ }
+
+ size = f->filesize - f->ptr;
+ }
+
+ memcpy( ptr, f->data + f->ptr, size );
+ f->ptr += (uint32_t)size;
+
+ return 1;
+}
+
+//----------------------------------------------------------------------------
+void Vfwrite(const void *ptr, size_t size, VFILE *f)
+{
+ if (!ptr || !f) return;
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ // TODO: with some cleverness we might be able to make this write to the read cache
+
+ // if we're cached, then our file position is off and we need to adjust
+ if (!f->r.empty())
+ Vfseek(f, -(f->r.size()), SEEK_CUR);
+
+ f->r.clear();
+ WriteFileN(ptr, size, f);
+ return;
+ }
+#endif
+ f->dirty=1;
+ size_t s = (size);
+ if (s + f->ptr > f->maxsize)
+ {
+ // grow f->data,f->maxsize to be (s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1)
+ // instead of calling Vgrow again which gets kinda slow
+ size_t newsize=(s + f->ptr + VFILE_INC-1)&~(VFILE_INC-1);
+ uint8_t *newdata=(uint8_t *)realloc(f->data,newsize);
+ if (newdata == NULL) return;
+ f->data = newdata;
+ memset(f->data+f->maxsize,0,newsize-f->maxsize);
+ f->maxsize=(uint32_t)newsize;
+ }
+ memcpy(f->data + f->ptr, ptr, s);
+ f->ptr += (uint32_t)s;
+ if (f->ptr > f->filesize)
+ f->filesize = f->ptr;
+}
+
+//----------------------------------------------------------------------------
+void Vgrow(VFILE *f)
+{
+ if (!f) return;
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached) return;
+#endif
+ uint8_t *newdata=(uint8_t *)realloc(f->data, f->maxsize + VFILE_INC);
+ if (newdata == NULL) return;
+ f->data = newdata;
+ f->maxsize += VFILE_INC;
+}
+
+//----------------------------------------------------------------------------
+uint32_t Vftell(VFILE *f)
+{
+ if (!f) return (unsigned)-1;
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ return SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
+ }
+#endif
+ return f->ptr;
+}
+
+//----------------------------------------------------------------------------
+void Vfseek(VFILE *f, uint32_t i, int whence)
+{
+ if (!f) return;
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ if (whence == SEEK_CUR && i > 0 && i <f->r.size())
+ {
+ f->r.advance(i);
+ }
+ else
+ {
+ f->r.clear();
+ SetFilePointer(f->hfile, i, NULL, whence);
+ f->endoffile = false;
+ }
+ return;
+ }
+#endif
+ switch (whence)
+ {
+ case SEEK_SET:
+ f->ptr = i;
+ break;
+ case SEEK_CUR:
+ f->ptr += i;
+ break;
+ case SEEK_END:
+ f->ptr = f->filesize+i;
+ break;
+ }
+}
+
+//----------------------------------------------------------------------------
+int Vfeof(VFILE *f)
+{
+ if (!f) return -1;
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ return !!f->endoffile;
+ }
+#endif
+ return (f->ptr >= f->filesize);
+}
+
+//----------------------------------------------------------------------------
+int Vsync(VFILE *f)
+{
+ if (!f) return 0;
+ if (!f->dirty) return 0;
+
+ if (f->mode & VFS_WRITE)
+ {
+#ifdef NDE_ALLOW_NONCACHED
+ if (!f->cached)
+ {
+ LONG p = SetFilePointer(f->hfile, 0, NULL, FILE_CURRENT);
+ CloseHandle(f->hfile);
+ f->hfile = CreateFileW(f->filename,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL);
+ if (f->hfile == INVALID_HANDLE_VALUE)
+ return 1;
+ SetFilePointer(f->hfile, p, NULL, SEEK_SET);
+ f->endoffile=false;
+
+ return 0;
+ }
+#endif
+
+ wchar_t newfn[MAX_PATH] = {0};
+ wchar_t oldfn[MAX_PATH] = {0};
+ DWORD mypid=GetCurrentProcessId();
+
+ StringCchPrintfW(newfn, MAX_PATH, L"%s.n3w%08X",f->filename,mypid);
+ StringCchPrintfW(oldfn, MAX_PATH, L"%s.o1d%08X",f->filename,mypid);
+
+ DeleteFileW(newfn);
+ DeleteFileW(oldfn);
+
+ HANDLE hFile = CreateFileW(newfn,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,NULL);
+ int success=0;
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ DWORD o = 0;
+ if (WriteFile(hFile,f->data,f->filesize,&o,NULL) && o == f->filesize) success++;
+ CloseHandle(hFile);
+ }
+ if (!success)
+ {
+ DeleteFileW(newfn);
+ return 1;
+ }
+
+ // TODO use this to keep a backup of the database file for edit fails, etc
+ if (MoveFileW(f->filename,oldfn) == 0) // if the function fails
+ {
+ CopyFileW(f->filename,oldfn, FALSE);
+ DeleteFileW(f->filename);
+ }
+
+ int rv=0;
+ if (MoveFileW(newfn,f->filename) == 0 && CopyFileW(newfn,f->filename, FALSE) == 0)
+ {
+ MoveFileW(oldfn,f->filename); // restore old file
+ rv=1;
+ }
+ else
+ {
+ f->dirty=0;
+ }
+
+ // clean up our temp files
+ DeleteFileW(oldfn);
+ DeleteFileW(newfn);
+
+ return rv;
+ }
+ f->dirty=0;
+ return 0;
+}
+
+// returns 0 on failure
+int Vflock(VFILE *fl, BOOL is_sync)
+{
+#ifndef NO_TABLE_WIN32_LOCKING
+ if (!fl) return 0;
+ if (!is_sync && fl->cached)
+ return 1;
+ // try for 10 seconds
+ if (fl->locks++ == 0)
+ {
+ if (WaitForSingleObject(fl->mutex, 10000) != WAIT_OBJECT_0)
+ {
+ fl->locks--;
+ return 0;
+ }
+ }
+#endif
+ return 1;
+}
+
+void Vfunlock(VFILE *fl, BOOL is_sync)
+{
+#ifndef NO_TABLE_WIN32_LOCKING
+ if (!is_sync && fl->cached)
+ return;
+
+ if (fl && fl->locks == 0)
+ DebugBreak();
+ if (--fl->locks == 0)
+ {
+ if (fl && fl->mutex)
+ {
+ ReleaseMutex(fl->mutex);
+ }
+ }
+#endif
+} \ No newline at end of file
diff --git a/Src/nde/win/Vfs.h b/Src/nde/win/Vfs.h
new file mode 100644
index 00000000..50490b83
--- /dev/null
+++ b/Src/nde/win/Vfs.h
@@ -0,0 +1,98 @@
+#ifndef __NDE_VFS_H
+#define __NDE_VFS_H
+
+#include <bfc/platform/types.h>
+#include <windows.h>
+
+//#define NDE_ALLOW_NONCACHED
+
+/*
+#ifdef NDE_ALLOW_NONCACHED
+ #ifndef NDE_NOWIN32FILEIO
+ #error NDE_ALLOW_NONCACHED at least for now requires NDE_NOWIN32FILEIO
+ #endif
+#endif
+*/
+
+#define VFILE_INC 65536
+
+#define VFS_READ 1
+#define VFS_WRITE 2
+#define VFS_SEEKEOF 4
+#define VFS_CREATE 8
+#define VFS_NEWCONTENT 16
+#define VFS_MUSTEXIST 32
+#if defined(NDE_ALLOW_NONCACHED)
+#include "../nu/RingBuffer.h"
+#endif
+typedef struct VFILEStruct
+{
+ void VFILE()
+ {
+ data = 0;
+ ptr = 0;
+ filesize = 0;
+ maxsize = 0;
+ filename = 0;
+ mode = 0;
+ cached = FALSE;
+ dirty = 0;
+
+ #ifdef NDE_ALLOW_NONCACHED
+ #ifdef NDE_NOWIN32FILEIO
+ rfile = 0;
+ #else
+ hfile = NULL;
+ endoffile = false;
+ r = 0;
+ #endif
+ #endif
+
+ mutex = NULL;
+ locks = 0;
+ }
+
+ uint8_t *data;
+ uint32_t ptr;
+ uint32_t filesize;
+ uint32_t maxsize;
+ wchar_t *filename;
+ char mode;
+ BOOL cached;
+ int dirty;
+
+#ifdef NDE_ALLOW_NONCACHED
+ #ifdef NDE_NOWIN32FILEIO
+ FILE *rfile;
+ #else
+ HANDLE hfile;
+ bool endoffile;
+ RingBuffer r;
+ #endif
+#endif
+ HANDLE mutex;
+ int locks;
+} VFILE;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+VFILE *Vfnew(const wchar_t *filename, const char *mode, BOOL Cached);
+VFILE *Vfopen(VFILE *f, wchar_t *filename, const char *mode, BOOL Cached); // filename must be an NDE string
+size_t Vfread(void *ptr, size_t size, VFILE *buf);
+void Vfseek(VFILE *fl, uint32_t i, int whence);
+uint32_t Vftell(VFILE *fl);
+void Vfclose(VFILE *fl);
+void Vfdestroy(VFILE *fl); // benski> TODO:
+void Vfwrite(const void *ptr, size_t size, VFILE *f);
+int Vfeof(VFILE *fl);
+int Vsync(VFILE *fl); // 1 on error updating
+int Vflock(VFILE *fl, BOOL is_sync=TRUE); // returns 0 on failure
+void Vfunlock(VFILE *fl, BOOL is_sync=TRUE);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/Src/nde/win/nde_c.cpp b/Src/nde/win/nde_c.cpp
new file mode 100644
index 00000000..aee577c5
--- /dev/null
+++ b/Src/nde/win/nde_c.cpp
@@ -0,0 +1,561 @@
+#include "nde_c.h"
+#include "../nde.h"
+#include "../../nu/AutoCharFn.h"
+#include "../../nu/AutoWide.h"
+
+/* Database */
+nde_database_t NDE_CreateDatabase(HINSTANCE hInstance)
+{
+ return (nde_database_t)new Database(hInstance);
+}
+
+void NDE_DestroyDatabase(nde_database_t db)
+{
+ delete (Database *)db;
+}
+
+nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *indexname, int create, int cache)
+{
+ Database *database = (Database *)db;
+ if (database && filename)
+ return (nde_table_t)database->OpenTable(filename, indexname, (BOOL)create, (BOOL)cache);
+ else
+ return 0;
+}
+
+void NDE_Database_CloseTable(nde_database_t db, nde_table_t t)
+{
+ Database *database = (Database *)db;
+ Table *table = (Table *)t;
+ if (database && table)
+ {
+ database->CloseTable(table);
+ }
+}
+/* Table */
+
+nde_field_t NDE_Table_NewColumn(nde_table_t t, unsigned char id, const char *name, unsigned char type)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return (nde_field_t)table->NewColumn(id, AutoWideDup(name), type, FALSE);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Table_NewColumnW(nde_table_t t, unsigned char id, const wchar_t *name, unsigned char type)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return (nde_field_t)table->NewColumn(id, name, type, FALSE);
+ else
+ return 0;
+}
+
+void NDE_Table_PostColumns(nde_table_t t)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->PostColumns();
+}
+
+void NDE_Table_AddIndexByID(nde_table_t t, unsigned char id, const char *name)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->AddIndexById(id, AutoWide(name));
+}
+
+void NDE_Table_AddIndexByIDW(nde_table_t t, unsigned char id, const wchar_t *name)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->AddIndexById(id, name);
+}
+
+nde_scanner_t NDE_Table_CreateScanner(nde_table_t t)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return (nde_scanner_t)table->NewScanner();
+ else
+ return 0;
+}
+
+void NDE_Table_DestroyScanner(nde_table_t t, nde_scanner_t s)
+{
+ Table *table = (Table *)t;
+ Scanner *scanner = (Scanner *)s;
+ if (table && scanner)
+ table->DeleteScanner(scanner);
+}
+
+void NDE_Table_Sync(nde_table_t t)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->Sync();
+}
+
+void NDE_Table_Compact(nde_table_t t, int *progress)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->Compact(progress);
+}
+
+int NDE_Table_GetRecordsCount(nde_table_t t)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return table->GetRecordsCount();
+ else
+ return 0;
+}
+
+nde_field_t NDE_Table_GetColumnByID(nde_table_t t, unsigned char id)
+{
+ Table *table = (Table *)t;
+ if (table)
+ return (nde_field_t)table->GetColumnById(id);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Table_GetColumnByName(nde_table_t t, const char *name)
+{
+ Table *table = (Table *)t;
+ if (table && name)
+ return (nde_field_t)table->GetColumnByName(AutoWide(name));
+ else
+ return 0;
+}
+
+void NDE_Table_SetColumnSearchableByID(nde_table_t t, unsigned char id, int searchable)
+{
+ Table *table = (Table *)t;
+ if (table)
+ table->SetFieldSearchableById(id, !!searchable);
+}
+
+/* Scanner */
+int NDE_Scanner_Query(nde_scanner_t s, const wchar_t *query)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Query(query);
+ else
+ return 0;
+}
+
+void NDE_Scanner_Search(nde_scanner_t s, const wchar_t *search_term)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->Search(search_term);
+}
+
+const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->GetLastQuery();
+ else
+ return 0;
+}
+
+int NDE_Scanner_GetRecordsCount(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->GetRecordsCount();
+ else
+ return 0;
+}
+
+void NDE_Scanner_New(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->New();
+}
+
+void NDE_Scanner_Post(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->Post();
+}
+
+void NDE_Scanner_First(nde_scanner_t s, int *killswitch)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->First(killswitch);
+}
+
+void NDE_Scanner_Delete(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->Delete();
+}
+
+void NDE_Scanner_Edit(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->Edit();
+}
+
+int NDE_Scanner_EOF(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Eof();
+ else
+ return 1;
+}
+
+int NDE_Scanner_BOF(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Bof();
+ else
+ return 1;
+}
+
+nde_field_t NDE_Scanner_NewFieldByID(nde_scanner_t s, unsigned char id)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->NewFieldById(id, PERM_READWRITE);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Scanner_NewFieldByType(nde_scanner_t s, unsigned char type, unsigned char id)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->NewFieldByType(type, id, PERM_READWRITE);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t s, const char *name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->NewFieldByName(AutoWide(name), PERM_READWRITE);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Scanner_GetFieldByID(nde_scanner_t s, unsigned char id)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return (nde_field_t)scanner->GetFieldById(id);
+ else
+ return 0;
+}
+
+nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t s, const char *name)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner && name)
+ return (nde_field_t)scanner->GetFieldByName(AutoWide(name));
+ else
+ return 0;
+}
+
+void NDE_Scanner_AddFilterByID(nde_scanner_t s, unsigned char id, nde_field_t f, unsigned char filter_operation)
+{
+ Scanner *scanner = (Scanner *)s;
+ Field *field = (Field *)f;
+ if (scanner && field)
+ scanner->AddFilterById(id, field, filter_operation);
+}
+
+int NDE_Scanner_LocateInteger(nde_scanner_t s, unsigned char id, int from, int value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ IntegerField field(value);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+int NDE_Scanner_LocateString(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ StringField field(value);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+
+int NDE_Scanner_LocateNDEString(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ StringField field(value, STRING_IS_NDESTRING);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+int NDE_Scanner_LocateFilename(nde_scanner_t s, unsigned char id, int from, const wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ FilenameField field(value);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+int NDE_Scanner_LocateNDEFilename(nde_scanner_t s, unsigned char id, int from, wchar_t *value, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ {
+ FilenameField field(value, STRING_IS_NDESTRING);
+ return scanner->LocateByIdEx(id, from, &field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+int NDE_Scanner_LocateField(nde_scanner_t s, unsigned char id, int from, nde_field_t f, int *nskip, int compare_mode)
+{
+ Scanner *scanner = (Scanner *)s;
+ Field *field = (Field *)f;
+ if (scanner && field)
+ {
+ return scanner->LocateByIdEx(id, from, field, nskip, compare_mode);
+ }
+ else
+ return 0;
+}
+
+void NDE_Scanner_DeleteField(nde_scanner_t s, nde_field_t f)
+{
+ Scanner *scanner = (Scanner *)s;
+ Field *field = (Field *)f;
+ if (scanner && field)
+ {
+ scanner->DeleteField(field);
+ }
+}
+
+void NDE_Scanner_RemoveFilters(nde_scanner_t s)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ scanner->RemoveFilters();
+}
+
+int NDE_Scanner_Next(nde_scanner_t s, int *killswitch)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->Next(killswitch);
+ else
+ return 0;
+}
+
+void NDE_Scanner_WalkFields(nde_scanner_t s, FieldEnumerator enumerator, void *context)
+{
+ Scanner *scanner = (Scanner *)s;
+ if (scanner)
+ return scanner->WalkFields((Record::FieldsWalker)enumerator, context);
+}
+
+/* Filter functions */
+unsigned char NDE_Filter_GetID(nde_filter_t f)
+{
+ Filter *filter = (Filter *)f;
+ if (filter)
+ return filter->GetId();
+ else
+ return FILTERS_INVALID; // right value but I'm not sure if it's the best constant name to use
+}
+
+NDE_API unsigned char NDE_Filter_GetOp(nde_filter_t f)
+{
+ Filter *filter = (Filter *)f;
+ if (filter)
+ return filter->GetOp();
+ else
+ return FILTERS_INVALID; // right value but I'm not sure if it's the best constant name to use
+}
+
+NDE_API nde_field_t NDE_Filter_GetData(nde_filter_t f)
+{
+ Filter *filter = (Filter *)f;
+ if (filter)
+ return (nde_field_t)filter->Data();
+ else
+ return 0;
+}
+
+/* Field functions */
+unsigned char NDE_Field_GetType(nde_field_t f)
+{
+ Field *field = (Field *)f;
+ if (field)
+ return field->GetType();
+ else
+ return FIELD_UNKNOWN;
+}
+
+unsigned char NDE_Field_GetID(nde_field_t f)
+{
+ Field *field = (Field *)f;
+ if (field)
+ return field->GetFieldId();
+ else
+ return FIELD_UNKNOWN;
+}
+
+/* StringField functions */
+
+void NDE_StringField_SetNDEString(nde_field_t f, wchar_t *nde_string)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ field->SetNDEString(nde_string);
+}
+
+wchar_t *NDE_StringField_GetString(nde_field_t f)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ return field->GetStringW();
+ else
+ return 0;
+}
+
+void NDE_StringField_SetString(nde_field_t f, const wchar_t *str)
+{
+ StringField *field = (StringField *)(Field *)f;
+ if (field)
+ field->SetStringW(str);
+}
+
+/* IntegerField functions */
+void NDE_IntegerField_SetValue(nde_field_t f, int value)
+{
+ IntegerField *field = (IntegerField *)(Field *)f;
+ if (field)
+ field->SetValue(value);
+}
+
+int NDE_IntegerField_GetValue(nde_field_t f)
+{
+ IntegerField *field = (IntegerField *)(Field *)f;
+ if (field)
+ return field->GetValue();
+ else
+ return 0;
+}
+
+nde_field_t NDE_IntegerField_Create(int value)
+{
+ return (nde_field_t)new IntegerField(value);
+}
+
+/* Int64Field functions */
+void NDE_Int64Field_SetValue(nde_field_t f, __int64 value)
+{
+ Int64Field *field = (Int64Field *)(Field *)f;
+ if (field)
+ field->SetValue(value);
+}
+
+__int64 NDE_Int64Field_GetValue(nde_field_t f)
+{
+ Int64Field *field = (Int64Field *)(Field *)f;
+ if (field)
+ return field->GetValue();
+ else
+ return 0;
+}
+
+nde_field_t NDE_Int64Field_Create(__int64 value)
+{
+ return (nde_field_t)new Int64Field(value);
+}
+
+/* BinaryField */
+void *NDE_BinaryField_GetData(nde_field_t f, size_t *length)
+{
+ BinaryField *field = (BinaryField *)(Field *)f;
+ if (field)
+ return (void *)field->GetData(length);
+ else
+ return 0;
+}
+void NDE_BinaryField_SetData(nde_field_t f, const void *data, size_t length)
+{
+ BinaryField *field = (BinaryField *)(Field *)f;
+ if (field)
+ field->SetData((const uint8_t *)data, length);
+}
+
+/* Int128Field */
+void NDE_Int128Field_SetValue(nde_field_t f, const void *value)
+{
+ Int128Field *field = (Int128Field *)(Field *)f;
+ if (field && value)
+ field->SetValue(value);
+}
+
+/* ColumnField */
+const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetFieldName();
+ else
+ return 0;
+}
+
+unsigned char NDE_ColumnField_GetDataType(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetDataType();
+ else
+ return FIELD_UNKNOWN;
+}
+
+unsigned char NDE_ColumnField_GetFieldID(nde_field_t f)
+{
+ ColumnField *field = (ColumnField *)(Field *)f;
+ if (field)
+ return field->GetFieldId();
+ else
+ return FIELD_UNKNOWN;
+}
+
+__time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp)
+{
+ IntegerField f((int)value);
+ f.ApplyConversion(str, tp);
+ return f.GetValue();
+} \ No newline at end of file
diff --git a/Src/nde/win/nde_c.h b/Src/nde/win/nde_c.h
new file mode 100644
index 00000000..a7faae66
--- /dev/null
+++ b/Src/nde/win/nde_c.h
@@ -0,0 +1,131 @@
+#pragma once
+/* C style API.
+
+We'll eventually deprecate the C++ API as it presents a lot of linking challenges
+*/
+#include "../nde_defines.h"
+#include "../NDEString.h"
+#include <bfc/platform/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ typedef struct nde_database_struct_t { } *nde_database_t;
+ typedef struct nde_table_struct_t { } *nde_table_t;
+ typedef struct nde_scanner_t_struct_t { } *nde_scanner_t;
+ typedef struct nde_field_t_struct_t { } *nde_field_t;
+ typedef struct nde_filter_struct_t { } *nde_filter_t;
+
+ /* Database functions */
+
+ // Windows API
+#ifdef __cplusplus
+#define NDE_DEFAULT_PARAMETER(x) =x
+#else
+#define NDE_DEFAULT_PARAMETER(x)
+#endif
+
+NDE_API void NDE_Init();
+NDE_API void NDE_Quit();
+NDE_API nde_database_t NDE_CreateDatabase(HINSTANCE hInstance NDE_DEFAULT_PARAMETER(0));
+NDE_API void NDE_DestroyDatabase(nde_database_t db);
+NDE_API nde_table_t NDE_Database_OpenTable(nde_database_t db, const wchar_t *filename, const wchar_t *filename_index, int create, int cache);
+
+NDE_API void NDE_Database_CloseTable(nde_database_t db, nde_table_t table);
+
+/* Table functions */
+NDE_API nde_field_t NDE_Table_NewColumn(nde_table_t table, unsigned char id, const char *name, unsigned char type);
+NDE_API nde_field_t NDE_Table_NewColumnW(nde_table_t table, unsigned char id, const wchar_t *name, unsigned char type);
+NDE_API void NDE_Table_PostColumns(nde_table_t table);
+NDE_API void NDE_Table_AddIndexByID(nde_table_t table, unsigned char id, const char *name);
+NDE_API void NDE_Table_AddIndexByIDW(nde_table_t table, unsigned char id, const wchar_t *name);
+NDE_API nde_scanner_t NDE_Table_CreateScanner(nde_table_t table);
+NDE_API void NDE_Table_DestroyScanner(nde_table_t table, nde_scanner_t scanner);
+NDE_API void NDE_Table_Sync(nde_table_t table);
+NDE_API void NDE_Table_Compact(nde_table_t table, int *progress NDE_DEFAULT_PARAMETER(0));
+NDE_API int NDE_Table_GetRecordsCount(nde_table_t table);
+NDE_API nde_field_t NDE_Table_GetColumnByID(nde_table_t table, unsigned char id);
+
+NDE_API nde_field_t NDE_Table_GetColumnByName(nde_table_t table, const char *name);
+
+NDE_API void NDE_Table_SetColumnSearchableByID(nde_table_t table, unsigned char id, int searchable);
+
+/* Scanner functions */
+NDE_API int NDE_Scanner_Query(nde_scanner_t scanner, const wchar_t *query);
+NDE_API void NDE_Scanner_Search(nde_scanner_t scanner, const wchar_t *search_term);
+NDE_API const wchar_t *NDE_Scanner_GetLastQuery(nde_scanner_t scanner);
+NDE_API int NDE_Scanner_GetRecordsCount(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_New(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_Post(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_First(nde_scanner_t scanner, int *killswitch NDE_DEFAULT_PARAMETER(0));
+NDE_API int NDE_Scanner_Next(nde_scanner_t scanner, int *killswitch NDE_DEFAULT_PARAMETER(0));
+NDE_API void NDE_Scanner_Delete(nde_scanner_t scanner);
+NDE_API void NDE_Scanner_Edit(nde_scanner_t scanner);
+NDE_API int NDE_Scanner_EOF(nde_scanner_t scanner);
+NDE_API int NDE_Scanner_BOF(nde_scanner_t scanner);
+NDE_API nde_field_t NDE_Scanner_NewFieldByID(nde_scanner_t scanner, unsigned char id);
+NDE_API nde_field_t NDE_Scanner_NewFieldByType(nde_scanner_t s, unsigned char type, unsigned char id);
+NDE_API nde_field_t NDE_Scanner_NewFieldByName(nde_scanner_t scanner, const char *name);
+NDE_API nde_field_t NDE_Scanner_GetFieldByID(nde_scanner_t scanner, unsigned char id);
+NDE_API nde_field_t NDE_Scanner_GetFieldByName(nde_scanner_t scanner, const char *name);
+NDE_API void NDE_Scanner_AddFilterByID(nde_scanner_t scanner, unsigned char id, nde_field_t field, unsigned char filter_operation);
+
+NDE_API int NDE_Scanner_LocateInteger(nde_scanner_t scanner, unsigned char id, int from, int value,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+NDE_API int NDE_Scanner_LocateString(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+NDE_API int NDE_Scanner_LocateNDEString(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+NDE_API int NDE_Scanner_LocateFilename(nde_scanner_t scanner, unsigned char id, int from, const wchar_t *value,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+NDE_API int NDE_Scanner_LocateNDEFilename(nde_scanner_t scanner, unsigned char id, int from, wchar_t *value,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+NDE_API int NDE_Scanner_LocateField(nde_scanner_t scanner, unsigned char id, int from, nde_field_t field,
+ int *nskip NDE_DEFAULT_PARAMETER(0), int compare_mode NDE_DEFAULT_PARAMETER(COMPARE_MODE_EXACT));
+
+NDE_API void NDE_Scanner_DeleteField(nde_scanner_t scanner, nde_field_t field);
+NDE_API void NDE_Scanner_RemoveFilters(nde_scanner_t scanner);
+typedef int (__cdecl *FieldEnumerator)(void *record, nde_field_t field, void *context);
+NDE_API void NDE_Scanner_WalkFields(nde_scanner_t scanner, FieldEnumerator enumerator, void *context);
+/* Filter functions */
+NDE_API unsigned char NDE_Filter_GetID(nde_filter_t filter);
+NDE_API unsigned char NDE_Filter_GetOp(nde_filter_t filter);
+NDE_API nde_field_t NDE_Filter_GetData(nde_filter_t filter);
+
+/* Field functions */
+NDE_API unsigned char NDE_Field_GetType(nde_field_t field);
+NDE_API unsigned char NDE_Field_GetID(nde_field_t field);
+
+/* String Field functions */
+NDE_API wchar_t *NDE_StringField_GetString(nde_field_t field); /* returns non-const because it's an NDE string (reference counted, see ndestring.h) */
+NDE_API void NDE_StringField_SetNDEString(nde_field_t field, wchar_t *nde_string);
+NDE_API void NDE_StringField_SetString(nde_field_t field, const wchar_t *str);
+
+/* IntegerField functions */
+NDE_API void NDE_IntegerField_SetValue(nde_field_t field, int value);
+NDE_API int NDE_IntegerField_GetValue(nde_field_t field);
+NDE_API nde_field_t NDE_IntegerField_Create(int value); /* mainly used for NDE_Scanner_AddFilterByID */
+
+/* Int64Field functions */
+NDE_API void NDE_Int64Field_SetValue(nde_field_t field, __int64 value);
+NDE_API __int64 NDE_Int64Field_GetValue(nde_field_t field);
+NDE_API nde_field_t NDE_Int64Field_Create(__int64 value);
+
+/* BinaryField functions */
+// on windows, the data pointer is optionally reference counted via ndestring (ndestring_retain if you plan on keeping it)
+NDE_API void *NDE_BinaryField_GetData(nde_field_t field, size_t *length);
+NDE_API void NDE_BinaryField_SetData(nde_field_t field, const void *data, size_t length);
+
+/* Int128Field functions */
+NDE_API void NDE_Int128Field_SetValue(nde_field_t field, const void *value);
+
+/* ColumnField functions */
+NDE_API const wchar_t *NDE_ColumnField_GetFieldName(nde_field_t field);
+NDE_API unsigned char NDE_ColumnField_GetDataType(nde_field_t field);
+NDE_API unsigned char NDE_ColumnField_GetFieldID(nde_field_t);
+
+NDE_API __time64_t NDE_Time_ApplyConversion(__time64_t value, const wchar_t *str, class TimeParse *tp);
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/Src/nde/win/nde_init.cpp b/Src/nde/win/nde_init.cpp
new file mode 100644
index 00000000..9dfc31ee
--- /dev/null
+++ b/Src/nde/win/nde_init.cpp
@@ -0,0 +1,36 @@
+#include "../nde_c.h"
+#include "../DBUtils.h"
+
+#include <atomic>
+
+extern "C" void NDE_HeapInit();
+extern "C" void NDE_HeapQuit();
+static volatile std::atomic<std::size_t> _init_count = 0;
+
+/* NDE_Init isn't thread safe, be aware
+best to call on the main thread during initialization
+*/
+void NDE_Init()
+{
+ if ( _init_count.load() == 0 )
+ {
+ NDE_HeapInit();
+ HMODULE klib = LoadLibraryW( L"Kernel32.dll" );
+ if ( klib )
+ {
+ void *nls = GetProcAddress( klib, "FindNLSString" );
+ if ( nls )
+ *( (void **)&findNLSString ) = nls;
+ }
+
+ FreeModule( klib );
+ }
+
+ _init_count.fetch_add( 1 );
+}
+
+void NDE_Quit()
+{
+ if ( _init_count.fetch_sub( 1 ) == 0 )
+ NDE_HeapQuit();
+} \ No newline at end of file