* | Inspire Internet Relay Chat Daemon |
* +------------------------------------+
*
- * InspIRCd: (C) 2002-2007 InspIRCd Development Team
+ * InspIRCd: (C) 2002-2008 InspIRCd Development Team
* See: http://www.inspircd.org/wiki/index.php/Credits
*
* This program is free but copyrighted software; see
- * the file COPYING for details.
+ * the file COPYING for details.
*
* ---------------------------------------------------
*/
#include <map>
#include "modules.h"
-/** SQLreq define.
- * This is the voodoo magic which lets us pass multiple
- * parameters to the SQLrequest constructor... voodoo...
- */
-#define SQLreq(a, b, c, d, e...) SQLrequest(a, b, c, (SQLquery(d), ##e))
-
/** Identifiers used to identify Request types
*/
#define SQLREQID "SQLv2 Request"
*/
SQLerror(SQLerrorNum i = NO_ERROR, const std::string &s = "")
: id(i), str(s)
- {
+ {
}
-
+
/** Return the ID of the error
*/
SQLerrorNum Id()
{
return id;
}
-
+
/** Set the ID of an error
* @param i The new error ID to set
* @return the ID which was set
id = i;
return id;
}
-
+
/** Set the error string for an error
* @param s The new error string to set
*/
{
str = s;
}
-
+
/** Return the error string for an error
*/
const char* Str()
{
if(str.length())
return str.c_str();
-
+
switch(id)
{
case NO_ERROR:
case QREPLY_FAIL:
return "Getting query result failed";
default:
- return "Unknown error";
+ return "Unknown error";
}
}
};
* the workaround for this isn't easy to describe simply, but in a nutshell what's really
* happening when - from the above example - you do this:
*
- * SQLrequest foo = SQLreq(this, target, "databaseid", "SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?", "Hello", "42");
+ * SQLrequest foo = SQLrequest(this, target, "databaseid", SQLquery("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?", "Hello", "42"));
*
* what's actually happening is functionally this:
*
- * SQLrequest foo = SQLreq(this, target, "databaseid", query("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?").addparam("Hello").addparam("42"));
+ * SQLrequest foo = SQLrequest(this, target, "databaseid", query("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?").addparam("Hello").addparam("42"));
*
* with 'query()' returning a reference to an object with a 'addparam()' member function which
* in turn returns a reference to that object. There are actually four ways you can create a
*
* SQLrequest foo = SQLrequest(this, target, "databaseid", (SQLquery("SELECT.. ?"), parameter, parameter));
*/
-class SQLquery
+class SQLquery : public classbase
{
public:
/** The query 'format string'
SQLquery(const std::string &query, const ParamL ¶ms)
: q(query), p(params)
{
- }
-
+ }
+
/** An overloaded operator for pushing parameters onto the parameter list
*/
template<typename T> SQLquery& operator,(const T &foo)
p.push_back(ConvToStr(foo));
return *this;
}
-
+
/** An overloaded operator for pushing parameters onto the parameter list.
* This has higher precedence than 'operator,' and can save on parenthesis.
*/
/** If an error occured, error.id will be any other value than NO_ERROR.
*/
SQLerror error;
-
+
/** Initialize an SQLrequest.
* For example:
*
- * SQLrequest req = SQLreq(MyMod, SQLModule, dbid, "INSERT INTO ircd_log_actors VALUES('','?')", nick);
+ * SQLrequest req = SQLrequest(MyMod, SQLModule, dbid, SQLquery("INSERT INTO ircd_log_actors VALUES('','?')" % nick));
*
* @param s A pointer to the sending module, where the result should be routed
* @param d A pointer to the receiving module, identified as implementing the 'SQL' feature
: Request(s, d, SQLREQID), query(q), dbid(databaseid), pri(false), id(0)
{
}
-
+
/** Set the priority of a request.
*/
void Priority(bool p = true)
{
pri = p;
}
-
+
/** Set the source of a request. You should not need to use this method.
*/
void SetSource(Module* mod)
SQLfield(const std::string &data = "", bool n = false)
: d(data), null(n)
{
-
+
}
};
* If an error occured the value of error.id will be any
* other value than NO_ERROR.
*/
- SQLerror error;
+ SQLerror error;
/**
* This will match query ID you were given when sending
* the request at an earlier time.
: Request(s, d, SQLRESID), id(i)
{
}
-
+
/**
* Return the number of rows in the result
* Note that if you have perfomed an INSERT
* @returns Number of rows in the result set.
*/
virtual int Rows() = 0;
-
+
/**
* Return the number of columns in the result.
* If you performed an UPDATE or INSERT which
* @returns Number of columns in the result set.
*/
virtual int Cols() = 0;
-
+
/**
* Get a string name of the column by an index number
* @param column The id number of a column
* @returns The column name associated with the given ID
*/
virtual std::string ColName(int column) = 0;
-
+
/**
* Get an index number for a column from a string name.
* An exception of type SQLbadColName will be thrown if
* @returns The ID number of the column provided
*/
virtual int ColNum(const std::string &column) = 0;
-
+
/**
* Get a string value in a given row and column
* This does not effect the internal cursor.
* @returns The value stored at [row,column] in the table
*/
virtual SQLfield GetValue(int row, int column) = 0;
-
+
/**
* Return a list of values in a row, this should
* increment an internal counter so you can repeatedly
* @returns A reference to the current row's SQLfieldList
*/
virtual SQLfieldList& GetRow() = 0;
-
+
/**
* As above, but return a map indexed by key name.
* The internal cursor (row counter) is incremented by one.
* @returns A reference to the current row's SQLfieldMap
*/
virtual SQLfieldMap& GetRowMap() = 0;
-
+
/**
* Like GetRow(), but returns a pointer to a dynamically
* allocated object which must be explicitly freed. For
* @returns A newly-allocated SQLfieldList
*/
virtual SQLfieldList* GetRowPtr() = 0;
-
+
/**
* As above, but return a map indexed by key name
* The internal cursor (row counter) is incremented by one.
* @returns A newly-allocated SQLfieldMap
*/
virtual SQLfieldMap* GetRowMapPtr() = 0;
-
+
/**
* Overloaded function for freeing the lists and maps
* returned by GetRowPtr or GetRowMapPtr.
virtual void Free(SQLfieldList* fl) = 0;
};
+
+/** SQLHost represents a <database> config line and is useful
+ * for storing in a map and iterating on rehash to see which
+ * <database> tags was added/removed/unchanged.
+ */
+class SQLhost
+{
+ public:
+ std::string id; /* Database handle id */
+ std::string host; /* Database server hostname */
+ std::string ip; /* resolved IP, needed for at least pgsql.so */
+ unsigned int port; /* Database server port */
+ std::string name; /* Database name */
+ std::string user; /* Database username */
+ std::string pass; /* Database password */
+ bool ssl; /* If we should require SSL */
+
+ SQLhost()
+ {
+ }
+
+ SQLhost(const std::string& i, const std::string& h, unsigned int p, const std::string& n, const std::string& u, const std::string& pa, bool s)
+ : id(i), host(h), port(p), name(n), user(u), pass(pa), ssl(s)
+ {
+ }
+
+ /** Overload this to return a correct Data source Name (DSN) for
+ * the current SQL module.
+ */
+ std::string GetDSN();
+};
+
+/** Overload operator== for two SQLhost objects for easy comparison.
+ */
+bool operator== (const SQLhost& l, const SQLhost& r)
+{
+ return (l.id == r.id && l.host == r.host && l.port == r.port && l.name == r.name && l.user == l.user && l.pass == r.pass && l.ssl == r.ssl);
+}
+
+
+/** QueryQueue, a queue of queries waiting to be executed.
+ * This maintains two queues internally, one for 'priority'
+ * queries and one for less important ones. Each queue has
+ * new queries appended to it and ones to execute are popped
+ * off the front. This keeps them flowing round nicely and no
+ * query should ever get 'stuck' for too long. If there are
+ * queries in the priority queue they will be executed first,
+ * 'unimportant' queries will only be executed when the
+ * priority queue is empty.
+ *
+ * We store lists of SQLrequest's here, by value as we want to avoid storing
+ * any data allocated inside the client module (in case that module is unloaded
+ * while the query is in progress).
+ *
+ * Because we want to work on the current SQLrequest in-situ, we need a way
+ * of accessing the request we are currently processing, QueryQueue::front(),
+ * but that call needs to always return the same request until that request
+ * is removed from the queue, this is what the 'which' variable is. New queries are
+ * always added to the back of one of the two queues, but if when front()
+ * is first called then the priority queue is empty then front() will return
+ * a query from the normal queue, but if a query is then added to the priority
+ * queue then front() must continue to return the front of the *normal* queue
+ * until pop() is called.
+ */
+
+class QueryQueue : public classbase
+{
+private:
+ typedef std::deque<SQLrequest> ReqDeque;
+
+ ReqDeque priority; /* The priority queue */
+ ReqDeque normal; /* The 'normal' queue */
+ enum { PRI, NOR, NON } which; /* Which queue the currently active element is at the front of */
+
+public:
+ QueryQueue()
+ : which(NON)
+ {
+ }
+
+ void push(const SQLrequest &q)
+ {
+ if(q.pri)
+ priority.push_back(q);
+ else
+ normal.push_back(q);
+ }
+
+ void pop()
+ {
+ if((which == PRI) && priority.size())
+ {
+ priority.pop_front();
+ }
+ else if((which == NOR) && normal.size())
+ {
+ normal.pop_front();
+ }
+
+ /* Reset this */
+ which = NON;
+
+ /* Silently do nothing if there was no element to pop() */
+ }
+
+ SQLrequest& front()
+ {
+ switch(which)
+ {
+ case PRI:
+ return priority.front();
+ case NOR:
+ return normal.front();
+ default:
+ if(priority.size())
+ {
+ which = PRI;
+ return priority.front();
+ }
+
+ if(normal.size())
+ {
+ which = NOR;
+ return normal.front();
+ }
+
+ /* This will probably result in a segfault,
+ * but the caller should have checked totalsize()
+ * first so..meh - moron :p
+ */
+
+ return priority.front();
+ }
+ }
+
+ std::pair<int, int> size()
+ {
+ return std::make_pair(priority.size(), normal.size());
+ }
+
+ int totalsize()
+ {
+ return priority.size() + normal.size();
+ }
+
+ void PurgeModule(Module* mod)
+ {
+ DoPurgeModule(mod, priority);
+ DoPurgeModule(mod, normal);
+ }
+
+private:
+ void DoPurgeModule(Module* mod, ReqDeque& q)
+ {
+ for(ReqDeque::iterator iter = q.begin(); iter != q.end(); iter++)
+ {
+ if(iter->GetSource() == mod)
+ {
+ if(iter->id == front().id)
+ {
+ /* It's the currently active query.. :x */
+ iter->SetSource(NULL);
+ }
+ else
+ {
+ /* It hasn't been executed yet..just remove it */
+ iter = q.erase(iter);
+ }
+ }
+ }
+ }
+};
+
+
#endif