pymssql-1.0.2/0000755000175000017500000000000011175604650011425 5ustar poxpoxpymssql-1.0.2/mssqldbmodule.c0000644000175000017500000023447211174155333014456 0ustar poxpox/* * _mssql module - low level Python module for communicating with MS SQL servers * * Initial Developer: * Joon-cheol Park , http://www.exman.pe.kr * * Active Developer: * Andrzej Kukula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA **************************************************************************** * CREDITS: * List ref count patch 2004.04.09 by Hans Roh * Significant contributions by Mark Pettit (thanks) * Multithreading patch by John-Peter Lee (thanks) ***************************************************************************/ #include #include #include // Py_ssize_t is defined starting from Python 2.5 #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #endif // lame assumption, but for now I have no better idea #ifndef MS_WINDOWS #define HAVE_FREETDS #endif #ifdef MS_WINDOWS #define DBNTWIN32 // must identify operating system environment #define NOCRYPT // must be defined under Visual C++ #include #include #include #include // DB-LIB header file (should always be included) #ifndef SQLINT8 #define SQLINT8 127 // from freetds/include/sybdb.h #endif #else #define MSDBLIB // we need FreeTDS to provide MSSQL API, // not Sybase API. See README.freetds for details #include #include #define SQLNUMERIC SYBNUMERIC #define SQLDECIMAL SYBDECIMAL #define SQLBIT SYBBIT #define SQLINT1 SYBINT1 #define SQLINT2 SYBINT2 #define SQLINT4 SYBINT4 #define SQLINT8 SYBINT8 #define SQLINTN SYBINTN #define SQLFLT4 SYBREAL #define SQLFLT8 SYBFLT8 #define SQLFLTN SYBFLTN #define SQLDATETIME SYBDATETIME #define SQLDATETIM4 SYBDATETIME4 #define SQLDATETIMN SYBDATETIMN #define SQLMONEY SYBMONEY #define SQLMONEY4 SYBMONEY4 #define SQLMONEYN SYBMONEYN #define SQLBINARY SYBBINARY #define SQLVARBINARY SYBVARBINARY #define SQLIMAGE SYBIMAGE #define SQLVARCHAR SYBVARCHAR #define SQLCHAR SYBCHAR #define SQLTEXT SYBTEXT #define BYTE unsigned char typedef unsigned char *LPBYTE; #endif #define TYPE_STRING 1 #define TYPE_BINARY 2 #define TYPE_NUMBER 3 #define TYPE_DATETIME 4 #define TYPE_DECIMAL 5 //#define PYMSSQLDEBUG 1 #include #include // include for string functions #define MSSQL_LASTMSGNO(self) \ ((self != NULL) ? self->last_msg_no : _mssql_last_msg_no) #define MSSQL_LASTMSGSEVERITY(self) \ ((self != NULL) ? self->last_msg_severity : _mssql_last_msg_severity) #define MSSQL_LASTMSGSTATE(self) \ ((self != NULL) ? self->last_msg_state : _mssql_last_msg_state) #define MSSQL_LASTMSGSTR(self) \ ((self != NULL) ? self->last_msg_str : _mssql_last_msg_str) #ifdef MS_WINDOWS #define DBFREELOGIN(login) dbfreelogin(login) #else #define DBFREELOGIN(login) dbloginfree(login) #define DBFLT4 DBREAL #endif #define check_and_raise(rtc,obj) \ { \ if (rtc == FAIL) { \ if (maybe_raise_MssqlDatabaseException(obj)) \ return NULL; \ } else if (*MSSQL_LASTMSGSTR(obj)) { \ if (maybe_raise_MssqlDatabaseException(obj)) \ return NULL; \ } \ } #define check_cancel_and_raise(rtc,obj) \ { \ if (rtc == FAIL) { \ db_cancel(obj); \ if (maybe_raise_MssqlDatabaseException(obj)) \ return NULL; \ } else if (*MSSQL_LASTMSGSTR(obj)) { \ if (maybe_raise_MssqlDatabaseException(obj)) \ return NULL; \ } \ } #define raise_MssqlDriverException(message) \ { \ PyErr_SetString(_mssql_MssqlDriverException, message); \ return NULL; \ } #define assert_connected(obj) \ { \ if (!obj->connected) \ raise_MssqlDriverException("Not connected to any MS SQL server"); \ } #define clr_metadata(conn) \ { \ Py_XDECREF(conn->column_names); \ Py_XDECREF(conn->column_types); \ conn->column_names = conn->column_types = NULL; \ conn->num_columns = 0; \ conn->last_dbresults = 0; \ } static PyObject *_mssql_module; static PyObject *_mssql_MssqlException; static PyObject *_mssql_MssqlDatabaseException; static PyObject *_mssql_MssqlDriverException; static PyObject *_decimal_module; // "decimal" module handle static PyObject *_decimal_class; static PyObject *_decimal_context; // Connection object typedef struct { PyObject_HEAD DBPROCESS *dbproc; // PDBPROCESS dbproc; int connected; // readonly property int query_timeout; // dbgettime() is absent in old FreeTDS... int rows_affected; // readonly property char *charset; // read/write property char *last_msg_str; // the error message buffer int last_msg_no; // most recent message code value int last_msg_severity; // most recent message severity value int last_msg_state; // most recent message state value int last_dbresults; // value of most recent call to dbresult() int num_columns; // number of columns in current result PyObject *column_names; // column names in a tuple PyObject *column_types; // column DB-API data types in a tuple int debug_queries; // whether to debug queries } _mssql_connection; // row iterator object typedef struct { PyObject_HEAD _mssql_connection *conn; // we just need to know what conn we iterate over } _mssql_row_iterator; // prototypes PyObject *_mssql_format_sql_command(PyObject * /*unused*/ self, PyObject *args); PyObject *_mssql_get_header(_mssql_connection *self); int rmv_lcl(char *, char *, size_t); PyObject *format_and_run_query(_mssql_connection *self, PyObject *args); PyObject *get_result(_mssql_connection *conn); PyObject *get_row(_mssql_connection *conn, int rowinfo); PyObject *fetch_next_row_dict(_mssql_connection *conn, int raise); void clr_err(_mssql_connection *self); int maybe_raise_MssqlDatabaseException(_mssql_connection *); RETCODE db_cancel(_mssql_connection *conn); static PyTypeObject _mssql_connection_type; static PyTypeObject _mssql_row_iterator_type; #define PYMSSQL_CHARSETBUFSIZE 100 #define MSSQLDB_MSGSIZE 1024 #define PYMSSQL_MSGSIZE (MSSQLDB_MSGSIZE*8) static char _mssql_last_msg_str[PYMSSQL_MSGSIZE] = { 0, }; static int _mssql_last_msg_no = 0; /* we'll be calculating max message severity returned by multiple calls to the handlers in a row, and if that severity is higher than minimum required, we'll raise exception. */ static int _mssql_last_msg_severity = 0; static int _mssql_last_msg_state = 0; /* there's only one error handler per dbinit() call, that is one for whole _mssql module; to be able to route error messages to appropriate connection instance, we must maintain a list of allocated connection objects */ struct _mssql_connection_list_node { struct _mssql_connection_list_node *next; _mssql_connection *obj; }; static struct _mssql_connection_list_node *connection_object_list = NULL; /* _mssql.Connection class methods *******************************************/ static char _mssql_select_db_doc[] = "select_db(dbname) -- Select the current database.\n\n\ This function selects given database as the current one.\n\ An exception is raised on failure."; // select_db static PyObject *_mssql_select_db(_mssql_connection *self, PyObject *args) { RETCODE rtc; char *dbname; char command[255]; PyObject *value, *search, *replace, *replaced = NULL; if (PyErr_Occurred()) return NULL; assert_connected(self); clr_err(self); if ((dbname = PyString_AsString(args)) == NULL) return NULL; /* this workaround is for db name truncation by ntwdblib.dll's version of dbuse(), thanks Luke Benstead */ /* if name starts in '[' and ends in ']' then we pass it as is; otherwise we replace all occurrences of ']' with ']]' then add '[' at the beginning and ']' at the end. */ if ((dbname[0] == '[') && dbname[strlen(dbname)-1] == ']') snprintf(command, 255, "USE %s", dbname); else { value = PyString_FromString(dbname); search = PyString_FromString("]"); replace = PyString_FromString("]]"); replaced = PyObject_CallMethod(value, "replace", "OO", search, replace); dbname = PyString_AsString(replaced); Py_DECREF(value); Py_DECREF(search); Py_DECREF(replace); snprintf(command, 255, "USE [%s]", dbname); Py_DECREF(replaced); } Py_BEGIN_ALLOW_THREADS rtc = dbcmd(self->dbproc, command); check_cancel_and_raise(rtc, self); Py_END_ALLOW_THREADS rtc = dbsqlexec(self->dbproc); check_and_raise(rtc, self); rtc = db_cancel(self); check_and_raise(rtc, self); Py_RETURN_NONE; } static char _mssql_execute_query_doc[] = "execute_query(query_string, params=None)\n\n\ This method sends a query to the MS SQL Server to which this object\n\ instance is connected. An exception is raised on failure. If there\n\ are pending results or rows prior to executing this command, they\n\ are silently discarded. After calling this method you may iterate\n\ over the connection object to get rows returned by the query.\n\n\ You can use Python formatting here and all values get properly\n\ quoted:\n\ conn.execute_query('SELECT * FROM empl WHERE id=%d', 13)\n\ conn.execute_query('SELECT * FROM empl WHERE id IN (%s)', ((5,6),))\n\ conn.execute_query('SELECT * FROM empl WHERE name=%s', 'John Doe')\n\ conn.execute_query('SELECT * FROM empl WHERE name LIKE %s', 'J%')\n\ conn.execute_query('SELECT * FROM empl WHERE name=%(name)s AND \\\n\ city=%(city)s', { 'name': 'John Doe', 'city': 'Nowhere' } )\n\ conn.execute_query('SELECT * FROM cust WHERE salesrep=%s \\\n\ AND id IN (%s)', ('John Doe', (1,2,3)))\n\ conn.execute_query('SELECT * FROM empl WHERE id IN (%s)',\\\n\ (tuple(xrange(4)),))\n\ conn.execute_query('SELECT * FROM empl WHERE id IN (%s)',\\\n\ (tuple([3,5,7,11]),))\n\n\ This method is intented to be used on queries that return results,\n\ i.e. SELECT. After calling this method AND reading all rows from,\n\ result rows_affected property contains number of rows returned by\n\ last command (this is how MS SQL returns it)."; static PyObject *_mssql_execute_query(_mssql_connection *self, PyObject *args) { if (format_and_run_query(self, args) == NULL) return NULL; // we must call dbresults() for nextresult() to work correctly if (get_result(self) == NULL) return NULL; Py_RETURN_NONE; } static char _mssql_execute_non_query_doc[] = "execute_non_query(query_string, params=None)\n\n\ This method sends a query to the MS SQL Server to which this object\n\ instance is connected. After completion, its results (if any) are\n\ discarded. An exception is raised on failure. If there are pending\n\ results or rows prior to executing this command, they are silently\n\n\ discarded. This method accepts Python formatting. Please see\n\ execute_query() for more details.\n\n\ This method is useful for INSERT, UPDATE, DELETE, and for Data\n\ Definition Language commands, i.e. when you need to alter your\n\ database schema.\n\n\ After calling this method, rows_affected property contains number\n\ of rows affected by last SQL command."; static PyObject *_mssql_execute_non_query(_mssql_connection *self, PyObject *args) { RETCODE rtc; if (format_and_run_query(self, args) == NULL) return NULL; Py_BEGIN_ALLOW_THREADS dbresults(self->dbproc); // only to get number of affected rows self->rows_affected = dbcount(self->dbproc); Py_END_ALLOW_THREADS rtc = db_cancel(self); check_and_raise(rtc, self); Py_RETURN_NONE; } static char _mssql_execute_scalar_doc[] = "execute_scalar(query_string, params=None)\n\n\ This method sends a query to the MS SQL Server to which this object\n\ instance is connected, then returns first column of first row from\n\ result. An exception is raised on failure. If there are pending\n\n\ results or rows prior to executing this command, they are silently\n\ discarded.\n\n\ This method accepts Python formatting. Please see execute_query()\n\ for details.\n\n\ This method is useful if you want just a single value, as in:\n\ conn.execute_scalar('SELECT COUNT(*) FROM employees')\n\n\ This method works in the same way as 'iter(conn).next()[0]'.\n\ Remaining rows, if any, can still be iterated after calling this\n\ method."; static PyObject *_mssql_execute_scalar(_mssql_connection *self, PyObject *args) { RETCODE rtc; PyObject *row, *val; if (format_and_run_query(self, args) == NULL) return NULL; if (get_result(self) == NULL) return NULL; Py_BEGIN_ALLOW_THREADS rtc = dbnextrow(self->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, self); self->rows_affected = dbcount(self->dbproc); // get rows affected, if possible // check if any rows (in case user passed e.g. DDL command here) if (rtc == NO_MORE_ROWS) { clr_metadata(self); self->last_dbresults = 0; // force next results, if any Py_RETURN_NONE; } row = get_row(self, rtc); // get first row of data if (row == NULL) return NULL; val = PyTuple_GetItem(row, 0); if (val == NULL) return NULL; Py_INCREF(val); // we don't call db_cancel, user can iterate // over remaining rows Py_DECREF(row); // row data no longer needed return val; } static char _mssql_execute_row_doc[] = "execute_row(query_string, params=None)\n\n\ This method sends a query to the MS SQL Server to which this object\n\ instance is connected, then returns first row of data from result.\n\n\ An exception is raised on failure. If there are pending results or\n\ rows prior to executing this command, they are silently discarded.\n\n\ This method accepts Python formatting. Please see execute_query()\n\ for details.\n\n\ This method is useful if you want just a single row and don't want\n\ or don't need to iterate, as in:\n\n\ conn.execute_row('SELECT * FROM employees WHERE id=%d', 13)\n\n\ This method works exactly the same as 'iter(conn).next()'. Remaining\n\ rows, if any, can still be iterated after calling this method."; static PyObject *_mssql_execute_row(_mssql_connection *self, PyObject *args) { if (format_and_run_query(self, args) == NULL) return NULL; return fetch_next_row_dict(self, 0); } /* datetime quoting (thanks Jan Finell ) described under "Writing International Transact-SQL Statements" in BOL beware the order: isinstance(x,datetime.date)=True even if x is datetime.datetime ! Also round x.microsecond to milliseconds, otherwise we get Msg 241, Level 16, State 1: Syntax error */ /* this is internal method, called for each element of a sequence. if it cannot be quoted, None is returned */ PyObject *_quote_simple_value(PyObject *value) { if (value == Py_None) return PyString_FromString("NULL"); if (PyBool_Check(value)) { // if bool then safe to return as is Py_INCREF(value); return value; } if (PyInt_Check(value) || PyLong_Check(value) || PyFloat_Check(value)) { // return as is Py_INCREF(value); return value; } if (PyUnicode_Check(value)) { // equivalent of // x = "N'" + value.encode('utf8').replace("'", "''") + "'" PyObject *search, *replace, *encoded, *replaced, *quoted; search = PyString_FromString("'"); replace = PyString_FromString("''"); encoded = PyUnicode_AsUTF8String(value); replaced = PyObject_CallMethod(encoded, "replace", "OO", search, replace); Py_DECREF(search); Py_DECREF(replace); Py_DECREF(encoded); quoted = PyString_FromString("N'"); PyString_ConcatAndDel("ed, replaced); // replaced is DECREFed if (quoted == NULL) return NULL; PyString_ConcatAndDel("ed, PyString_FromString("'")); if (quoted == NULL) return NULL; return quoted; } if (PyString_Check(value)) { // x = "'" + x.replace("'", "''") + "'" PyObject *search, *replace, *replaced, *quoted; search = PyString_FromString("'"); replace = PyString_FromString("''"); replaced = PyObject_CallMethod(value, "replace", "OO", search, replace); Py_DECREF(search); Py_DECREF(replace); quoted = PyString_FromString("'"); PyString_ConcatAndDel("ed, replaced); // replaced is DECREFed if (quoted == NULL) return NULL; PyString_ConcatAndDel("ed, PyString_FromString("'")); if (quoted == NULL) return NULL; return quoted; } if (PyDateTime_CheckExact(value)) { PyObject *quoted, *val, *format, *tuple; // prepare arguments for format tuple = PyTuple_New(7); if (tuple == NULL) return NULL; PyTuple_SET_ITEM(tuple, 0, PyObject_GetAttrString(value, "year")); PyTuple_SET_ITEM(tuple, 1, PyObject_GetAttrString(value, "month")); PyTuple_SET_ITEM(tuple, 2, PyObject_GetAttrString(value, "day")); PyTuple_SET_ITEM(tuple, 3, PyObject_GetAttrString(value, "hour")); PyTuple_SET_ITEM(tuple, 4, PyObject_GetAttrString(value, "minute")); PyTuple_SET_ITEM(tuple, 5, PyObject_GetAttrString(value, "second")); val = PyObject_GetAttrString(value, "microsecond"); PyTuple_SET_ITEM(tuple, 6, PyLong_FromLong(PyLong_AsLong(val) / 1000)); Py_DECREF(val); format = PyString_FromString("{ts '%04d-%02d-%02d %02d:%02d:%02d.%d'}"); quoted = PyString_Format(format, tuple); Py_DECREF(format); Py_DECREF(tuple); return quoted; } if (PyDate_CheckExact(value)) { PyObject *quoted, *format, *tuple; // prepare arguments for format tuple = PyTuple_New(3); if (tuple == NULL) return NULL; PyTuple_SET_ITEM(tuple, 0, PyObject_GetAttrString(value, "year")); PyTuple_SET_ITEM(tuple, 1, PyObject_GetAttrString(value, "month")); PyTuple_SET_ITEM(tuple, 2, PyObject_GetAttrString(value, "day")); format = PyString_FromString("{d '%04d-%02d-%02d'}"); quoted = PyString_Format(format, tuple); Py_DECREF(format); Py_DECREF(tuple); return quoted; } // return None so caller knows we're succeeded, but not quoted anything, // so it can try sequence types Py_RETURN_NONE; } /* this function quotes its argument if it's a simple data type, or flattens it if it's a list or a tuple. returns new reference to quoted string */ PyObject *_quote_or_flatten(PyObject *data) { PyObject *res = _quote_simple_value(data); if (res == NULL) return NULL; if (res != Py_None) { // we got something, return it return res; } // we got Py_None from _quote_simple_value, so it wasn't simple type // we only accept a list or a tuple below this point Py_DECREF(res); if (PyList_Check(data)) { PyObject *str; int i; Py_ssize_t len = PyList_GET_SIZE(data); str = PyString_FromString(""); // an empty string if (str == NULL) return NULL; for (i = 0; i < len; i++) { PyObject *o, *quoted, *quotedstr; o = PyList_GET_ITEM(data, i); // borrowed quoted = _quote_simple_value(o); // new ref if (quoted == NULL) { Py_DECREF(str); return NULL; } if (quoted == Py_None) { Py_DECREF(Py_None); Py_DECREF(str); PyErr_SetString(PyExc_ValueError, "argument error, expected simple value, found nested sequence."); return NULL; } quotedstr = PyObject_Str(quoted); Py_DECREF(quoted); if (quotedstr == NULL) { Py_DECREF(str); return NULL; } PyString_ConcatAndDel(&str, quotedstr); if (str == NULL) return NULL; if (i < len-1) { PyString_ConcatAndDel(&str, PyString_FromString(",")); if (str == NULL) return NULL; } } return str; } if (PyTuple_Check(data)) { PyObject *str; int i; Py_ssize_t len = PyTuple_GET_SIZE(data); str = PyString_FromString(""); // an empty string if (str == NULL) return NULL; for (i = 0; i < len; i++) { PyObject *o, *quoted, *quotedstr; o = PyTuple_GET_ITEM(data, i); // borrowed quoted = _quote_simple_value(o); // new ref if (quoted == NULL) { Py_DECREF(str); return NULL; } if (quoted == Py_None) { Py_DECREF(Py_None); Py_DECREF(str); PyErr_SetString(PyExc_ValueError, "argument error, expected simple value, found nested sequence."); return NULL; } quotedstr = PyObject_Str(quoted); Py_DECREF(quoted); if (quotedstr == NULL) { Py_DECREF(str); return NULL; } PyString_ConcatAndDel(&str, quotedstr); if (str == NULL) return NULL; if (i < len-1) { PyString_ConcatAndDel(&str, PyString_FromString(",")); if (str == NULL) return NULL; } } return str; } PyErr_SetString(PyExc_ValueError, "expected simple type, a tuple or a list."); return NULL; } static char _mssql_quote_data_doc[] = "_quote_data(data) -- quote value so it is safe for query string.\n\n\ This method transforms given data into form suitable for putting in\n\ a query string and returns transformed data. This feature may\n\ not be very useful by itself, so examples below will be from other\n\ method, _format_sql_command.\n\n\ Argument may be one of the simple types: string, unicode, bool,\n\ int, long, float, None, or it can be a tuple, or a dictionary.\n\ If it's a tuple or a dict, it's elements can only be of the same\n\ simple types, or tuple, or list. If such an element is encountered\n\ it is 'flattened' by producing a comma separated string of its items.\n\ This is useful for IN operators. Examples:\n\ _mssql._format_sql_command('SELECT * FROM cust WHERE id=%d', 13)\n\ \n\ _mssql._format_sql_command('SELECT * FROM cust WHERE id IN (%s)',\n\ ((1,2,3,4,5),))\n\ you can see here a tuple with another tuple as the only element.\n\ If you need to pass a list, an xrange, a generator or an iterator,\n\ just use tuple() constructor:\n\ _mssql._format_sql_command('SELECT * FROM cust WHERE id IN (%s)',\n\ (tuple(xrange(4)),)\n"; PyObject *_mssql_quote_data(PyObject * /*unused*/ self, PyObject *data) { PyObject *res; res = _quote_simple_value(data); if (res == NULL) return NULL; if (res != Py_None) { // we got something, return it return res; } // we got Py_None from _quote_simple_value, it may be a sequence type Py_DECREF(res); // first check if dict, and if so, quote values if (PyDict_Check(data)) { PyObject *dict, *k, *v; Py_ssize_t pos = 0; dict = PyDict_New(); // new dictionary for results if (dict == NULL) return NULL; while (PyDict_Next(data, &pos, &k, &v)) { PyObject *quoted = _quote_or_flatten(v); // new ref if (quoted == NULL) { Py_DECREF(dict); return NULL; } PyDict_SetItem(dict, k, quoted); // dict[k] = _quote_single_value(v) Py_DECREF(quoted); } return dict; } if (PyTuple_Check(data)) { // input is a tuple PyObject *res; int i; Py_ssize_t len = PyTuple_GET_SIZE(data); res = PyTuple_New(len); if (res == NULL) return NULL; for (i = 0; i < len; i++) { PyObject *o, *quoted; o = PyTuple_GET_ITEM(data, i); // borrowed quoted = _quote_or_flatten(o); // new ref if (quoted == NULL) { Py_DECREF(res); return NULL; } PyTuple_SET_ITEM(res, i, quoted); // ref stolen } return res; } PyErr_SetString(PyExc_ValueError, "expected simple type, a tuple or a dictionary."); return NULL; } static char _mssql_format_sql_command_doc[] = "_format_sql_command(format_str, params) -- build an SQL command.\n\n\ This method outputs a string with all '%' placeholders replaced\n\ with given value(s). Mechanism is similar to built-in Python\n\ string interpolation, but the values get quoted before interpolation.\n\ Quoting means transforming data into form suitable for puttin\n\ in a query string.\n\nExamples:\n\ _mssql._format_sql_command(\n\ 'UPDATE customers SET salesrep=%s WHERE id IN (%s)',\n\ ('John Doe', (1,2,3)))\n\ Please see docs for _quote_data() for details."; PyObject *_mssql_format_sql_command(PyObject * /*unused*/ self, PyObject *args) { PyObject *format = NULL, *params = NULL, *quoted, *ret; if (!PyArg_ParseTuple(args, "O|O:_format_sql_command", &format, ¶ms)) return NULL; if (params == NULL) { // no params given, all we can do is return the string Py_INCREF(format); return format; } // check if quotable type; WARNING: update below // whenever quote_simple_value() function changes if (!((params == Py_None) || PyBool_Check(params) || PyInt_Check(params) || PyLong_Check(params) || PyFloat_Check(params) || PyUnicode_Check(params) || PyString_Check(params) || PyDateTime_CheckExact(params) || PyDate_CheckExact(params) || PyTuple_Check(params) || PyDict_Check(params))) { PyErr_SetString(PyExc_ValueError, "'params' arg can be only a tuple or a dictionary."); return NULL; } // we got acceptable params, quote them quoted = _mssql_quote_data(self, params); if (quoted == NULL) return NULL; ret = PyString_Format(format, quoted); Py_DECREF(quoted); return ret; } static char _mssql_get_header_doc[] = "get_header() -- get the Python DB-API compliant header information.\n\n\ This method is infrastructure and don't need to be called by your\n\ code. It returns a list of 7-element tuples describing current\n\ result header. Only name and DB-API compliant type is filled, rest\n\ of the data is None, as permitted by the specs."; PyObject *_mssql_get_header(_mssql_connection *self) { int col; PyObject *colname, *coltype, *headertuple; if (get_result(self) == NULL) return NULL; if (self->num_columns == 0) // either not returned any rows or no more results Py_RETURN_NONE; headertuple = PyTuple_New(self->num_columns); if (headertuple == NULL) raise_MssqlDriverException("Could not create tuple for column header."); for (col = 1; col <= self->num_columns; col++) { // loop on all columns PyObject *colinfotuple = NULL; colinfotuple = PyTuple_New(7); if (colinfotuple == NULL) raise_MssqlDriverException("Could not create tuple for column header details."); colname = PyTuple_GetItem(self->column_names, col-1); coltype = PyTuple_GetItem(self->column_types, col-1); Py_INCREF(colname); Py_INCREF(coltype); PyTuple_SET_ITEM(colinfotuple, 0, colname); PyTuple_SET_ITEM(colinfotuple, 1, coltype); Py_INCREF(Py_None); PyTuple_SET_ITEM(colinfotuple, 2, Py_None); Py_INCREF(Py_None); PyTuple_SET_ITEM(colinfotuple, 3, Py_None); Py_INCREF(Py_None); PyTuple_SET_ITEM(colinfotuple, 4, Py_None); Py_INCREF(Py_None); PyTuple_SET_ITEM(colinfotuple, 5, Py_None); Py_INCREF(Py_None); PyTuple_SET_ITEM(colinfotuple, 6, Py_None); PyTuple_SET_ITEM(headertuple, col-1, colinfotuple); } return headertuple; } static char _mssql_cancel_doc[] = "cancel() -- cancel all pending results.\n\n\ This function cancels all pending results from last SQL operation.\n\ It can be called more than one time in a row. No exception is\n\ raised in this case."; static PyObject *_mssql_cancel(_mssql_connection *self, PyObject *args) { RETCODE rtc; if (PyErr_Occurred()) return NULL; assert_connected(self); clr_err(self); rtc = db_cancel(self); check_and_raise(rtc, self); Py_RETURN_NONE; } static char _mssql_nextresult_doc[] = "nextresult() -- move to the next result, skipping all pending rows.\n\n\ This method fetches and discards any rows remaining from current\n\ result, then it advances to next (if any). Returns True value if\n\ next result is available, None otherwise."; static PyObject *_mssql_nextresult(_mssql_connection *self, PyObject *args) { RETCODE rtc; if (PyErr_Occurred()) return NULL; assert_connected(self); clr_err(self); Py_BEGIN_ALLOW_THREADS rtc = dbnextrow(self->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, self); while (rtc != NO_MORE_ROWS) { Py_BEGIN_ALLOW_THREADS rtc = dbnextrow(self->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, self); } self->last_dbresults = 0; // force call to dbresults() inside get_result() if (get_result(self) == NULL) return NULL; if (self->last_dbresults != NO_MORE_RESULTS) return PyInt_FromLong(1); Py_RETURN_NONE; } static char _mssql_close_doc[] = "close() -- close connection to an MS SQL Server.\n\n\ This function tries to close the connection and free all memory\n\ used. It can be called more than one time in a row. No exception\n\ is raised in this case."; static PyObject *_mssql_close(_mssql_connection *self, PyObject *args) { #ifdef MS_WINDOWS RETCODE rtc; #endif struct _mssql_connection_list_node *p, *n; if (self == NULL) // this can be true if called from tp_dealloc Py_RETURN_NONE; if (!self->connected) Py_RETURN_NONE; clr_err(self); #ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS rtc = dbclose(self->dbproc); self->dbproc = NULL; Py_END_ALLOW_THREADS check_and_raise(rtc, self); #else Py_BEGIN_ALLOW_THREADS dbclose(self->dbproc); Py_END_ALLOW_THREADS #endif self->connected = 0; /* find and remove the connection from internal list used for error message routing */ n = connection_object_list; p = NULL; while (n != NULL) { if (n->obj == self) { // found PyMem_Free(n->obj->last_msg_str); PyMem_Free(n->obj->charset); n->obj->last_msg_str = NULL; n->obj->charset = NULL; if (p != NULL) { p->next = n->next; PyMem_Free(n); } else connection_object_list = n->next; break; } p = n; n = n->next; } Py_RETURN_NONE; } /* this method returns pointer to a new RowIterator instance (yet we don't know if there are any results, it will turn out later) */ static PyObject *_mssql___iter__(_mssql_connection *self) { _mssql_row_iterator *iter; assert_connected(self); clr_err(self); iter = PyObject_NEW(_mssql_row_iterator, &_mssql_row_iterator_type); if (iter == NULL) return NULL; Py_INCREF(self); iter->conn = self; return (PyObject *) iter; } /* _mssql.Connection class properties ****************************************/ /* conn.query_timeout property getter */ PyObject *_mssql_query_timeout_get(_mssql_connection *self, void *closure) { return PyInt_FromLong(self->query_timeout); } /* conn.query_timeout property setter */ int _mssql_query_timeout_set(_mssql_connection *self, PyObject *val, void *closure) { long intval; RETCODE rtc; if (PyErr_Occurred()) return -1; // properties return -1 on error! clr_err(self); if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete 'query_timeout' attribute."); return -1; } if (!PyInt_Check(val)) { PyErr_SetString(PyExc_TypeError, "The 'query_timeout' attribute value must be an integral number."); return -1; } intval = PyInt_AS_LONG(val); if (intval < 0) { PyErr_SetString(PyExc_ValueError, "The 'query_timeout' attribute value must be >= 0."); return -1; } // WARNING - by inspecting FreeTDS sources it turns out that it affects // all connections made from this application rtc = dbsettime(intval); if (rtc == FAIL) { // can't use check_and_raise if (maybe_raise_MssqlDatabaseException(self)) return -1; } else if (*MSSQL_LASTMSGSTR(self)) if (maybe_raise_MssqlDatabaseException(self)) return -1; self->query_timeout = intval; return 0; } /* conn.identity property getter */ PyObject *_mssql_identity_get(_mssql_connection *self, void *closure) { RETCODE rtc; PyObject *row, *id; if (PyErr_Occurred()) return NULL; assert_connected(self); clr_err(self); db_cancel(self); // cancel any pending results Py_BEGIN_ALLOW_THREADS dbcmd(self->dbproc, "SELECT @@IDENTITY"); rtc = dbsqlexec(self->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, self); if (get_result(self) == NULL) return NULL; Py_BEGIN_ALLOW_THREADS rtc = dbnextrow(self->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, self); if (rtc == NO_MORE_ROWS) { // check it, just to be pendatically sure clr_metadata(self); self->last_dbresults = 0; // force next results, if any Py_RETURN_NONE; } row = get_row(self, rtc); // get first row of data if (row == NULL) return NULL; id = PyTuple_GetItem(row, 0); if (id == NULL) return NULL; Py_INCREF(id); db_cancel(self); Py_DECREF(row); // row data no longer needed return id; } /* _mssql module methods *****************************************************/ /* err_handler() and msg_handler() are callbacks called by DB-Library or FreeTDS whenever database or library error occurs. There are one err_handler and one msg_handler per dbinit() call, that is, presently, for the whole _mssql module. But we maintain multiple connections and messages may relate to any of them, so we need a way to know which connection object route message to. For this purpose we maintain a list of DBPROCESS pointers and compare dbproc parameter until they match. Then, there's also a filter. We don't consider messages that have severity value less than _mssql.min_error_severity. This is because they are informational only, and don't represent any real value (for example if you call select_db, "Database changed" informational message is returned). The handlers used to be separated in previous versions of pymssql, but it turned out that it has no added value, so I decided to combine them, and I checked that min_error_severity of 6 is ok for most users. But they can change it if they need. */ int err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { struct _mssql_connection_list_node *p, *n; PyObject *o; long lo; char *mssql_lastmsgstr = _mssql_last_msg_str; int *mssql_lastmsgno = &_mssql_last_msg_no; int *mssql_lastmsgseverity = &_mssql_last_msg_severity; int *mssql_lastmsgstate = &_mssql_last_msg_state; #ifdef PYMSSQLDEBUG fprintf(stderr, "\n*** err_handler(dbproc = %p, severity = %d, dberr = %d, \ oserr = %d, dberrstr = '%s', oserrstr = '%s'); DBDEAD(dbproc) = %d\n", (void *)dbproc, severity, dberr, oserr, dberrstr, oserrstr, DBDEAD(dbproc)); fprintf(stderr, "*** previous max severity = %d\n\n", _mssql_last_msg_severity); #endif // mute if below the acceptable threshold o = PyObject_GetAttr(_mssql_module, PyString_FromString("min_error_severity")); lo = PyInt_AS_LONG(o); Py_DECREF(o); if (severity < lo) return INT_CANCEL; // ntwdblib.dll 2000.80.2273.0 hangs Python here // try to find out which connection this handler belongs to. // do it by scanning the list n = connection_object_list; p = NULL; while (n != NULL) { if (n->obj->dbproc == dbproc) { // found mssql_lastmsgstr = n->obj->last_msg_str; mssql_lastmsgno = &n->obj->last_msg_no; mssql_lastmsgseverity = &n->obj->last_msg_severity; mssql_lastmsgstate = &n->obj->last_msg_state; break; } p = n; n = n->next; } // if not found, pointers will point to global vars, which is good // calculate the maximum severity of all messages in a row if (severity > *mssql_lastmsgseverity) { *mssql_lastmsgseverity = severity; *mssql_lastmsgno = dberr; *mssql_lastmsgstate = oserr; } // but get all of them regardless of severity snprintf(mssql_lastmsgstr + strlen(mssql_lastmsgstr), PYMSSQL_MSGSIZE - strlen(mssql_lastmsgstr), "DB-Lib error message %d, severity %d:\n%s\n", dberr, severity, dberrstr); if ((oserr != DBNOERR) && (oserr != 0)) { /* get a textual representation of the error code */ #ifdef MS_WINDOWS HMODULE hModule = NULL; // default to system source LPSTR msg; DWORD buflen; DWORD fmtflags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM; if (oserr > NERR_BASE && oserr <= MAX_NERR) { // this can take a long time... Py_BEGIN_ALLOW_THREADS hModule = LoadLibraryEx(TEXT("netmsg.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE); Py_END_ALLOW_THREADS if (hModule != NULL) fmtflags |= FORMAT_MESSAGE_FROM_HMODULE; } buflen = FormatMessageA(fmtflags, hModule, // module to get message from (NULL == system) oserr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language (LPSTR) &msg, 0, NULL); if (buflen) { #else #define EXCOMM 9 char *msg = strerror(oserr); #endif snprintf(mssql_lastmsgstr + strlen(mssql_lastmsgstr), PYMSSQL_MSGSIZE - strlen(mssql_lastmsgstr), "%s error during %s ", (severity == EXCOMM) ? "Net-Lib" : "Operating system", oserrstr); snprintf(mssql_lastmsgstr + strlen(mssql_lastmsgstr), PYMSSQL_MSGSIZE - strlen(mssql_lastmsgstr), "Error %d - %s", oserr, msg); #ifdef MS_WINDOWS LocalFree(msg); // unload netmsg.dll if (hModule != NULL) FreeLibrary(hModule); } #endif } return INT_CANCEL; /* sigh FreeTDS sets DBDEAD on incorrect login! */ } /* gosh! different prototypes! (again...) */ #ifdef MS_WINDOWS #define LINE_T DBUSMALLINT #else #define LINE_T int #endif int msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, LINE_T line) { struct _mssql_connection_list_node *p, *n; PyObject *o; long lo; char *mssql_lastmsgstr = _mssql_last_msg_str; int *mssql_lastmsgno = &_mssql_last_msg_no; int *mssql_lastmsgseverity = &_mssql_last_msg_severity; int *mssql_lastmsgstate = &_mssql_last_msg_state; #ifdef PYMSSQLDEBUG fprintf(stderr, "\n+++ msg_handler(dbproc = %p, msgno = %d, msgstate = %d, \ severity = %d, msgtext = '%s', srvname = '%s', procname = '%s', line = %d\n", (void *)dbproc, msgno, msgstate, severity, msgtext, srvname, procname, line); fprintf(stderr, "+++ previous max severity = %d\n\n", _mssql_last_msg_severity); #endif // mute if below the acceptable threshold o = PyObject_GetAttr(_mssql_module, PyString_FromString("min_error_severity")); lo = PyInt_AS_LONG(o); Py_DECREF(o); if (severity < lo) return 0; // try to find out which connection this handler belongs to. // do it by scanning the list n = connection_object_list; p = NULL; while (n != NULL) { if (n->obj->dbproc == dbproc) { // found mssql_lastmsgstr = n->obj->last_msg_str; mssql_lastmsgno = &n->obj->last_msg_no; mssql_lastmsgseverity = &n->obj->last_msg_severity; mssql_lastmsgstate = &n->obj->last_msg_state; break; } p = n; n = n->next; } // calculate the maximum severity of all messages in a row // fill the remaining fields if this is going to raise the exception if (*mssql_lastmsgseverity < severity) { *mssql_lastmsgseverity = severity; *mssql_lastmsgno = msgno; *mssql_lastmsgstate = msgstate; } // but get all of them regardless of severity if ((procname != NULL) && *procname) snprintf(mssql_lastmsgstr + strlen(mssql_lastmsgstr), PYMSSQL_MSGSIZE - strlen(mssql_lastmsgstr), "SQL Server message %ld, severity %d, state %d, procedure %s, line %d:\n%s\n", (long)msgno, severity, msgstate, procname, line, msgtext); else snprintf(mssql_lastmsgstr + strlen(mssql_lastmsgstr), PYMSSQL_MSGSIZE - strlen(mssql_lastmsgstr), "SQL Server message %ld, severity %d, state %d, line %d:\n%s\n", (long)msgno, severity, msgstate, line, msgtext); return 0; } static char _mssql_connect_doc[] = "connect(server, user, password, trusted, charset, database)\n\ -- connect to an MS SQL Server.\n\ This method returns an instance of class _mssql.MssqlConnection.\n\n\ server - an instance of MS SQL server to connect to; it can be host\n\ name, 'host,port' or 'host:port' syntax, or server\n\ identifier from freetds.conf config file. On Windows you\n\ can also use r'hostname\\instancename' to connect to a named\n\ instance.\n\ user - user name to login as.\n\ password - password to authenticate with.\n\ trusted - use trusted connection (Windows Integrated Authentication)\n\ instead of SQL user and password, user and password\n\ are ignored. Only available on Windows.\n\ charset - character set name to set for the connection.\n\ database - database name to select initially as the current database.\n\ max_conn - maximum number of simultaneous connections allowed; default\n\ is 25\n\ "; static PyObject *_mssql_connect(_mssql_connection *self, PyObject *args, PyObject *kwargs) { _mssql_connection *dbconn; LOGINREC *login; // DB-LIB login structure char *server = NULL, *user = NULL, *password = NULL; char *database = NULL, *charset = NULL; char *p; int trusted = 0, max_conn = 25; RETCODE rtc; struct _mssql_connection_list_node *n; PyObject *ologintimeout, *o; static char *kwlist[] = { "server", "user", "password", "trusted", "charset", "database", "max_conn", NULL }; if(!PyArg_ParseTupleAndKeywords(args, kwargs, "s|zzizzi:connect", kwlist, &server, &user, &password, &trusted, &charset, &database, &max_conn)) return NULL; clr_err(NULL); #ifdef PYMSSQLDEBUG fprintf(stderr, "_mssql.connect(server=\"%s\", user=\"%s\", password=\"%s\", trusted=\"%d\", charset=\"%s\", database=\"%s\", max_conn=\"%d\")\n", server, user, password, trusted, charset, database, max_conn); #endif // lame hack to improve portability, will break with IPv6 // FreeTDS doesn't accept ',' as port number separator #ifdef MS_WINDOWS // Windows accepts syntax 'host.name.or.ip,port' with comma p = strchr(server, ':'); if (p != NULL) *p = ','; #else // FreeTDS accepts syntax 'host.name.or.ip:port' with colon p = strchr(server, ','); if (p != NULL) *p = ':'; #endif login = dblogin(); // get login record from DB-LIB if (login == NULL) raise_MssqlDriverException("Out of memory"); if (max_conn < 0) raise_MssqlDriverException("max_conn value must be greater than 0."); // these don't need to release GIL DBSETLUSER(login, user); DBSETLPWD(login, password); DBSETLAPP(login, "pymssql"); dbsetmaxprocs(max_conn); #ifdef MS_WINDOWS DBSETLVERSION(login, DBVER60); #else DBSETLHOST(login, server); #endif #ifdef MS_WINDOWS if (trusted) DBSETLSECURE(login); #endif dbconn = PyObject_NEW(_mssql_connection, &_mssql_connection_type); if (dbconn == NULL) { DBFREELOGIN(login); raise_MssqlDriverException("Could not create _mssql.MssqlConnection object"); } dbconn->connected = 0; dbconn->column_names = dbconn->column_types = NULL; dbconn->num_columns = 0; dbconn->debug_queries = 0; dbconn->last_msg_str = PyMem_Malloc(PYMSSQL_MSGSIZE); dbconn->charset = PyMem_Malloc(PYMSSQL_CHARSETBUFSIZE); if ((dbconn->last_msg_str == NULL) || (dbconn->charset == NULL)) { Py_DECREF(dbconn); DBFREELOGIN(login); raise_MssqlDriverException("Out of memory"); } *dbconn->last_msg_str = 0; *dbconn->charset = 0; // create list node and allocate message buffer n = PyMem_Malloc(sizeof(struct _mssql_connection_list_node)); if (n == NULL) { Py_DECREF(dbconn); // also frees last_msg_str and charset buf DBFREELOGIN(login); raise_MssqlDriverException("Out of memory"); } // prepend this connection to the list, will be needed soon, because // dbopen can raise errors n->next = connection_object_list; n->obj = dbconn; connection_object_list = n; // set the character set name if (charset) { strncpy(dbconn->charset, charset, PYMSSQL_CHARSETBUFSIZE); #ifndef MS_WINDOWS if (DBSETLCHARSET(login, dbconn->charset) == FAIL) { Py_DECREF(dbconn); DBFREELOGIN(login); raise_MssqlDriverException("Could not set character set"); } #endif } // set login timeout ologintimeout = PyObject_GetAttrString(_mssql_module, "login_timeout"); if (ologintimeout == NULL) { connection_object_list = connection_object_list->next; PyMem_Free(n); Py_DECREF(dbconn); // also frees last_msg_str and charset buf DBFREELOGIN(login); return NULL; } dbsetlogintime((int) PyInt_AS_LONG(ologintimeout)); Py_DECREF(ologintimeout); // connect to the database Py_BEGIN_ALLOW_THREADS dbconn->dbproc = dbopen(login, server); Py_END_ALLOW_THREADS if (dbconn->dbproc == NULL) { connection_object_list = connection_object_list->next; PyMem_Free(n); Py_DECREF(dbconn); DBFREELOGIN(login); maybe_raise_MssqlDatabaseException(NULL); // we hope it will raise something if (!PyErr_Occurred()) // but if not, give a meaningful response PyErr_SetString(_mssql_MssqlDriverException, "Connection to the database failed for an unknown reason."); return NULL; } // these don't need to release GIL DBFREELOGIN(login); // Frees a login record. dbconn->connected = 1; // set initial connection properties to some reasonable values Py_BEGIN_ALLOW_THREADS dbcmd(dbconn->dbproc, "SET ARITHABORT ON;" "SET CONCAT_NULL_YIELDS_NULL ON;" "SET ANSI_NULLS ON;" "SET ANSI_NULL_DFLT_ON ON;" "SET ANSI_PADDING ON;" "SET ANSI_WARNINGS ON;" "SET ANSI_NULL_DFLT_ON ON;" "SET CURSOR_CLOSE_ON_COMMIT ON;" "SET QUOTED_IDENTIFIER ON" ); rtc = dbsqlexec(dbconn->dbproc); Py_END_ALLOW_THREADS if (rtc == FAIL) { raise_MssqlDriverException("Could not set connection properties"); // connection is still valid and open } db_cancel(dbconn); clr_err(dbconn); if (database) { o = _mssql_select_db(dbconn, PyString_FromString(database)); if (o == NULL) return NULL; } return (PyObject *)dbconn; } /* _mssql.Connection class definition ****************************************/ static void _mssql_connection_dealloc(_mssql_connection *self) { if (self->connected) { PyObject *o = _mssql_close(self, NULL); Py_XDECREF(o); } if (self->last_msg_str) PyMem_Free(self->last_msg_str); if (self->charset) PyMem_Free(self->charset); Py_XDECREF(self->column_names); Py_XDECREF(self->column_types); PyObject_Free((void *) self); } static PyObject *_mssql_connection_repr(_mssql_connection *self) { return PyString_FromFormat("<%s mssql connection at %p>", self->connected ? "Open" : "Closed", self); } /* instance methods */ static PyMethodDef _mssql_connection_methods[] = { { "select_db", (PyCFunction) _mssql_select_db, METH_O, _mssql_select_db_doc }, { "execute_query", (PyCFunction) _mssql_execute_query, METH_VARARGS,_mssql_execute_query_doc }, { "execute_non_query", (PyCFunction) _mssql_execute_non_query, METH_VARARGS,_mssql_execute_non_query_doc }, { "execute_scalar", (PyCFunction) _mssql_execute_scalar, METH_VARARGS,_mssql_execute_scalar_doc }, { "execute_row", (PyCFunction) _mssql_execute_row, METH_VARARGS,_mssql_execute_row_doc }, { "get_header", (PyCFunction) _mssql_get_header, METH_NOARGS, _mssql_get_header_doc }, { "cancel", (PyCFunction) _mssql_cancel, METH_NOARGS, _mssql_cancel_doc }, { "nextresult", (PyCFunction) _mssql_nextresult, METH_NOARGS, _mssql_nextresult_doc }, { "close", (PyCFunction) _mssql_close, METH_NOARGS, _mssql_close_doc }, { NULL, NULL, 0, NULL } }; /* properties */ static PyGetSetDef _mssql_connection_getset[] = { { "query_timeout", (getter) _mssql_query_timeout_get, (setter) _mssql_query_timeout_set, "Query timeout in seconds. This value affects all connections\n" "opened in current script.", NULL }, { "identity", (getter) _mssql_identity_get, (setter) NULL, "Returns identity value of last inserted row. If previous operation\n" "did not involve inserting a row into a table with identity column,\n" "None is returned. Example usage:\n\n" "conn.execute_non_query(\"INSERT INTO table (name) VALUES ('John')\")\n" "print 'Last inserted row has ID = ' + conn.identity", NULL }, { NULL, NULL, NULL, NULL, NULL } }; #define _MSSQL_OFF(m) offsetof(_mssql_connection, m) /* instance properties */ static PyMemberDef _mssql_connection_members[] = { { "connected", T_INT, _MSSQL_OFF(connected), READONLY, "True if the connection to a database is open." }, { "rows_affected", T_INT, _MSSQL_OFF(rows_affected), READONLY, "Number of rows affected by last query. For SELECT statements\n" "this value is only meaningful after reading all rows." }, { "charset", T_STRING, _MSSQL_OFF(charset), READONLY, "Character set name that was passed to _mssql.connect()." }, { "debug_queries", T_INT, _MSSQL_OFF(debug_queries), RESTRICTED, "If set to True, all queries are printed to stderr after\n" "formatting and quoting, just before execution." }, { NULL, 0, 0, 0, NULL }, }; static char _mssql_connection_type_doc[] = "This object represents an MS SQL database connection. You can\n\ make queries and obtain results through a database connection."; static PyTypeObject _mssql_connection_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "_mssql.MssqlConnection", /* tp_name */ sizeof(_mssql_connection), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)_mssql_connection_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)_mssql_connection_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ _mssql_connection_type_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)_mssql___iter__, /* tp_iter */ 0, /* tp_iternext */ _mssql_connection_methods, /* tp_methods */ _mssql_connection_members, /* tp_members */ _mssql_connection_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free Low-level free-memory routine */ 0, /* tp_bases */ 0, /* tp_mro method resolution order */ 0, /* tp_defined */ }; /* module methods */ static PyMethodDef _mssql_methods[] = { { "connect", (PyCFunction) _mssql_connect, METH_KEYWORDS, _mssql_connect_doc }, { "_quote_data", (PyCFunction) _mssql_quote_data, METH_O, _mssql_quote_data_doc }, { "_format_sql_command", (PyCFunction) _mssql_format_sql_command, METH_VARARGS, _mssql_format_sql_command_doc }, { NULL, NULL } }; static char _mssql_MssqlDatabaseException_doc[] = "Exception raised when a database (query or server) error occurs.\n\n\ You can use it as in the following example:\n\n\ try:\n\ conn = _mssql.connect('server','user','password')\n\ conn.execute_non_query('CREATE TABLE t1(id INT, name VARCHAR(50))')\n\ except _mssql.MssqlDatabaseException,e:\n\ if e.number == 2714 and e.severity == 16:\n\ # table already existed, so mute the error\n\ else:\n\ raise # re-raise real error\n\ finally:\n\ conn.close()\n\n\ Message numbers can be obtained by executing the query without\n\ the 'try' block, or from SQL Server Books Online.\n\n\ --------------------------------------------------------"; PyMODINIT_FUNC init_mssql(void) { #ifdef MS_WINDOWS LPCSTR rtc; #else RETCODE rtc; #endif PyObject *dict; /* if we initialize this at declar ation, MSVC 7 issues the following warn: warning C4232: nonstandard extension used : 'tp_getattro': address of dllimport 'PyObject_GenericGetAttr' is not static, identity not guaranteed */ _mssql_connection_type.tp_getattro = PyObject_GenericGetAttr; _mssql_row_iterator_type.tp_getattro = PyObject_GenericGetAttr; PyDateTime_IMPORT; // import datetime _decimal_module = PyImport_ImportModule("decimal"); if (_decimal_module == NULL) return; _decimal_class = PyObject_GetAttrString(_decimal_module, "Decimal"); _decimal_context = PyObject_CallMethod(_decimal_module, "getcontext", NULL); if (PyType_Ready(&_mssql_connection_type) == -1) return; if (PyType_Ready(&_mssql_row_iterator_type) == -1) return; _mssql_module = Py_InitModule3("_mssql", _mssql_methods, "Low level Python module for communicating with MS SQL servers."); if (_mssql_module == NULL) return; // add the connection object to module dictionary Py_INCREF(&_mssql_connection_type); if (PyModule_AddObject(_mssql_module, "MssqlConnection", (PyObject *)&_mssql_connection_type) == -1) return; // MssqlException dict = PyDict_New(); if (dict == NULL) return; if (PyDict_SetItemString(dict, "__doc__", PyString_FromString("Base class for all _mssql related exceptions.")) == -1) return; _mssql_MssqlException = PyErr_NewException("_mssql.MssqlException", NULL, dict); if (_mssql_MssqlException == NULL) return; if (PyModule_AddObject(_mssql_module, "MssqlException", _mssql_MssqlException) == -1) return; // MssqlDatabaseException dict = PyDict_New(); if (dict == NULL) return; if (PyDict_SetItemString(dict, "__doc__", PyString_FromString(_mssql_MssqlDatabaseException_doc)) == -1) return; if (PyDict_SetItemString(dict, "number", PyInt_FromLong(0)) == -1) return; if (PyDict_SetItemString(dict, "severity", PyInt_FromLong(0)) == -1) return; if (PyDict_SetItemString(dict, "state", PyInt_FromLong(0)) == -1) return; // explicit definition of message will allow us to use it // without DeprecationWarning in Python 2.6 Py_INCREF(Py_None); if (PyDict_SetItemString(dict, "message", Py_None) == -1) return; _mssql_MssqlDatabaseException = PyErr_NewException("_mssql.MssqlDatabaseException", _mssql_MssqlException, dict); if (_mssql_MssqlDatabaseException == NULL) return; if (PyModule_AddObject(_mssql_module, "MssqlDatabaseException", _mssql_MssqlDatabaseException) == -1) return; // MssqlDriverException dict = PyDict_New(); if (dict == NULL) return; if (PyDict_SetItemString(dict, "__doc__", PyString_FromString("Exception raised when an _mssql module error occurs.")) == -1) return; _mssql_MssqlDriverException = PyErr_NewException("_mssql.MssqlDriverException", _mssql_MssqlException, dict); if (_mssql_MssqlDriverException == NULL) return; if (PyModule_AddObject(_mssql_module, "MssqlDriverException", _mssql_MssqlDriverException) == -1) return; if (PyModule_AddIntConstant(_mssql_module, "STRING", TYPE_STRING) == -1) return; if (PyModule_AddIntConstant(_mssql_module, "BINARY", TYPE_BINARY) == -1) return; if (PyModule_AddIntConstant(_mssql_module, "NUMBER", TYPE_NUMBER) == -1) return; if (PyModule_AddIntConstant(_mssql_module, "DATETIME", TYPE_DATETIME) == -1) return; if (PyModule_AddIntConstant(_mssql_module, "DECIMAL", TYPE_DECIMAL) == -1) return; // don't set it too high - for example query timeouts has severity = 6, // for now the best seems 6, because 5 generates too many db-lib warnings if (PyModule_AddObject(_mssql_module, "min_error_severity", PyInt_FromLong((long) 6)) == -1) return; if (PyModule_AddObject(_mssql_module, "login_timeout", PyInt_FromLong((long) 60)) == -1) return; rtc = dbinit(); #ifdef MS_WINDOWS if (rtc == (char *)NULL) { #else if (rtc == FAIL) { #endif PyErr_SetString(_mssql_MssqlDriverException, "Could not initialize communication layer"); return; } // these don't need to release GIL #ifdef MS_WINDOWS dberrhandle((DBERRHANDLE_PROC)err_handler); dbmsghandle((DBMSGHANDLE_PROC)msg_handler); #else dberrhandle(err_handler); dbmsghandle(msg_handler); #endif } /* _mssql.MssqlRowIterator class methods *************************************/ static PyObject *_mssql_row_iterator_repr(_mssql_row_iterator *self) { return PyString_FromFormat("<_mssql.MssqlRowIterator at %p>", self); } static PyObject *_mssql_row_iterator__iter__(PyObject *self) { Py_INCREF(self); return self; } static PyObject *_mssql_row_iterator_iternext(_mssql_row_iterator *self) { if (PyErr_Occurred()) return NULL; assert_connected(self->conn); clr_err(self->conn); return fetch_next_row_dict(self->conn, /* raise StopIteration= */ 1); } void _mssql_row_iterator_dealloc(_mssql_row_iterator *self) { Py_CLEAR(self->conn); PyObject_Del(self); } /* _mssql.MssqlRowIterator class definition **********************************/ static char _mssql_row_iterator_doc[] = "This object represents an iterator that iterates over rows\n\ from a result set."; static PyTypeObject _mssql_row_iterator_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "_mssql.MssqlRowIterator", /* tp_name */ sizeof(_mssql_row_iterator), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)_mssql_row_iterator_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)_mssql_row_iterator_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ _mssql_row_iterator_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ _mssql_row_iterator__iter__, /* tp_iter */ (iternextfunc)_mssql_row_iterator_iternext, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free Low-level free-memory routine */ 0, /* tp_bases */ 0, /* tp_mro method resolution order */ 0, /* tp_defined */ }; /* internal functions ********************************************************/ /* rmv_lcl() -- strip off all locale formatting buf is supplied to make this solution thread-safe; conversion will succeed when buf is the same size as s (or larger); s is the string rep of the number to strip; scientific formats are not supported; buf can be == s (it can fix numbers in-place.) return codes: 0 - conversion failed (buf too small or buf or s is null) <>0 - conversion succeeded, new len is returned Idea by Mark Pettit. */ int rmv_lcl(char *s, char *buf, size_t buflen) { char c, *lastsep = NULL, *p = s, *b = buf; size_t l; if (b == (char *)NULL) return 0; if (s == (char *)NULL) { *b = 0; return 0; } /* find last separator and length of s */ while ((c = *p)) { if ((c == '.') || (c == ',')) lastsep = p; ++p; } l = p - s; // strlen(s) if (buflen < l) return 0; /* copy the number skipping all but last separator and all other chars */ p = s; while ((c = *p)) { if (((c >= '0') && (c <= '9')) || (c == '-') || (c == '+')) *b++ = c; else if (p == lastsep) *b++ = '.'; ++p; } *b = 0; // cast to int to make x64 happy, can do it because numbers are not so very long return (int)(b - buf); // return new len } /* This is a helper function, which does most work needed by any execute_*() function. It returns NULL on error, non-NULL on success. Actually it returns Py_None, but there's no need to DECREF it. Also, we can't use int as retcode here because assert_connected and other macros return NULL on failure. */ PyObject *format_and_run_query(_mssql_connection *self, PyObject *args) { RETCODE rtc; char *query; PyObject *format = NULL, *params = NULL, *formatted = NULL, *tuple; if (PyErr_Occurred()) return NULL; assert_connected(self); clr_err(self); if (!PyArg_ParseTuple(args, "O|O", &format, ¶ms)) return NULL; // format the string then convert it to char * // Warning: don't put params != Py_None here as None is a valid value! if (params != NULL) { tuple = PyTuple_New(2); if (tuple == NULL) return NULL; Py_INCREF(format); Py_INCREF(params); PyTuple_SET_ITEM(tuple, 0, format); PyTuple_SET_ITEM(tuple, 1, params); formatted = _mssql_format_sql_command(NULL, tuple); Py_DECREF(tuple); if (formatted == NULL) return NULL; query = PyString_AsString(formatted); // remember to DECREF later } else { query = PyString_AsString(format); } if (query == NULL) return NULL; db_cancel(self); // cancel any pending results if (self->debug_queries) { fprintf(stderr, "#%s#\n", query); fflush(stderr); } // execute the query Py_BEGIN_ALLOW_THREADS dbcmd(self->dbproc, query); rtc = dbsqlexec(self->dbproc); Py_END_ALLOW_THREADS // remember to DECREF this as soon as 'query' is no longer needed // and before any possible return statement Py_XDECREF(formatted); check_cancel_and_raise(rtc, self); return Py_None; // borrowed, but it doesn't matter, it's just a flag } /* This function advances to next result, and fetch column names and types, setting column_names and column_types properties. If the result appears to be already read, function does nothing. It returns NULL on error and non-NULL on success. Actually it returns Py_None, but there's no need to DECREF it. This function skips over all result sets that have no columns (for example the query: "DECLARE @a INT; SELECT 1" returns two result sets: one with zero columns, followed by one with one column. */ PyObject *get_result(_mssql_connection *conn) { int col; if (conn->last_dbresults) // already read, return success return Py_None; // borrowed, but it doesn't matter, it's just a flag clr_metadata(conn); // find a result set that has at least one column conn->last_dbresults = SUCCEED; while (conn->last_dbresults == SUCCEED && (conn->num_columns = dbnumcols(conn->dbproc)) <= 0) { Py_BEGIN_ALLOW_THREADS conn->last_dbresults = dbresults(conn->dbproc); Py_END_ALLOW_THREADS } check_cancel_and_raise(conn->last_dbresults, conn); if (conn->last_dbresults == NO_MORE_RESULTS) // no result, but no exception, so return success return Py_None; // borrowed, but it doesn't matter, it's just a flag // we need the row affected value, but at this point it is probably 0, // unless user issued a statement that doesn't return rows conn->rows_affected = dbcount(conn->dbproc); conn->num_columns = dbnumcols(conn->dbproc); conn->column_names = PyTuple_New(conn->num_columns); if (!conn->column_names) return NULL; conn->column_types = PyTuple_New(conn->num_columns); if (!conn->column_types) return NULL; for (col = 1; col <= conn->num_columns; col++) { int coltype, apicoltype; char *colname = (char *) dbcolname(conn->dbproc, col); coltype = dbcoltype(conn->dbproc, col); switch (coltype) { case SQLBIT: case SQLINT1: case SQLINT2: case SQLINT4: case SQLINT8: case SQLINTN: case SQLFLT4: case SQLFLT8: case SQLFLTN: apicoltype = TYPE_NUMBER; break; case SQLMONEY: case SQLMONEY4: case SQLMONEYN: case SQLNUMERIC: case SQLDECIMAL: apicoltype = TYPE_DECIMAL; break; case SQLDATETIME: case SQLDATETIM4: case SQLDATETIMN: apicoltype = TYPE_DATETIME; break; case SQLVARCHAR: case SQLCHAR: case SQLTEXT: apicoltype = TYPE_STRING; break; //case SQLVARBINARY: case SQLBINARY: case SQLIMAGE: default: apicoltype = TYPE_BINARY; } #ifdef PYMSSQLDEBUG // DB-Library for C is truncating column names to 30 characters... // thanks stefan fprintf(stderr, "Got column name '%s', name length = %d, coltype=%d, apicoltype=%d\n", colname, strlen(colname), coltype, apicoltype); #endif if (PyTuple_SetItem(conn->column_names, col-1, PyString_FromString(colname)) != 0) return NULL; if (PyTuple_SetItem(conn->column_types, col-1, PyInt_FromLong(apicoltype)) != 0) return NULL; } return Py_None; // borrowed, but it doesn't matter, it's just a flag } #define GET_DATA(dbproc, rowinfo, x) \ ((rowinfo == REG_ROW)?(BYTE*)dbdata(dbproc, x):(BYTE*)dbadata(dbproc, rowinfo, x)) #define GET_TYPE(dbproc, rowinfo, x) \ ((rowinfo == REG_ROW)?dbcoltype(dbproc, x):dbalttype(dbproc, rowinfo, x)) #define GET_LEN(dbproc, rowinfo, x) \ ((rowinfo == REG_ROW)?dbdatlen(dbproc, x):dbadlen(dbproc, rowinfo, x)) #define NUMERIC_BUF_SZ 45 /* This function gets data from CURRENT row. Remember to call get_result() somewhere before calling this function. The result is a tuple with Pythonic representation of row data. I use it just because in general it's more convenient than C arrays especially WRT memory management. In most cases after calling this function the next thing is to create row dictionary. */ PyObject *get_row(_mssql_connection *conn, int rowinfo) { DBPROCESS *dbproc = conn->dbproc; int col, coltype, len; LPBYTE data; // column data pointer long intdata; PY_LONG_LONG longdata; double ddata; PyObject *record; DBDATEREC di; DBDATETIME dt; PyObject *o; // temporary object char buf[NUMERIC_BUF_SZ]; // buffer in which we store text rep of big nums DBCOL dbcol; BYTE prec; long prevPrec; #ifdef PYMSSQLDEBUG static int DEBUGRowNumber=0; #endif record = PyTuple_New(conn->num_columns); if (!record) raise_MssqlDriverException("Could not create record tuple"); #ifdef PYMSSQLDEBUG DEBUGRowNumber++; #endif for (col = 1; col <= conn->num_columns; col++) { // do for all columns //#ifdef PYMSSQLDEBUG // fprintf(stderr, "Processing row %d, column %d\n", DEBUGRowNumber, col); //#endif Py_BEGIN_ALLOW_THREADS // get pointer to column's data // rowinfo == FAIL and rowinfo == NO_MORE_ROWS are already handled in caller data = GET_DATA(dbproc, rowinfo, col); // get pointer to column's data coltype = GET_TYPE(dbproc, rowinfo, col); Py_END_ALLOW_THREADS if (data == NULL) { // if NULL, use None Py_INCREF(Py_None); PyTuple_SET_ITEM(record, col-1, Py_None); continue; } #ifdef PYMSSQLDEBUG fprintf(stderr, "Processing row %d, column %d. Got data=%x, coltype=%d, len=%d\n", DEBUGRowNumber, col, data, coltype, GET_LEN(dbproc, rowinfo, col)); #endif switch (coltype) { // else we have data case SQLBIT: intdata = (int) *(DBBIT *) data; PyTuple_SET_ITEM(record, col-1, PyBool_FromLong((long) intdata)); break; case SQLINT1: intdata = (int) *(DBTINYINT *) data; PyTuple_SET_ITEM(record, col-1, PyInt_FromLong((long) intdata)); break; case SQLINT2: intdata = (int) *(DBSMALLINT *) data; PyTuple_SET_ITEM(record, col-1, PyInt_FromLong((long) intdata)); break; case SQLINT4: intdata = (int) *(DBINT *) data; PyTuple_SET_ITEM(record, col-1, PyInt_FromLong((long) intdata)); break; case SQLINT8: longdata = *(PY_LONG_LONG *) data; PyTuple_SET_ITEM(record, col-1, PyLong_FromLongLong((PY_LONG_LONG) longdata)); break; case SQLFLT4: ddata = *(DBFLT4 *)data; PyTuple_SET_ITEM(record, col-1, PyFloat_FromDouble(ddata)); break; case SQLFLT8: ddata = *(DBFLT8 *)data; PyTuple_SET_ITEM(record, col-1, PyFloat_FromDouble(ddata)); break; case SQLMONEY: case SQLMONEY4: case SQLNUMERIC: case SQLDECIMAL: dbcol.SizeOfStruct = sizeof(dbcol); if (dbcolinfo(dbproc, (rowinfo == REG_ROW) ? CI_REGULAR : CI_ALTERNATE, col, (rowinfo == REG_ROW) ? 0 : rowinfo, &dbcol) == FAIL) raise_MssqlDriverException("Could not obtain column info"); if (coltype == SQLMONEY || coltype == SQLMONEY4) prec = 4; else prec = dbcol.Scale; o = PyObject_GetAttrString(_decimal_context, "prec"); if (o == NULL) return NULL; prevPrec = PyInt_AsLong(o); Py_DECREF(o); o = PyInt_FromLong((long) prec); if (PyObject_SetAttrString(_decimal_context, "prec", o) == -1) raise_MssqlDriverException("Could not set decimal precision"); Py_DECREF(o); len = dbconvert(dbproc, coltype, data, -1, SQLCHAR, (LPBYTE)buf, NUMERIC_BUF_SZ); buf[len] = 0; // null terminate the string len = rmv_lcl(buf, buf, NUMERIC_BUF_SZ); if (!len) raise_MssqlDriverException("Could not remove locale formatting"); o = PyObject_CallFunction(_decimal_class, "s", buf); // new ref if (o == NULL) return NULL; PyTuple_SET_ITEM(record, col-1, o); // steals ref from CallFunction() o = PyInt_FromLong((long) prevPrec); if (PyObject_SetAttrString(_decimal_context, "prec", o) == -1) raise_MssqlDriverException("Could not restore decimal precision"); Py_DECREF(o); break; case SQLDATETIM4: dbconvert(dbproc, coltype, data, -1, SQLDATETIME, (LPBYTE)&dt, -1); data = (LPBYTE) &dt; // smalldatetime converted to full datetime // fall through case SQLDATETIME: dbdatecrack(dbproc, &di, (DBDATETIME*)data); // see README.freetds for info about date problem with FreeTDS o = PyDateTime_FromDateAndTime( di.year, di.month, di.day, di.hour, di.minute, di.second, di.millisecond*1000); PyTuple_SET_ITEM(record, col-1, o); break; case SQLVARCHAR: case SQLCHAR: case SQLTEXT: // Convert the strings from the encoding provided by the user // If there is none, just return the strings as is. if (*conn->charset) PyTuple_SET_ITEM(record,col-1,PyUnicode_Decode((char *) data, GET_LEN(dbproc,rowinfo,col), conn->charset, NULL)); else // return as-is PyTuple_SET_ITEM(record, col-1, PyString_FromStringAndSize( (const char *)data, GET_LEN(dbproc, rowinfo, col))); break; //case SQLBINARY: case SQLVARBINARY: case SQLIMAGE: default: // return as is (binary string) PyTuple_SET_ITEM(record, col-1, PyString_FromStringAndSize( (const char *)data, GET_LEN(dbproc, rowinfo, col))); } // end switch } // end for return record; } /* This function fetches NEXT ROW of data and returns it as a dictionary. It is used by execute_row() and by row iterator iternext(). */ PyObject *fetch_next_row_dict(_mssql_connection *conn, int raise) { PyObject *row, *dict; RETCODE rtc; int col; if (get_result(conn) == NULL) return NULL; // ok, we did everything to set up results, if there aren't any, // just stop the iteration if (conn->last_dbresults == NO_MORE_RESULTS) { clr_metadata(conn); if (raise) { PyErr_SetNone(PyExc_StopIteration); return NULL; } Py_RETURN_NONE; } // iterate and build row dictionary Py_BEGIN_ALLOW_THREADS rtc = dbnextrow(conn->dbproc); Py_END_ALLOW_THREADS check_cancel_and_raise(rtc, conn); if (rtc == NO_MORE_ROWS) { clr_metadata(conn); // 'rows affected' is nonzero only after all records are read conn->rows_affected = dbcount(conn->dbproc); conn->last_dbresults = 0; // force next results, if any if (raise) { PyErr_SetNone(PyExc_StopIteration); return NULL; } Py_RETURN_NONE; } if ((dict = PyDict_New()) == NULL) return NULL; row = get_row(conn, rtc); // pass constant REG_ROW or compute id if (row == NULL) return NULL; for (col = 1; col <= conn->num_columns; col++) { PyObject *name, *val; name = PyTuple_GetItem(conn->column_names, col-1); if (name == NULL) return NULL; val = PyTuple_GetItem(row, col-1); if (val == NULL) return NULL; // add key by column name, do not add if name == '' if (strlen(PyString_AS_STRING(name)) != 0) if ((PyDict_SetItem(dict, name, val)) == -1) return NULL; // add key by column number if ((PyDict_SetItem(dict, PyInt_FromLong(col-1), val)) == -1) return NULL; } Py_DECREF(row); // row data no longer needed return dict; } /* clear error condition so we can start accumulating error messages again */ void clr_err(_mssql_connection *self) { *MSSQL_LASTMSGSTR(self) = '\0'; if (self != NULL) { self->last_msg_no = 0; self->last_msg_severity = 0; self->last_msg_state = 0; } else { _mssql_last_msg_no = 0; _mssql_last_msg_severity = 0; _mssql_last_msg_state = 0; } } /* Check whether accumulated severity is equal to or higher than min_error_severity, and if so, set exception and return true; else return false (no need to raise exception) */ int maybe_raise_MssqlDatabaseException(_mssql_connection *self) { PyObject *o; long lo; char *errptr; o = PyObject_GetAttr(_mssql_module, PyString_FromString("min_error_severity")); lo = PyInt_AS_LONG(o); Py_DECREF(o); if (MSSQL_LASTMSGSEVERITY(self) < lo) return 0; // severe enough to raise error errptr = MSSQL_LASTMSGSTR(self); if (!errptr || !*errptr) errptr = "Unknown error"; PyObject_SetAttrString(_mssql_MssqlDatabaseException, "number", PyInt_FromLong(MSSQL_LASTMSGNO(self))); PyObject_SetAttrString(_mssql_MssqlDatabaseException, "severity", PyInt_FromLong(MSSQL_LASTMSGSEVERITY(self))); PyObject_SetAttrString(_mssql_MssqlDatabaseException, "state", PyInt_FromLong(MSSQL_LASTMSGSTATE(self))); PyObject_SetAttrString(_mssql_MssqlDatabaseException, "message", PyString_FromString(errptr)); PyErr_SetString(_mssql_MssqlDatabaseException, errptr); // cancel the (maybe partial, invalid) results db_cancel(self); return 1; } /* cancel pending results */ RETCODE db_cancel(_mssql_connection *conn) { RETCODE rtc; // db_cancel() may be called from maybe_raise_MssqlDatabaseException() // after failed call to dbopen(), so we must handle all cases if (conn == NULL) return SUCCEED; if (conn->dbproc == NULL) return SUCCEED; Py_BEGIN_ALLOW_THREADS rtc = dbcancel(conn->dbproc); Py_END_ALLOW_THREADS clr_metadata(conn); return rtc; } /* EOF */ pymssql-1.0.2/pymssql.egg-info/0000755000175000017500000000000011175604650014627 5ustar poxpoxpymssql-1.0.2/pymssql.egg-info/PKG-INFO0000644000175000017500000000151711175600061015720 0ustar poxpoxMetadata-Version: 1.0 Name: pymssql Version: 1.0.2 Summary: A simple database interface to MS-SQL for Python. Home-page: http://pymssql.sourceforge.net Author: Andrzej Kukula Author-email: akukula+pymssql@gmail.com License: LGPL Description: A simple database interface to MS-SQL for Python. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Programming Language :: Python Classifier: Programming Language :: C Classifier: Topic :: Database :: Front-Ends Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: Unix pymssql-1.0.2/pymssql.egg-info/dependency_links.txt0000644000175000017500000000000111142420134020660 0ustar poxpox pymssql-1.0.2/pymssql.egg-info/not-zip-safe0000644000175000017500000000000111140655534017054 0ustar poxpox pymssql-1.0.2/pymssql.egg-info/SOURCES.txt0000644000175000017500000000061111142421323016475 0ustar poxpox.cvsignore ChangeLog MANIFEST Makefile.debian PKG-INFO README TODO apitest_mssql.py dbapitest.py dist.bat dist.sh lgpl.txt mssqldbmodule.c ntwdblib.dll pymssql.py setup.cfg setup.py test.py nagios-plugin/check_mssql nagios-plugin/mssql.cfg pymssql.egg-info/PKG-INFO pymssql.egg-info/SOURCES.txt pymssql.egg-info/dependency_links.txt pymssql.egg-info/not-zip-safe pymssql.egg-info/top_level.txtpymssql-1.0.2/pymssql.egg-info/top_level.txt0000644000175000017500000000001711142420134017342 0ustar poxpoxpymssql _mssql pymssql-1.0.2/TODO0000644000175000017500000000067511175563100012117 0ustar poxpoxSerious/important: Moderate: - Sybase compatibility update (private mail from Kurt Sutter on 2009-04-24) didn't make it into 1.0.2 - improve inline documentation strings - that's job for English native speakers - fix apitest_mssql.py - test on big-endian platforms Low priority: - try to call dbexit() on module dealloc / python unload (how?) Other notes: - when and how to use unicode(row[index], 'fancy-encoding', 'ignore') pymssql-1.0.2/MANIFEST0000644000175000017500000000047711142421407012555 0ustar poxpoxChangeLog MANIFEST PKG-INFO README TODO lgpl.txt mssqldbmodule.c ntwdblib.dll pymssql.py setup.py nagios-plugin/check_mssql nagios-plugin/mssql.cfg pymssql.egg-info/PKG-INFO pymssql.egg-info/SOURCES.txt pymssql.egg-info/dependency_links.txt pymssql.egg-info/not-zip-safe pymssql.egg-info/top_level.txt pymssql-1.0.2/pymssql.py0000644000175000017500000004457111174147041013515 0ustar poxpox"""DB-SIG compliant module for communicating with MS SQL servers""" #*************************************************************************** # pymssql.py - description # # begin : 2003-03-03 # copyright : (C) 2003-03-03 by Joon-cheol Park # email : jooncheol@gmail.com # current developer : Andrzej Kukula # homepage : http://pymssql.sourceforge.net # #*************************************************************************** # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA #*************************************************************************** __author__ = "Joon-cheol Park , Andrzej Kukula " __version__ = '1.0.2' import _mssql, types, string, time, datetime, warnings ### module constants # compliant with DB SIG 2.0 apilevel = '2.0' # module may be shared, but not connections threadsafety = 1 # this module use extended python format codes paramstyle = 'pyformat' #export column type names from _mssql class DBAPITypeObject: def __init__(self,*values): self.values = values def __cmp__(self,other): if other in self.values: return 0 if other < self.values: return 1 else: return -1 STRING = DBAPITypeObject(_mssql.STRING) BINARY = DBAPITypeObject(_mssql.BINARY) NUMBER = DBAPITypeObject(_mssql.NUMBER) DATETIME = DBAPITypeObject(_mssql.DATETIME) DECIMAL = DBAPITypeObject(_mssql.DECIMAL) ### exception hierarchy class Warning(StandardError): pass class Error(StandardError): pass class InterfaceError(Error): pass class DatabaseError(Error): pass class DataError(DatabaseError): pass class OperationalError(DatabaseError): pass class IntegrityError(DatabaseError): pass class InternalError(DatabaseError): pass class ProgrammingError(DatabaseError): pass class NotSupportedError(DatabaseError): pass ### cursor object class pymssqlCursor(object): """ This class represent a database cursor, which is used to issue queries and fetch results from a database connection. """ def __init__(self, src, as_dict): """ Initialize a Cursor object. 'src' is a pymssqlCnx object instance. """ self.__source = src self.description = None self._batchsize = 1 self._rownumber = 0 self.as_dict = as_dict @property def _source(self): """ INTERNAL PROPERTY. Returns the pymssqlCnx object, and raise exception if it's set to None. It's easier than adding necessary checks to every other method. """ if self.__source == None: raise InterfaceError, "Cursor is closed." return self.__source @property def rowcount(self): """ Returns number of rows affected by last operation. In case of SELECTs it returns meaningful information only after all rows has been fetched. """ return self._source.rows_affected @property def connection(self): """ Returns a reference to the connection object on which the cursor was created. This is the extension of the DB-API specification. """ #warnings.warn("DB-API extension cursor.connection used", SyntaxWarning, 2) return self._source @property def lastrowid(self): """ Returns identity value of last inserted row. If previous operation did not involve inserting a row into a table with identity column, None is returned. This is the extension of the DB-API specification. """ #warnings.warn("DB-API extension cursor.lastrowid used", SyntaxWarning, 2) return self._source.identity @property def rownumber(self): """ Returns current 0-based index of the cursor in the result set. This is the extension of the DB-API specification. """ #warnings.warn("DB-API extension cursor.rownumber used", SyntaxWarning, 2) return self._rownumber def close(self): """ Closes the cursor. The cursor is unusable from this point. """ self.__source = None self.description = None def execute(self, operation, *args): """ Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. Parameter style for pymssql is %-formatting, as in: cur.execute('select * from table where id=%d', id) cur.execute('select * from table where strname=%s', name) Please consult online documentation for more examples and guidelines. """ self.description = None self._rownumber = 0 # don't raise warning # for this method default value for params cannot be None, # because None is a valid value for format string. if (args != () and len(args) != 1): raise TypeError, "execute takes 1 or 2 arguments (%d given)" % (len(args) + 1,) try: if args == (): self._source.execute_query(operation) else: self._source.execute_query(operation, args[0]) self.description = self._source.get_header() except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def executemany(self, operation, param_seq): """ Execute a database operation repeatedly for each element in the parameter sequence. Example: cur.executemany("INSERT INTO table VALUES(%s)", [ 'aaa', 'bbb' ]) """ self.description = None for params in param_seq: self.execute(operation, params) def nextset(self): """ This method makes the cursor skip to the next available result set, discarding any remaining rows from the current set. Returns true value if next result is available, or None if not. """ try: if self._source.nextresult(): self._rownumber = 0 self.description = self._source.get_header() return 1 except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] return None def fetchone(self): """ Fetch the next row of a query result, returning a tuple, or None when no more data is available. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." try: if self.as_dict: row = iter(self._source).next() self._rownumber += 1 return row else: row = iter(self._source).next() self._rownumber += 1 return tuple([row[r] for r in sorted(row.keys()) if type(r) == int]) except StopIteration: return None except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def fetchmany(self, size=None): """ Fetch the next batch of rows of a query result, returning them as a list of tuples. An empty list is returned if no more rows are available. You can adjust the batch size using the 'size' parameter, which is preserved across many calls to this method. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." if size == None: size = self._batchsize self._batchsize = size try: list = [] for i in xrange(size): try: row = iter(self._source).next() if self.as_dict: t = row # pass through else: t = tuple([row[r] for r in sorted(row.keys()) if type(r) == int]) self._rownumber += 1 list.append(t) except StopIteration: break return list except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def fetchall(self): """ Fetch all remaining rows of a query result, returning them as a list of tuples. An empty list is returned if no more rows are available. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." try: if self.as_dict: list = [ row for row in self._source ] else: list = [tuple([row[r] for r in sorted(row.keys()) if type(r) == int]) for row in self._source] self._rownumber += len(list) return list except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def __iter__(self): """ Return self to make cursors compatible with Python iteration protocol. """ #warnings.warn("DB-API extension cursor.__iter__() used", SyntaxWarning, 2) return self def next(self): """ This method supports Python iterator protocol. It returns next row from the currently executing SQL statement. StopIteration exception is raised when the result set is exhausted. With this method you can iterate over cursors: cur.execute('SELECT * FROM persons') for row in cur: print row[0] """ #warnings.warn("DB-API extension cursor.next() used", SyntaxWarning, 2) try: row = iter(self._source).next() self._rownumber += 1 return tuple([row[r] for r in sorted(row.keys()) if type(r) == int]) except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def fetchone_asdict(self): """ Warning: this method is not part of Python DB-API. Fetch the next row of a query result, returning a dictionary, or None when no more data is available. Data can be accessed by 0-based numeric column index, or by column name. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." try: row = iter(self._source).next() self._rownumber += 1 return row except StopIteration: return None except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def fetchmany_asdict(self, size=None): """ Warning: this method is not part of Python DB-API. Fetch the next batch of rows of a query result, returning them as a list of dictionaries. An empty list is returned if no more rows are available. You can adjust the batch size using the 'size' parameter, which is preserved across many calls to this method. Data can be accessed by 0-based numeric column index, or by column name. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." if size == None: size = self._batchsize self._batchsize = size try: list = [] for i in xrange(size): try: row = iter(self._source).next() self._rownumber += 1 list.append(row) except StopIteration: break return list except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def fetchall_asdict(self): """ Warning: this method is not part of Python DB-API. Fetch all remaining rows of a query result, returning them as a list of dictionaries. An empty list is returned if no more rows are available. Data can be accessed by 0-based numeric column index, or by column name. Raises OperationalError if previous call to execute*() did not produce any result set or no call was issued yet. """ if self._source.get_header() == None: raise OperationalError, "No data available." try: return [ row for row in self._source ] except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] def setinputsizes(self, sizes=None): """ This method does nothing, as permitted by DB-API specification. """ pass def setoutputsize(self, size=None, column=0): """ This method does nothing, as permitted by DB-API specification. """ pass ### connection object class pymssqlCnx: """ This class represent an MS SQL database connection. """ def __init__(self, cnx, as_dict): self.__cnx = cnx self._autocommit = False self.as_dict = as_dict try: self._cnx.execute_non_query("BEGIN TRAN") except Exception, e: raise OperationalError, "cannot start transaction." + e[0] @property def _cnx(self): """ INTERNAL PROPERTY. Returns the _mssql.MssqlConnection object, and raise exception if it's set to None. It's easier than adding necessary checks to every other method. """ if self.__cnx == None: raise InterfaceError, "Connection is closed." return self.__cnx def __del__(self): if self.__cnx: self.__cnx.close() self.__cnx = None def close(self): """ Close connection to the database. """ if self.__cnx: self.__cnx.close() self.__cnx = None def commit(self): """ Commit transaction which is currently in progress. """ if self._autocommit == True: return try: self._cnx.execute_non_query("COMMIT TRAN") self._cnx.execute_non_query("BEGIN TRAN") except Exception, e: raise OperationalError, "cannot commit transaction." + e[0] def rollback(self): """ Roll back transaction which is currently in progress. """ if self._autocommit == True: return try: self._cnx.execute_non_query("ROLLBACK TRAN") self._cnx.execute_non_query("BEGIN TRAN") except Exception, e: raise OperationalError, "cannot roll back transaction: " + e[0] def cursor(self): """ Return cursor object that can be used to make queries and fetch results from the database. """ return pymssqlCursor(self._cnx, self.as_dict) def autocommit(self,status): """ Turn autocommit ON or OFF. """ if status: if self._autocommit == False: self._cnx.execute_non_query("ROLLBACK TRAN") self._autocommit = True else: if self._autocommit == True: self._cnx.execute_non_query("BEGIN TRAN") self._autocommit = False # connects to a database def connect(dsn = None, user = "sa", password = "", host = ".", database = "", timeout = 0, login_timeout = 60, trusted = False, charset = None, as_dict = False, max_conn = 25): """ Constructor for creating a connection to the database. Returns a connection object. Paremeters are as follows: dsn colon-delimited string in form host:dbase:user:pass:opt:tty primarily for compatibility with previous versions of pymssql. user database user to connect as password user's password trusted whether to use Windows Integrated Authentication to connect instead of SQL autentication with user and password [Win] host database host and instance, valid examples are: r'.\SQLEXPRESS' - named instance on local machine [Win] r'(local)\SQLEXPRESS' - same as above [Win] r'SQLHOST' - default instance and port [Win] r'SQLHOST' - instance set up in freetds.conf [Lnx] r'SQLHOST,1433' - specified TCP port at a host [ALL] r'SQLHOST:1433' - same as above [ALL] r'SQLHOST,5000' - if you have set up an instance to listen on port 5000 [ALL] r'SQLHOST:5000' - same as above [ALL] '.' is assumed is host is not provided database the database you want initially to connect to timeout query timeout in seconds, default 0 (no timeout) login_timeout timeout for connection and login in seconds, default 60 charset character set with which to connect to the database as_dict whether rows should be returned as dictionaries instead of tuples max_conn how many simultaneous connections to allow; default is 25 Examples: con = pymssql.connect(host=r'DBHOST,1433', user='username', password='P@ssw0rd', database='MYDB') con = pymssql.connect(host=r'DBHOST\INSTANCE',trusted=True) """ # first try to get params from DSN dbhost = "" dbbase = "" dbuser = "" dbpasswd = "" dbopt = "" dbtty = "" try: params = string.split(dsn, ":") dbhost = params[0] dbbase = params[1] dbuser = params[2] dbpasswd = params[3] dbopt = params[4] dbtty = params[5] except: pass # override if necessary if user != "": dbuser = user if password != "": dbpasswd = password if database != "": dbbase = database if host != "": dbhost = host # empty host is localhost if dbhost == "": dbhost = "." if dbuser == "": dbuser = "sa" # add default port # it will be deleted, it doesn't work well for different names # of local machine; it forces TCP/IP communication for them [on windows] # if ":" not in dbhost and "," not in dbhost and "\\" not in dbhost: # dbhost += ",1433" _mssql.login_timeout = login_timeout # open the connection try: if dbbase != "": con = _mssql.connect(dbhost, dbuser, dbpasswd, trusted, charset, database, max_conn=max_conn) else: con = _mssql.connect(dbhost, dbuser, dbpasswd, trusted, charset, max_conn=max_conn) except _mssql.MssqlDatabaseException, e: raise OperationalError, e[0] except _mssql.MssqlDriverException, e: raise InterfaceError, e[0] # default query timeout try: timeout = int(timeout) except ValueError, e: timeout = 0 if timeout != 0: con.query_timeout = timeout return pymssqlCnx(con, as_dict) pymssql-1.0.2/PKG-INFO0000644000175000017500000000151711175604650012526 0ustar poxpoxMetadata-Version: 1.0 Name: pymssql Version: 1.0.2 Summary: A simple database interface to MS-SQL for Python. Home-page: http://pymssql.sourceforge.net Author: Andrzej Kukula Author-email: akukula+pymssql@gmail.com License: LGPL Description: A simple database interface to MS-SQL for Python. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Programming Language :: Python Classifier: Programming Language :: C Classifier: Topic :: Database :: Front-Ends Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: Unix pymssql-1.0.2/lgpl.txt0000644000175000017500000006447410501623242013131 0ustar poxpox GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! pymssql-1.0.2/setup.py0000644000175000017500000001124711175600265013142 0ustar poxpox#!/usr/bin/env python import sys, os have_setuptools = 0 from distutils.core import setup, Extension #try: # from setuptools import setup, Extension # have_setuptools = 1 #except ImportError: # from distutils.core import setup, Extension # for setup.py register classifiers = """\ Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Programming Language :: Python Programming Language :: C Topic :: Database :: Front-Ends Topic :: Software Development :: Libraries :: Python Modules Operating System :: Microsoft :: Windows Operating System :: POSIX Operating System :: Unix """ minpyver = (2, 4) if sys.version_info < minpyver: print """ ERROR: You're using Python %d.%d, but pymssql requires at least Python %d.%d. Setup cannot continue. """ % ( sys.version_info[0], sys.version_info[1], minpyver[0], minpyver[1] ) sys.exit(1) if sys.platform == "win32": p = '' import os.path try: # those modules are available out-of-the box in ActivePython import win32api, win32con # try to determine include and lib path try: h = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\Microsoft SQL Server\80\Tools\ClientSetup') p = win32api.RegQueryValueEx(h,'SQLPath')[0] except: e = sys.exc_info() print """ Setup.py is unable to find path to SQL 2000 tools. Either it's not installed or you have insufficient permissions to read Windows registry. Please make sure you've got administrator rights. Setup.py will try some generic paths. """ if p: e = '' if (not os.path.exists(os.path.join(p, r'DevTools\include'))): e += "Setup.py is unable to find SQL 2000 developer tools include directory.\n" if (not os.path.exists(os.path.join(p, r'DevTools\lib'))): e += "Setup.py is unable to find SQL 2000 developer tools library directory.\n" if e: print e + """ Either the developer tools package is not installed or you have insufficient permissions to read the files. Please make sure you've got administrator rights. Setup.py will try some generic paths. """ except ImportError: pass # first some generic paths # XXX TODO remove developer paths include_dirs = [ r'c:\Program Files\Microsoft SQL Server\80\Tools\DevTools\Include', r'c:\mssql7\DevTools\Include', r'd:\DEVEL\pymssql-DEVTOOLS\INCLUDE' ] library_dirs = [ r'c:\Program Files\Microsoft SQL Server\80\Tools\DevTools\Lib', r'\mssql7\DevTools\Lib', r'd:\DEVEL\pymssql-DEVTOOLS\X86LIB' ] libraries = ["ntwdblib", "msvcrt", "kernel32", "user32", "gdi32", "winspool", "comdlg32", "advapi32", "shell32", "ole32", "oleaut32", "uuid", "odbc32", "odbccp32", ] data_files = [("LIB/site-packages",["ntwdblib.dll",]),] # prepend path from registry, if any if p: include_dirs.insert(0, os.path.join(p, r'DevTools\include')) library_dirs.insert(0, os.path.join(p, r'DevTools\lib')) else: # try some generic paths include_dirs = [ '/usr/local/include', '/usr/local/include/freetds', # first local install '/usr/include', '/usr/include/freetds', # some generic Linux paths '/usr/include/freetds_mssql', # some versions of Mandriva '/usr/local/freetds/include', # FreeBSD '/usr/pkg/freetds/include' # NetBSD ] library_dirs = [ '/usr/local/lib', '/usr/local/lib/freetds', '/usr/lib', '/usr/lib/freetds', '/usr/lib/freetds_mssql', '/usr/local/freetds/lib', '/usr/pkg/freetds/lib' ] libraries = [ "sybdb" ] # on Mandriva you may have to change it to sybdb_mssql data_files = [] if sys.platform == "cygwin": libraries.append("iconv") # when using Fink (http://Fink.SF.Net) under OS X, the following is needed: # (thanks Terrence Brannon ) if sys.platform == "darwin": fink = '/sw/' include_dirs.insert(0, fink + 'include') library_dirs.insert(0, fink + 'lib') setup(name = 'pymssql', version = '1.0.2', description = 'A simple database interface to MS-SQL for Python.', long_description = 'A simple database interface to MS-SQL for Python.', author = 'Joon-cheol Park', author_email = 'jooncheol@gmail.com', maintainer = 'Andrzej Kukula', maintainer_email = 'akukula+pymssql@gmail.com', license = 'LGPL', url = 'http://pymssql.sourceforge.net', py_modules = [ 'pymssql' ], ext_modules = [ Extension('_mssql', ['mssqldbmodule.c'], include_dirs = include_dirs, library_dirs = library_dirs, libraries = libraries) ], classifiers = filter(None, classifiers.split('\n')), data_files = data_files, #zip_safe = False # we accept the warning if there's no setuptools and in case of Python 2.4 and 2.5 ) pymssql-1.0.2/nagios-plugin/0000755000175000017500000000000011175604650014201 5ustar poxpoxpymssql-1.0.2/nagios-plugin/check_mssql0000644000175000017500000000506111174157726016430 0ustar poxpox#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright © 2008 Julien Blache # Copyright © 2008 Josselin Mouette # Licensed under the LGPL version 2.1 or later import sys import _mssql import pymssql import optparse from datetime import datetime class MyParser(optparse.OptionParser): def error(self,msg): sys.stdout.write("UNKNOWN: %s\n" %msg) sys.exit(3) parser=MyParser(description="Nagios check for MS SQL Server 2000 & up") parser.add_option ('-H', '--host', dest="hostname", help="Host to query (required)") parser.add_option ('-P', '--port', dest="port", type="int", help="Port to connect to", default=1433) parser.add_option ('-u', '--user', dest="user", help="Database user name (required)") parser.add_option ('-p', '--password', dest="password", help="Database password (required)") parser.add_option ('-d', '--database', dest="database", help="Database to check (optional)") (options, args) = parser.parse_args() if options.hostname == None or options.user == None or options.password == None: parser.error('missing a required argument') if options.database: if not options.database.replace('_','').isalnum(): parser.error('incorrect database name') def escape (string, *args): bs = '\\' qt = '\'' l = [] for arg in args: l.append (qt+str(arg).replace(bs, bs+bs).replace(qt, bs+qt)+qt) return string%tuple(l) try: stamp1 = datetime.utcnow() conn = pymssql.connect(host = "%s:%i"%(options.hostname,options.port), user = options.user, password = options.password, database="master") curs = conn.cursor() if options.database: curs.execute(escape('''SELECT COUNT(*) FROM sysprocesses p JOIN sysdatabases d ON d.dbid = p.dbid WHERE p.sid > 50 AND d.name = %s;''', options.database)) else: curs.execute("SELECT COUNT(*) FROM sysprocesses WHERE spid > 50;") nbusers = curs.fetchone()[0] if options.database: curs.execute("USE %s;"%options.database) stamp2 = datetime.utcnow() conn.close() except pymssql.Error, errstr: print "CRITICAL: %s" % str(errstr).splitlines()[-1] sys.exit(2) except _mssql.error, errstr: print "CRITICAL: %s" % str(errstr).splitlines()[-1] sys.exit(2) #except: # print "UNKNOWN: plugin error" # sys.exit(3) dstamp = stamp2 - stamp1 sec = dstamp.seconds + dstamp.microseconds / 1000000.0 print "OK: %d users, response time %.3f ms|users=%d time=%.6fs" % (nbusers, sec*1000., nbusers, sec) sys.exit(0) pymssql-1.0.2/nagios-plugin/mssql.cfg0000644000175000017500000000060511142420640016007 0ustar poxpox# 'check_mssql' command definition define command{ command_name check_mssql command_line /usr/lib/nagios/plugins/check_mssql -H $HOSTADDRESS$ -u $ARG1$ -p $ARG2$ } # 'check_mssql_db' command definition define command{ command_name check_mssql_db command_line /usr/lib/nagios/plugins/check_mssql -H $HOSTADDRESS$ -u $ARG1$ -p $ARG2$ -d $ARG3$ } pymssql-1.0.2/README0000644000175000017500000000157011140400154012271 0ustar poxpoxpymssql - simple MS SQL Python extension module by Park joon-cheol (Initial Version Developer) Andrzej Kukula (Active Developer) pymssql is the Python language extension module that provides access to Microsoft SQL Servers from Python scripts. It is compliant with Python DB-API 2.0 Specification. pymssql project is hosted on Sourceforge.net. ----------------------------------------------------------------------- This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. ----------------------------------------------------------------------- Detailed information on pymssql is available on the website: http://pymssql.sourceforge.net/ pymssql-1.0.2/ChangeLog0000644000175000017500000003136311175330336013202 0ustar poxpoxTue Apr 23 23:00:00 2009 Andrzej Kukula + bugfix: fixed rare quoting bug in select_db() + feature: added 'max_conn' parameter to pymssql.connect() and _mssql.connect() which defaults to 25, thanks Daniel Watrous * nagios-plugin update - thanks Josselin Mouette : + Include a -P port option, to avoid having to passing it with the host name + Fix the encoding of the comments; utf-8 is the declared encoding of the file and must be followed + Fix a typo in the SQL syntax + Connect explicitly to the "master" database (required since 1.0.0) + Improve perfdata output. * version 1.0.2 Tue Apr 21 22:56:00 2009 Andrzej Kukula * mssqldbmodule.c: + bugfix in format_and_run_query(): query strings were sometimes overwritten with garbage due to DECREF in wrong place; thanks Igor Nazarenko + bugfix in get_result(): if a query batch contained DECLARE or possibly other T-SQL statements, no results were returned thanks Kay Schluehr + bugfix in execute_scalar(): check if there are any columns in result + bugfix: check for FAIL after each dbnextrow() + feature: Add support for bigint - #2660972; thanks Alexandr Zamaraev * pymssql.c: + bugfix in execute(): if execute is called without second argument, don't treat '%' in query string as formatting character; restored compatibility with common sense and with pymssql < 1.0.0; thanks Corey Bertram , Wes McKinney + feature: it is possible to specify 'as_dict' to pymssql.connect and rows will be returned as dictionaries instead of tuples; thanks Daniel Watrous Thu Jan 30 18:36:00 2009 Andrzej Kukula * mssqldbmodule.c: + Pyssize_t error on x64 - thanks Josselin Mouette + critical charset updates, thanks Josselin Mouette + more Py_ssize_t updates, further code cleanups + fixed some compiler warnings * pymssql.py: + execute() failed, thanks Josselin Mouette + critical charset updates, thanks Josselin Mouette + removed warnings, users don't want them and they are not 'MUST' priority in DB-API spec * nagios-plugin: introducted Nagios plugin, thanks Julien Blache and Josselin Mouette * version 1.0.1 Thu Jan 29 19:23:00 2009 Andrzej Kukula * version 1.0.0 * so many changes I'll not put them here, I'll document changes from now on. Mon Sep 25 20:18:00 2006 Andrzej Kukula * setup.py: fix for Fink (http://Fink.SF.Net) under OS X (thanks Terrence Brannon ) Sun Sep 24 10:44:00 2006 Andrzej Kukula * setup.py: + it can now dynamically determine the path to SQL 2000 Developer Tools, if win32api and win32con modules are available + simple Python version check to prevent most frequently asked question + version 0.8.0 Wed Sep 13 01:20:00 2006 Andrzej Kukula * mssqldbmodule.c: + corrected misspellings in docstrings + fixed segfault on connection close with Python 2.5; thanks Justin Francis * pymssql.py: + fixed two minor DB-API incompatibilities (thanks Matthew Good ) + fixed datetime quoting (thanks Jan Finell ) * pymssql should be able to build on cygwin (thanks rob@robnet.com) * docstring fixes, webpage doc updates Tue May 15 03:18:00 2006 Jooncheol Park * setup.py, PKG-INFO, README: license change to LGPL Wed Mar 15 08:18:00 2006 Andrzej Kukula * pymssql.py: fixed datetime issue (thanks Jan Finell ) Fri Feb 24 16:11:00 2006 Andrzej Kukula * mssqldbmodule.c: fixed typos in docstrings (thanks Konstantin Veretennicov) Tue Dec 27 15:14:00 2005 Andrzej Kukula * mssqldbmodule.c: bug fixes, improvements and cleanups: + implemented set_login_timeout() and set_query_timeout() functions; + eliminated unnecessary ODBC code + cleaned up exception code and improved exception handling, SF bug #1335560 + web page now correctly mentions FreeTDS 0.63 as the minimal required version + stdmsg() method is now deprecated; all errors are concatenated in errmsg() + implemented min_error_severity: all errors at or above that level will raise the exception; if the severity is lower, they will just accumulate in errmsg() + added setting coltype to NUMBER for float types (found by Jakub Labath) * setup.py: + reincarnated ntwdblib.dll which turned out to be redistributable after all; pymssql includes the latest version that allows connecting to SQL 2005; eliminated some stupid notes from the web page and will ease set up process for users * apitest_mssql.py: new file + provided by Jakub Labath, this file performs some basic DB-API compliance tests; it immediately triggered the unicode bug * version 0.7.4 Sat Oct 22 19:41:00 2005 Andrzej Kukula * mssqldbmodule.c: multithreading improvements - from now on pymssql is thread-safe, it releases GIL in proper places; idea and initial patch by John-Peter Lee (thanks very much!) Mon Sep 5 23:29:00 2005 Andrzej Kukula * setup.py: fixed an installation issue regarding importing pymssql that imports _mssql which isn't installed, and blows up with AttributeError... (thanks Vsevolod Stakhov) * version 0.7.3 Mon Sep 5 00:32:00 2005 Andrzej Kukula * version 0.7.2 Sun Sep 4 23:12:00 2005 Andrzej Kukula * mssqldbmodule.c: improvements and cleanups: + improved error handling: if the db function fails, the exception is thrown automatically and immediately; no need to check return value of conn.query(), just catch _mssql.error + improved error handling: it is possible that MS SQL calls message handler twice; now _mssql catches and reports both of them at once + improved error handling: in some cases _mssql.query() returns success but the results are invalid; now it is handled properly (example "SELECT CAST(1234.5678 AS NUMERIC(4,2))") + added proper connection initialization: a number of SET statements are executed upon connection setup to set sensible SQL behaviour; see source for details; one needs to unset them if needed + implemented min_{message|error}_severity as it is in php_mssql to ignore unimportant errors; it's work in progress + new function rmv_lcl() initially by Mark Pettit, to strip locale crap from MONEY values converted to SQLCHAR while generating Decimal object + other small fixes, improvements and janitorial work Tue Aug 30 00:16:00 2005 Andrzej Kukula * mssqldbmodule.c: new features: + large numbers (DECIMAL, NUMERIC, MONEY, SMALLMONEY) are returned as Decimal object -- this helps maintain accuracy; thanks to Mark Pettit for help + COMPUTE clauses are supported (it wouldn't fetch data for those columns before) + ROWID type has been removed from _mssql module + new type DECIMAL to denote Decimal objects in result set Mon Aug 29 21:59:00 2005 Andrzej Kukula * mssqldbmodule.c: some improvements: + BIT values are returned as Python bool objects, suggested by Mark Pettit + close() method returns None on success (not to be used at all) and throws exception on error + fixed use of uninitialized value when parsing SMALLDATETIME + another round of performance improvements in GetRow() - eliminated unnecessary data conversions and unneeded DB-Lib calls + janitorial fixes Mon Aug 22 04:35:00 2005 Andrzej Kukula * mssqldbmodule.c: massive diff: + fixed bug with fetching query results of some data types; found by Mark Pettit + fixed IndexError when query returns no rows; patch by Jakub Labath + rewritten function GetRow() that fetches query results: performance improvements, better handling of result data types; datetime is returned as datetime object instead of string (it's more consistent with other values -- and more pythonic :) + eliminated DetermineRowSize() + cleanups: _mssql_init() further improvements w.r.t. Python API + janitorial fixes + added licensing information * pymssql.py: docstring changed to look nicer with help() * version 0.7.2 Thu Aug 11 02:12:00 2005 Andrzej Kukula * mssqldbmodule.c: improved module init function: added doc string, made compliant with Python 2.0+ module interface (there are no more coredumps on help()) * mssqldbmodule.c: documented that _mssql.connect() is not portable between FreeTDS-dependent platforms and Windows platforms; documented host:port usage Sat Jul 23 14:20:00 2005 Andrzej Kukula * mssqldbmodule.c: eliminated problems with Python exiting upon invalid login credentials with FreeTDS - the culprit was INT_EXIT and FreeTDS setting DBDEAD * mssqldbmodule.c: added better error messages (esp. on Windows) * mssqldbmodule.c: added msg_handler and err_handler debugging * 0.7.1 packages re-released Fri Jul 22 03:19:00 2005 Andrzej Kukula * mssqldbmodule.c: major change; module revamped to support some more builtin Python features; some redundant code removed; memset() removed as there were no benefits but performance decrease * mssqldbmodule.c: help(_mssql) works; help for conn object works too * pymssql.py: _quote: removed escaping backslash -- with MSSQL it is only needed to escape single quotes by duplicating them * pymssql.py: pymssqlCnx class: added a few checks to properly support DB-API 2.0 (see .close() in PEP 249) * version 0.7.1 Wed Jul 20 22:12:00 2005 Andrzej Kukula * mssqldbmodule.c: removed the workaround for date issue; there were more problems than benefits * mssqldbmodule_tds.c: removed * some more cleanups and corrections Tue Jul 19 14:23:00 2005 Andrzej Kukula * mssqldbmodule.c: major change; many portability problems fixed * mssqldbmodule.c: eliminated port setting; this is job for freetds.conf * mssqldbmodule_tds.c: module to get FreeTDS compile-time settings * build fixes; now it builds cleanly on FreeBSD, Linux and Windows * version 0.7.0 Mon Jul 18 15:21:00 2005 Andrzej Kukula * mssqldbmodule.c: fix build on Windows: changed MS_WIN32 to MS_WINDOWS reported by Mirek Rusin * mssqldbmodule.c: many small fixes and cleanups; janitorial fixes; indentation using indent(1L) * ChangeLog fix! 'mysql' was mentioned instead of 'mssql'... Fri Feb 25 02:15:01 2005 Andrzej Kukula * Fix build on Windows with Visual Studio .NET 2003 and MS SQL Server 2000 SP3a * mssqldbmodule.c: Fix compile error with Visual Studio .NET 2003 * mssqldbmodule.c: Add detection/workaround for date issue caused by different dbdatecrack() prototypes * README.freetds: describe dbdatecrack()-related issue Thu Feb 24 02:03:14 2005 Alejandro Dubrovsky * Export column type names * mssqldbmodule.c: Return column type information for headers * Use type information to make cursor.description conform to API 2 2005-02-17 Alejandro Dubrovsky * Apply patch by Rob Nichols to get cursor.description closer to API 2 compliance 2005-02-08 Alejandro Dubrovsky * Message changes in mssqldbmodule.c (typos, grammar, etc) 2005-02-07 Alejandro Dubrovsky * Added ChangeLog * API Change: add 6th parameter 'port' to connect * Don't close connection on cursor close (noted by Alberto Pastore on the sourceforge project page) * Make cursor.fetchone comply with DB-SIG return a tuple, not a list of tuples (report and patch by Chris Curvey)