From acb833d72477b0ba93162bce66ed33a399516e82 Mon Sep 17 00:00:00 2001
From: "Adam D. Ruppe" <destructionator@gmail.com>
Date: Mon, 8 Jul 2019 11:03:26 -0400
Subject: [PATCH] lots of compatibility

---
 cgi.d                 | 211 +++++++++++++++++++++++++-----------------
 database.d            |   4 +-
 database_generation.d |  49 +++++++---
 sqlite.d              |  43 ++++++---
 4 files changed, 194 insertions(+), 113 deletions(-)

diff --git a/cgi.d b/cgi.d
index bb3e144..d136cca 100644
--- a/cgi.d
+++ b/cgi.d
@@ -6076,21 +6076,24 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS
 		if(listen(sock, 128) == -1)
 			throw new Exception("listen " ~ to!string(errno));
 
+		makeNonBlocking(sock);
+
 		version(linux) {
-
-			makeNonBlocking(sock);
-
 			import core.sys.linux.epoll;
 			auto epoll_fd = epoll_create1(EPOLL_CLOEXEC);
 			if(epoll_fd == -1)
 				throw new Exception("epoll_create1 " ~ to!string(errno));
 			scope(failure)
 				close(epoll_fd);
+		} else {
+			import core.sys.posix.poll;
+		}
 
-			auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
-			scope(exit)
-				freeIoOp(acceptOp);
+		auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
+		scope(exit)
+			freeIoOp(acceptOp);
 
+		version(linux) {
 			epoll_event ev;
 			ev.events = EPOLLIN | EPOLLET;
 			ev.data.ptr = acceptOp;
@@ -6098,111 +6101,153 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS
 				throw new Exception("epoll_ctl " ~ to!string(errno));
 
 			epoll_event[64] events;
+		} else {
+			pollfd[] pollfds;
+			pollfds ~= pollfd(sock, POLLIN);
+			IoOp*[int] ioops;
+		}
 
-			while(true) {
+		while(true) {
 
-				// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
+			// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
 
-				int timeout_milliseconds = 15000; //  -1; // infinite
-				//writeln("waiting for ", name);
+			int timeout_milliseconds = 15000; //  -1; // infinite
+			//writeln("waiting for ", name);
+
+			version(linux) {
 				auto nfds = epoll_wait(epoll_fd, events.ptr, events.length, timeout_milliseconds);
 				if(nfds == -1) {
 					if(errno == EINTR)
 						continue;
 					throw new Exception("epoll_wait " ~ to!string(errno));
 				}
+			} else {
+				int nfds = poll(pollfds.ptr, pollfds.length, timeout_milliseconds);
+			}
 
-				if(nfds == 0) {
-					eis.wait_timeout();
-				}
+			if(nfds == 0) {
+				eis.wait_timeout();
+			}
 
-				foreach(idx; 0 .. nfds) {
+			foreach(idx; 0 .. nfds) {
+				version(linux) {
 					auto flags = events[idx].events;
 					auto ioop = cast(IoOp*) events[idx].data.ptr;
+				} else {
+					auto ioop = ioops[pollfds[idx].fd];
+				}
 
-					//writeln(flags, " ", ioop.fd);
+				//writeln(flags, " ", ioop.fd);
 
-					if(ioop.fd == sock && (flags & EPOLLIN)) {
-						// on edge triggering, it is important that we get it all
-						while(true) {
-							auto size = cast(uint) addr.sizeof;
-							auto ns = accept(sock, cast(sockaddr*) &addr, &size);
-							if(ns == -1) {
-								if(errno == EAGAIN || errno == EWOULDBLOCK) {
-									// all done, got it all
-									break;
-								}
-								throw new Exception("accept " ~ to!string(errno));
+				void newConnection() {
+					// on edge triggering, it is important that we get it all
+					while(true) {
+						auto size = cast(uint) addr.sizeof;
+						auto ns = accept(sock, cast(sockaddr*) &addr, &size);
+						if(ns == -1) {
+							if(errno == EAGAIN || errno == EWOULDBLOCK) {
+								// all done, got it all
+								break;
 							}
+							throw new Exception("accept " ~ to!string(errno));
+						}
 
-							makeNonBlocking(ns);
+						makeNonBlocking(ns);
+						auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096, &eis.handleLocalConnectionData);
+						niop.closeHandler = &eis.handleLocalConnectionClose;
+						niop.completeHandler = &eis.handleLocalConnectionComplete;
+						scope(failure) freeIoOp(niop);
+
+						version(linux) {
 							epoll_event nev;
 							nev.events = EPOLLIN | EPOLLET;
-							auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096, &eis.handleLocalConnectionData);
-							niop.closeHandler = &eis.handleLocalConnectionClose;
-							niop.completeHandler = &eis.handleLocalConnectionComplete;
-							scope(failure) freeIoOp(niop);
 							nev.data.ptr = niop;
 							if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ns, &nev) == -1)
 								throw new Exception("epoll_ctl " ~ to!string(errno));
-						}
-					} else if(ioop.operation == IoOp.ReadSocketHandle) {
-						while(true) {
-							int in_fd;
-							auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
-							if(got == -1) {
-								if(errno == EAGAIN || errno == EWOULDBLOCK) {
-									// all done, got it all
-									if(ioop.completeHandler)
-										ioop.completeHandler(ioop);
-									break;
+						} else {
+							bool found = false;
+							foreach(ref pfd; pollfds) {
+								if(pfd.fd < 0) {
+									pfd.fd = ns;
+									found = true;
 								}
-								throw new Exception("recv " ~ to!string(errno));
 							}
-
-							if(got == 0) {
-								if(ioop.closeHandler)
-									ioop.closeHandler(ioop);
-								close(ioop.fd);
-								freeIoOp(ioop);
-								break;
-							}
-
-							ioop.bufferLengthUsed = cast(int) got;
-							ioop.handler(ioop, in_fd);
-						}
-					} else if(ioop.operation == IoOp.Read) {
-						while(true) {
-							auto got = recv(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, 0);
-							if(got == -1) {
-								if(errno == EAGAIN || errno == EWOULDBLOCK) {
-									// all done, got it all
-									if(ioop.completeHandler)
-										ioop.completeHandler(ioop);
-									break;
-								}
-								throw new Exception("recv " ~ to!string(errno));
-							}
-
-							if(got == 0) {
-								if(ioop.closeHandler)
-									ioop.closeHandler(ioop);
-								close(ioop.fd);
-								freeIoOp(ioop);
-								break;
-							}
-
-							ioop.bufferLengthUsed = cast(int) got;
-							ioop.handler(ioop, -1);
+							if(!found)
+								pollfds ~= pollfd(ns, POLLIN);
 						}
 					}
-
-					// EPOLLHUP?
 				}
+
+				bool newConnectionCondition() {
+					version(linux)
+						return ioop.fd == sock && (flags & EPOLLIN);
+					else
+						return pollfds[idx].fd == sock && (pollfds[idx].revents & POLLIN);
+				}
+
+				if(newConnectionCondition()) {
+					newConnection();
+				} else if(ioop.operation == IoOp.ReadSocketHandle) {
+					while(true) {
+						int in_fd;
+						auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
+						if(got == -1) {
+							if(errno == EAGAIN || errno == EWOULDBLOCK) {
+								// all done, got it all
+								if(ioop.completeHandler)
+									ioop.completeHandler(ioop);
+								break;
+							}
+							throw new Exception("recv " ~ to!string(errno));
+						}
+
+						if(got == 0) {
+							if(ioop.closeHandler) {
+								ioop.closeHandler(ioop);
+								version(linux) {} // nothing needed
+								else {
+									foreach(ref pfd; pollfds) {
+										if(pfd.fd == ioop.fd)
+											pfd.fd = -1;
+									}
+								}
+							}
+							close(ioop.fd);
+							freeIoOp(ioop);
+							break;
+						}
+
+						ioop.bufferLengthUsed = cast(int) got;
+						ioop.handler(ioop, in_fd);
+					}
+				} else if(ioop.operation == IoOp.Read) {
+					while(true) {
+						auto got = recv(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, 0);
+						if(got == -1) {
+							if(errno == EAGAIN || errno == EWOULDBLOCK) {
+								// all done, got it all
+								if(ioop.completeHandler)
+									ioop.completeHandler(ioop);
+								break;
+							}
+							throw new Exception("recv " ~ to!string(errno));
+						}
+
+						if(got == 0) {
+							if(ioop.closeHandler)
+								ioop.closeHandler(ioop);
+							close(ioop.fd);
+							freeIoOp(ioop);
+							break;
+						}
+
+						ioop.bufferLengthUsed = cast(int) got;
+						ioop.handler(ioop, -1);
+					}
+				}
+
+				// EPOLLHUP?
 			}
-		} else {
-			// this isn't seriously implemented.
-			static assert(0);
 		}
 	} else version(Windows) {
 
diff --git a/database.d b/database.d
index 94bf103..85f5d97 100644
--- a/database.d
+++ b/database.d
@@ -376,8 +376,8 @@ string toSql(Database db, Variant a) {
 
 // just for convenience; "str".toSql(db);
 string toSql(string s, Database db) {
-	if(s is null)
-		return "NULL";
+	//if(s is null)
+		//return "NULL";
 	return '\'' ~ db.escape(s) ~ '\'';
 }
 
diff --git a/database_generation.d b/database_generation.d
index 4a2313b..dcf7d33 100644
--- a/database_generation.d
+++ b/database_generation.d
@@ -112,6 +112,7 @@ string generateCreateTableFor(alias O)() {
 	static foreach(memberName; __traits(allMembers, O)) {{
 		alias member = __traits(getMember, O, memberName);
 		static if(is(typeof(member) == Constraint!constraintSql, string constraintSql)) {
+		version(dbgenerate_sqlite) {} else { // FIXME: make it work here too, it is the specifics of the constraint strings
 			if(outputted) {
 				sql ~= ",";
 			}
@@ -120,6 +121,7 @@ string generateCreateTableFor(alias O)() {
 			sql ~= " ";
 			sql ~= constraintSql;
 			outputted = true;
+		}
 		} else static if(is(typeof(member) == Index!Fields, Fields...)) {
 			string fields = "";
 			static foreach(field; Fields) {
@@ -155,27 +157,42 @@ string generateCreateTableFor(alias O)() {
 				else static assert(0, P.stringof);
 			} else static if(is(T == int))
 				sql ~= " INTEGER NOT NULL";
-			else static if(is(T == Serial))
-				sql ~= " SERIAL"; // FIXME postgresism
-			else static if(is(T == string))
+			else static if(is(T == Serial)) {
+				version(dbgenerate_sqlite)
+					sql ~= " INTEGER PRIMARY KEY AUTOINCREMENT";
+				else
+					sql ~= " SERIAL"; // FIXME postgresism
+			} else static if(is(T == string))
 				sql ~= " TEXT NOT NULL";
 			else static if(is(T == double))
 				sql ~= " FLOAT NOT NULL";
 			else static if(is(T == bool))
 				sql ~= " BOOLEAN NOT NULL";
-			else static if(is(T == Timestamp))
-				sql ~= " TIMESTAMPTZ NOT NULL"; // FIXME: postgresism
-			else static if(is(T == enum))
+			else static if(is(T == Timestamp)) {
+				version(dbgenerate_sqlite)
+					sql ~= " TEXT NOT NULL";
+				else
+					sql ~= " TIMESTAMPTZ NOT NULL"; // FIXME: postgresism
+			} else static if(is(T == enum))
 				sql ~= " INTEGER NOT NULL"; // potentially crap but meh
 
 			static foreach(attr; __traits(getAttributes, member)) {
 				static if(is(typeof(attr) == Default)) {
 					// FIXME: postgresism there, try current_timestamp in sqlite
-					sql ~= " DEFAULT " ~ attr.sql;
+					version(dbgenerate_sqlite) {
+						import std.string;
+						sql ~= " DEFAULT " ~ std.string.replace(attr.sql, "now()", "current_timestamp");
+					} else
+						sql ~= " DEFAULT " ~ attr.sql;
 				} else static if(is(attr == Unique)) {
 					sql ~= " UNIQUE";
 				} else static if(is(attr == PrimaryKey)) {
-					addPostSql("PRIMARY KEY(" ~ memberName ~ ")");
+					version(dbgenerate_sqlite) {
+						static if(is(T == Serial)) {} // skip, it is done above
+						else
+						addPostSql("PRIMARY KEY(" ~ memberName ~ ")");
+					} else
+						addPostSql("PRIMARY KEY(" ~ memberName ~ ")");
 				} else static if(is(attr == ForeignKey!(to, sqlPolicy), alias to, string sqlPolicy)) {
 					string refTable = toTableName(__traits(parent, to).stringof);
 					string refField = to.stringof;
@@ -299,9 +316,9 @@ void insert(O)(ref O t, Database db) {
 				} else {
 					// skip and let it auto-fill
 				}
-			} else static if(is(T == string))
+			} else static if(is(T == string)) {
 				builder.addVariable(memberName, __traits(getMember, t, memberName));
-			else static if(is(T == double))
+			} else static if(is(T == double))
 				builder.addVariable(memberName, __traits(getMember, t, memberName));
 			else static if(is(T == bool))
 				builder.addVariable(memberName, __traits(getMember, t, memberName));
@@ -315,8 +332,14 @@ void insert(O)(ref O t, Database db) {
 	}}
 
 	import std.conv;
-	foreach(row; builder.execute(db, "RETURNING id")) // FIXME: postgres-ism
-		t.id.value = to!int(row[0]);
+	version(dbgenerate_sqlite) {
+		builder.execute(db);
+		foreach(row; db.query("SELECT max(id) FROM " ~ toTableName(O.stringof)))
+			t.id.value = to!int(row[0]);
+	} else {
+		foreach(row; builder.execute(db, "RETURNING id")) // FIXME: postgres-ism
+			t.id.value = to!int(row[0]);
+	}
 }
 
 ///
@@ -370,7 +393,7 @@ private void populateFromDbVal(V)(ref V val, string value) {
 
 	} else static if(is(V == Nullable!P, P)) {
 		// FIXME
-		if(value.length) {
+		if(value.length && value != "null") {
 			val.isNull = false;
 			val.value = to!P(value);
 		}
diff --git a/sqlite.d b/sqlite.d
index 8348651..ea45a9d 100644
--- a/sqlite.d
+++ b/sqlite.d
@@ -121,7 +121,6 @@ class Sqlite : Database {
 		foreach(i, arg; args) {
 			s.bind(cast(int) i + 1, arg);
 		}
-
 		return s.execute();
 	}
 
@@ -263,6 +262,12 @@ struct Statement {
 	Sqlite db;
 
 	this(Sqlite db, string sql) {
+		// the arsd convention is zero based ?, but sqlite insists on one based. so this is stupid but still
+		if(sql.indexOf("?0") != -1) {
+			foreach_reverse(i; 0 .. 10)
+				sql = sql.replace("?" ~ to!string(i), "?" ~ to!string(i + 1));
+		}
+
 		this.db = db;
 		if(sqlite3_prepare_v2(db.db, toStringz(sql), cast(int) sql.length, &s, null) != SQLITE_OK)
 			throw new DatabaseException(db.error());
@@ -487,14 +492,13 @@ template extract(A, T, R...){
 		void bind (const char[] name, float value){ bind(bindNameLookUp(name), value); }
 		void bind (const char[] name, const byte[] value){ bind(bindNameLookUp(name), value); }
 
+	void bind(int col, typeof(null) value){
+		if(sqlite3_bind_null(s, col) != SQLITE_OK)
+			throw new DatabaseException("bind " ~ db.error());
+	}
 	void bind(int col, const char[] value){
-		if(value is null) {
-			if(sqlite3_bind_null(s, col) != SQLITE_OK)
-				throw new DatabaseException("bind " ~ db.error());
-		} else {
-			if(sqlite3_bind_text(s, col, value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
-				throw new DatabaseException("bind " ~ db.error());
-		}
+		if(sqlite3_bind_text(s, col, value.ptr is null ? "" : value.ptr, cast(int) value.length, cast(void*)-1) != SQLITE_OK)
+			throw new DatabaseException("bind " ~ db.error());
 	}
 
 	void bind(int col, float value){
@@ -525,18 +529,27 @@ template extract(A, T, R...){
 	void bind(int col, Variant v) {
 		if(v.peek!long)
 			bind(col, v.get!long);
-		if(v.peek!ulong)
+		else if(v.peek!ulong)
 			bind(col, v.get!ulong);
-		if(v.peek!int)
+		else if(v.peek!int)
 			bind(col, v.get!int);
-		if(v.peek!string)
+		else if(v.peek!(const(int)))
+			bind(col, v.get!(const(int)));
+		else if(v.peek!bool)
+			bind(col, v.get!bool ? 1 : 0);
+		else if(v.peek!DateTime)
+			bind(col, v.get!DateTime.toISOExtString());
+		else if(v.peek!string)
 			bind(col, v.get!string);
-		if(v.peek!float)
+		else if(v.peek!float)
 			bind(col, v.get!float);
-		if(v.peek!(byte[]))
+		else if(v.peek!(byte[]))
 			bind(col, v.get!(byte[]));
-		if(v.peek!(void*) && v.get!(void*) is null)
-			bind(col, cast(string) null);
+		else if(v.peek!(void*) && v.get!(void*) is null)
+			bind(col, null);
+		else
+			bind(col, v.coerce!string);
+		//assert(0, v.type.toString ~ " " ~ v.coerce!string);
 	}
 
 	~this(){