diff --git a/MANIFEST.in b/MANIFEST.in index 5f52b189..16d7b03d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,8 @@ -include src/*.h -include src/*.cpp -include tests/* -include README.rst - -# Include this file, needed for bdist_rpm -include MANIFEST.in +include src/*.h +include src/*.cpp +include tests/* +include README.rst +recursive-include utils *.cpp + +# Include this file, needed for bdist_rpm +include MANIFEST.in diff --git a/setup.py b/setup.py index df022825..0844d77a 100755 --- a/setup.py +++ b/setup.py @@ -121,6 +121,18 @@ def get_compiler_settings(version_str): # OS/X now ships with iODBC. settings['libraries'].append('iodbc') + elif sys.platform.startswith('freebsd'): + try: + include = '-I'+os.environ['PREFIX']+'/include' + lib = '-L'+os.environ['PREFIX']+'/lib' + except: + include = '-I/usr/local/include' + lib = '-L/usr/local/lib' + + settings['extra_compile_args'] = ['-Wno-write-strings', include, lib] + settings['extra_link_args'] = [ lib ] + settings['libraries'].append('odbc') + else: # Other posix-like: Linux, Solaris, etc. @@ -263,13 +275,13 @@ def get_version(): def _get_version_pkginfo(): filename = join(dirname(abspath(__file__)), 'PKG-INFO') if exists(filename): - re_ver = re.compile(r'^Version: \s+ (\d+)\.(\d+)\.(\d+) (?: -beta(\d+))?', re.VERBOSE) + re_ver = re.compile(r'^Version: \s+ (?: (.+)-) (\d+)\.(\d+)\.(\d+) (?: -beta(\d+))?', re.VERBOSE) for line in open(filename): match = re_ver.search(line) if match: name = line.split(':', 1)[1].strip() - numbers = [int(n or 0) for n in match.groups()[:3]] - numbers.append(int(match.group(4) or OFFICIAL_BUILD)) # don't use 0 as a default for build + numbers = [int(n or 0) for n in match.groups()[1:4]] + numbers.append(int(match.group(5) or OFFICIAL_BUILD)) # don't use 0 as a default for build return name, numbers return None, None diff --git a/src/connection.cpp b/src/connection.cpp index c9025406..963338d0 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -17,6 +17,7 @@ #include "wrapper.h" #include "cnxninfo.h" #include "sqlwchar.h" +#include "virtuoso.h" static char connection_doc[] = "Connection objects manage connections to the database.\n" @@ -190,6 +191,8 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, bool fAnsi, cnxn->conv_types = 0; cnxn->conv_funcs = 0; + cnxn->virtuoso = isVirtuoso(hdbc); + // // Initialize autocommit mode. // diff --git a/src/connection.h b/src/connection.h index 2ce539c6..f723453e 100644 --- a/src/connection.h +++ b/src/connection.h @@ -52,6 +52,8 @@ struct Connection int wvarchar_maxlength; int binary_maxlength; + bool virtuoso; + // Output conversions. Maps from SQL type in conv_types to the converter function in conv_funcs. // // If conv_count is zero, conv_types and conv_funcs will also be zero. diff --git a/src/cursor.cpp b/src/cursor.cpp index 498f7339..7ec4e80a 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -24,6 +24,7 @@ #include "getdata.h" #include "dbspecific.h" #include "sqlwchar.h" +#include "virtuoso.h" enum { @@ -778,6 +779,8 @@ execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first) FreeParameterData(cur); + cur->spasql = (cur->cnxn->virtuoso && isSPASQL(pSql)); + if (ret == SQL_NO_DATA) { // Example: A delete statement that did not delete anything. @@ -1002,7 +1005,7 @@ Cursor_fetch(Cursor* cur) return 0; } - apValues[i] = value; + apValues[i] = value; } return (PyObject*)Row_New(cur->description, cur->map_name_to_index, field_count, apValues); diff --git a/src/cursor.h b/src/cursor.h index e7be1c92..6e105344 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -115,6 +115,9 @@ struct Cursor // The Cursor.rowcount attribute from the DB API specification. int rowcount; + // is a SPASQL query on a virtuoso server, requires special datatype handling + bool spasql; + // A dictionary that maps from column name (PyString) to index into the result columns (PyInteger). This is // constructued during an execute and shared with each row (reference counted) to implement accessing results by // column name. diff --git a/src/getdata.cpp b/src/getdata.cpp index b526e7c6..557bb5cc 100644 --- a/src/getdata.cpp +++ b/src/getdata.cpp @@ -9,6 +9,7 @@ #include "errors.h" #include "dbspecific.h" #include "sqlwchar.h" +#include "virtuoso.h" void GetData_init() { @@ -594,6 +595,87 @@ int GetUserConvIndex(Cursor* cur, SQLSMALLINT sql_type) return -1; } +static +PyObject *GetDataSPASQL(Cursor *cur, Py_ssize_t column) +{ + // Return a tuple of information sufficient to glean the + // real underlying type in case of a Virtuoso SPASQL query + int dvtype, flag; + SQLHANDLE hdesc = SQL_NULL_HANDLE; + SQLRETURN ret; + SQLCHAR lang[0x100], dtype[0x100]; + SQLINTEGER len, dv_dt_type; + PyObject *value, *colinfo; + + memset(lang, 0, sizeof(lang)); + memset(dtype, 0, sizeof(dtype)); + + value = GetDataString(cur, column); + if (!value) + return Py_None; + + // why do the virtuoso extensions number the columns from 1??? + column += 1; + + Py_BEGIN_ALLOW_THREADS + ret = SQLGetStmtAttr(cur->hstmt, SQL_ATTR_IMP_ROW_DESC, &hdesc, SQL_IS_POINTER, NULL); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) { + return Py_None; + } + Py_BEGIN_ALLOW_THREADS + ret = SQLGetDescField(hdesc, column, SQL_DESC_COL_DV_TYPE, &dvtype, SQL_IS_INTEGER, NULL); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) { + return Py_None; + } + Py_BEGIN_ALLOW_THREADS + ret = SQLGetDescField(hdesc, column, SQL_DESC_COL_BOX_FLAGS, &flag, SQL_IS_INTEGER, NULL); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) { + return Py_None; + } + + switch (dvtype) { + case VIRTUOSO_DV_RDF: + Py_BEGIN_ALLOW_THREADS + ret = SQLGetDescField(hdesc, column, SQL_DESC_COL_LITERAL_LANG, lang, sizeof(lang), &len); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) + return Py_None; + Py_BEGIN_ALLOW_THREADS + ret = SQLGetDescField(hdesc, column, SQL_DESC_COL_LITERAL_TYPE, dtype, sizeof(dtype), &len); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) + return Py_None; + break; + case VIRTUOSO_DV_TIMESTAMP: + case VIRTUOSO_DV_DATE: + case VIRTUOSO_DV_TIME: + case VIRTUOSO_DV_DATETIME: + Py_BEGIN_ALLOW_THREADS + ret = SQLGetDescField (hdesc, column, SQL_DESC_COL_DT_DT_TYPE, &dv_dt_type, SQL_IS_INTEGER, NULL); + Py_END_ALLOW_THREADS; + if (!SQL_SUCCEEDED(ret)) + return Py_None; + break; + default: + break; + } + + colinfo = Py_BuildValue("(Oiiiss)", + value, + dvtype, + dv_dt_type, + flag, + (char *)lang, + (char *)dtype); + if (!colinfo) + return Py_None; + + return colinfo; +} + PyObject* GetData(Cursor* cur, Py_ssize_t iCol) @@ -610,6 +692,10 @@ GetData(Cursor* cur, Py_ssize_t iCol) if (conv_index != -1) return GetDataUser(cur, iCol, conv_index); + // Check if we have to apply SPASQL processing + if (cur->spasql) + return GetDataSPASQL(cur, iCol); + switch (pinfo->sql_type) { case SQL_WCHAR: diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index 518d2a59..cdf452ba 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -19,6 +19,7 @@ #include "getdata.h" #include "cnxninfo.h" #include "dbspecific.h" +#include "virtuoso.h" #include #include @@ -890,6 +891,22 @@ static const ConstantDef aConstants[] = { MAKECONST(SQL_UNION), MAKECONST(SQL_USER_NAME), MAKECONST(SQL_XOPEN_CLI_YEAR), + // Virtuoso Extensions + MAKECONST(VIRTUOSO_DV_DATE), + MAKECONST(VIRTUOSO_DV_DATETIME), + MAKECONST(VIRTUOSO_DV_DOUBLE_FLOAT), + MAKECONST(VIRTUOSO_DV_IRI_ID), + MAKECONST(VIRTUOSO_DV_LONG_INT), + MAKECONST(VIRTUOSO_DV_NUMERIC), + MAKECONST(VIRTUOSO_DV_RDF), + MAKECONST(VIRTUOSO_DV_SINGLE_FLOAT), + MAKECONST(VIRTUOSO_DV_STRING), + MAKECONST(VIRTUOSO_DV_TIME), + MAKECONST(VIRTUOSO_DV_TIMESTAMP), + MAKECONST(VIRTUOSO_DV_TIMESTAMP_OBJ), + MAKECONST(VIRTUOSO_DT_TYPE_DATETIME), + MAKECONST(VIRTUOSO_DT_TYPE_DATE), + MAKECONST(VIRTUOSO_DT_TYPE_TIME) }; diff --git a/src/virtuoso.cpp b/src/virtuoso.cpp new file mode 100644 index 00000000..e662be41 --- /dev/null +++ b/src/virtuoso.cpp @@ -0,0 +1,35 @@ +#include "pyodbc.h" +#include "virtuoso.h" + +bool +isVirtuoso(HDBC hdbc) +{ + char buf[0x1000]; + SQLSMALLINT len; + SQLRETURN ret; + + ret = SQLGetInfo(hdbc, (SQLUSMALLINT)SQL_DBMS_NAME, buf, sizeof(buf), &len); + if (!SQL_SUCCEEDED(ret)) + return false; + if (!strncasecmp(buf, "OpenLink Virtuoso", sizeof(buf))) { + return true; + } + + return false; +} + +bool +isSPASQL(PyObject *pSql) +{ + char *query = PyString_AS_STRING(pSql); + + if (!query) + return false; + while (*query && isspace(*query)) + query++; + + if (!strncasecmp(query, "SPARQL", 6)) + return true; + return false; +} + diff --git a/src/virtuoso.h b/src/virtuoso.h new file mode 100644 index 00000000..881644a3 --- /dev/null +++ b/src/virtuoso.h @@ -0,0 +1,51 @@ +#ifndef VIRTUOSO_H +#define VIRTUOSO_H + +#ifdef HAVE_IODBC +#include +#endif + +/* + * Include Virtuoso ODBC extensions for SPASQL result set + */ +#if !defined (SQL_DESC_COL_DV_TYPE) + +/* + * ODBC extensions for SQLGetDescField + */ +# define SQL_DESC_COL_DV_TYPE 1057L +# define SQL_DESC_COL_DT_DT_TYPE 1058L +# define SQL_DESC_COL_LITERAL_ATTR 1059L +# define SQL_DESC_COL_BOX_FLAGS 1060L +# define SQL_DESC_COL_LITERAL_LANG 1061L +# define SQL_DESC_COL_LITERAL_TYPE 1062L + +/* + * Virtuoso - ODBC SQL_DESC_COL_DV_TYPE + */ +# define VIRTUOSO_DV_DATE 129 +# define VIRTUOSO_DV_DATETIME 211 +# define VIRTUOSO_DV_DOUBLE_FLOAT 191 +# define VIRTUOSO_DV_IRI_ID 243 +# define VIRTUOSO_DV_LONG_INT 189 +# define VIRTUOSO_DV_NUMERIC 219 +# define VIRTUOSO_DV_RDF 246 +# define VIRTUOSO_DV_SINGLE_FLOAT 190 +# define VIRTUOSO_DV_STRING 182 +# define VIRTUOSO_DV_TIME 210 +# define VIRTUOSO_DV_TIMESTAMP 128 +# define VIRTUOSO_DV_TIMESTAMP_OBJ 208 + +/* + * Virtuoso - ODBC SQL_DESC_COL_DT_DT_TYPE + */ +# define VIRTUOSO_DT_TYPE_DATETIME 1 +# define VIRTUOSO_DT_TYPE_DATE 2 +# define VIRTUOSO_DT_TYPE_TIME 3 + +#endif /* SQL_DESC_COL_DV_TYPE */ + +bool isVirtuoso(HDBC); +bool isSPASQL(PyObject *); + +#endif /* VIRTUOSO_H */