diff U3wB ./src/indexer.cpp ./src/indexer.cpp --- ./src/indexer.cpp Thu Aug 28 20:41:38 2008 +++ ./src/indexer.cpp Sun Nov 02 15:55:10 2008 @@ -506,6 +506,28 @@ #endif // USE_MYSQL +#if USE_FIREBIRD +CSphSource * SpawnSourceFBSQL ( const CSphConfigSection & hSource, const char * sSourceName ) +{ + assert ( hSource["type"]=="firebird" ); + + CSphSourceParams_FBSQL tParams; + if ( !SqlParamsConfigure ( tParams, hSource, sSourceName ) ) + return NULL; + + LOC_GETS ( tParams.m_sCharset, "sql_charset" ); + LOC_GETS ( tParams.m_sRole, "sql_role" ); + + CSphSource_FBSQL * pSrcFBSQL = new CSphSource_FBSQL ( sSourceName ); + if ( !pSrcFBSQL->Setup ( tParams ) ) + SafeDelete ( pSrcFBSQL ); + + return pSrcFBSQL; +} +#endif // USE_FIREBIRD + + + CSphSource * SpawnSourceXMLPipe ( const CSphConfigSection & hSource, const char * sSourceName, bool bUTF8 ) { assert ( hSource["type"]=="xmlpipe" || hSource["type"]=="xmlpipe2" ); @@ -571,6 +593,12 @@ if ( hSource["type"]=="mysql") return SpawnSourceMySQL ( hSource, sSourceName ); #endif + + #if USE_FIREBIRD + if ( hSource["type"]=="firebird") + return SpawnSourceFBSQL ( hSource, sSourceName ); + #endif + if ( hSource["type"]=="xmlpipe" || hSource["type"]=="xmlpipe2" ) return SpawnSourceXMLPipe ( hSource, sSourceName, bUTF8 ); diff U3wB ./src/sphinx.cpp ./src/sphinx.cpp --- ./src/sphinx.cpp Wed Oct 08 17:27:18 2008 +++ ./src/sphinx.cpp Sun Nov 02 15:56:25 2008 @@ -81,6 +81,11 @@ #pragma message("Automatically linking with libpq.lib") #endif +#if ( USE_WINDOWS && USE_FIREBIRD ) + #pragma comment(linker, "/defaultlib:fbclient_ms.lib") + #pragma message("Automatically linking with fbclient.lib") +#endif + #if ( USE_WINDOWS && USE_LIBSTEMMER ) #pragma comment(linker, "/defaultlib:libstemmer_c.lib") #pragma message("Automatically linking with libstemmer_c.lib") @@ -16983,6 +16988,423 @@ } #endif // USE_PGSQL + + +#if USE_FIREBIRD + +CSphSourceParams_FBSQL::CSphSourceParams_FBSQL () : + CSphSourceParams_SQL () +{ +} + +CSphSource_FBSQL::CSphSource_FBSQL ( const char * sName ) : + CSphSource_SQL ( sName ), + m_database ( NULL ), + m_transaction ( NULL ), + m_statement ( NULL ), + m_record ( NULL ), + m_recsize ( 0 ), + m_selectable ( false ), + m_blob ( NULL ), + m_blobsize ( 0 ) +{ + memset(m_status, 0, sizeof(m_status)); + m_error[0] = 0; + + const short DEF_SQLVARS = 4; + m_xsqlda = (XSQLDA *) new char[XSQLDA_LENGTH(DEF_SQLVARS)]; + m_xsqlda->version = 1; + m_xsqlda->sqln = DEF_SQLVARS; +} + +CSphSource_FBSQL::~CSphSource_FBSQL () +{ + delete[] (char *) m_xsqlda; + delete[] m_record; + delete[] m_blob; +} + +bool CSphSource_FBSQL::Setup ( const CSphSourceParams_FBSQL & pParams ) +{ + if ( !CSphSource_SQL::Setup ( pParams ) ) + return false; + + m_sCharset = pParams.m_sCharset; + m_sRole = pParams.m_sRole; + + return true; +} + +void CSphSource_FBSQL::SqlDismissResult () +{ + if ( !m_statement ) + return; + + if ( m_selectable ) { + isc_dsql_free_statement ( m_status, &m_statement, DSQL_close ); + } +} + +bool CSphSource_FBSQL::SqlQuery ( const char * sQuery ) +{ + if ( !m_statement ) { + if ( isc_dsql_allocate_statement ( m_status, &m_database, &m_statement ) ) + return false; + } + + if ( isc_dsql_prepare ( m_status, &m_transaction, &m_statement, 0, sQuery, + SQL_DIALECT_CURRENT, NULL ) ) + { + return false; + } + + m_selectable = false; + + // get statement type + const char stmt_info[] = { isc_info_sql_stmt_type }; + char info_buff[16]; + if ( isc_dsql_sql_info ( m_status, &m_statement, sizeof(stmt_info), stmt_info, + sizeof(info_buff), info_buff ) ) + { + return false; + } + if ( info_buff[0] != stmt_info[0] ) + return false; + + { + const int len = isc_vax_integer ( &info_buff[1], 2 ); + const int stmt_type = isc_vax_integer ( &info_buff[3], (short) len ); + + m_selectable = ( stmt_type == isc_info_sql_stmt_select || + stmt_type == isc_info_sql_stmt_select_for_upd ); + } + + + if ( m_selectable ) + { + if ( isc_dsql_describe ( m_status, &m_statement, SQLDA_VERSION1, m_xsqlda ) ) + return false; + + if ( m_xsqlda->sqld > m_xsqlda->sqln ) + { + const short len = m_xsqlda->sqld; + delete[] (char*) m_xsqlda; + + m_xsqlda = (XSQLDA*) new char[XSQLDA_LENGTH ( len )]; + m_xsqlda->sqln = len; + m_xsqlda->version = 1; + + if ( isc_dsql_describe ( m_status, &m_statement, SQLDA_VERSION1, m_xsqlda ) ) + return false; + } + + const size_t recsize = parseSQLDA ( m_xsqlda, NULL ); + if ( recsize > m_recsize ) + { + delete[] m_record; + + m_recsize = recsize; + m_record = new char[m_recsize]; + } + parseSQLDA ( m_xsqlda, m_record ); + } + else { + m_xsqlda->sqld = 0; + } + + if ( isc_dsql_execute ( m_status, &m_transaction, &m_statement, SQLDA_VERSION1, NULL ) ) + return false; + + return true; +} + +bool CSphSource_FBSQL::SqlIsError () +{ + return ( m_status[1] != 0 ); +} + +const char * CSphSource_FBSQL::SqlError () +{ + char * p = m_error, * const end = m_error + sizeof(m_error); + const ISC_STATUS * s = m_status; + while ( fb_interpret ( p, end - p, &s ) ) + { + p += strlen ( p ); + if ( p < end - 1 ) + *p++ = '\n'; + *p = 0; + } + + return m_error; +} + +bool CSphSource_FBSQL::SqlConnect () +{ + char dpb[256]; + char *p = dpb; + + *p++ = isc_dpb_version1; + p = putSphStringInDPB ( p, isc_dpb_user_name, m_tParams.m_sUser ); + p = putSphStringInDPB ( p, isc_dpb_password, m_tParams.m_sPass ); + p = putSphStringInDPB ( p, isc_dpb_sql_role_name, m_sRole ); + p = putSphStringInDPB ( p, isc_dpb_lc_ctype, m_sCharset ); + + if ( isc_attach_database ( m_status, 0, m_tParams.m_sDB.cstr(), &m_database, + (short) (p - dpb), dpb ) ) + { + return false; + } + + //char tpb[] = {isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed, + // isc_tpb_rec_version, isc_tpb_wait}; + + char tpb[] = { isc_tpb_version3, isc_tpb_write, isc_tpb_concurrency, isc_tpb_wait }; + + if ( isc_start_transaction ( m_status, &m_transaction, 1, &m_database, + sizeof ( tpb ), tpb ) ) + { + ISC_STATUS_ARRAY temp = { 0 }; + isc_detach_database ( temp, &m_database ); + return false; + } + + return true; +} + +void CSphSource_FBSQL::SqlDisconnect () +{ + if ( m_transaction ) { + if ( isc_commit_transaction ( m_status, &m_transaction ) ) + { + ISC_STATUS_ARRAY temp = {0}; + isc_rollback_transaction ( temp, &m_transaction ); + } + } + + if ( m_statement ) + isc_dsql_free_statement ( m_status, &m_statement, DSQL_drop ); + + if ( m_database ) + isc_detach_database ( m_status, &m_database ); +} + +int CSphSource_FBSQL::SqlNumFields () +{ + if ( !m_xsqlda ) + return 0; + + return m_xsqlda->sqld; +} + +bool CSphSource_FBSQL::SqlFetchRow () +{ + if ( !m_statement ) + return false; + + if ( isc_dsql_fetch ( m_status, &m_statement, SQLDA_VERSION1, m_xsqlda ) == 100 ) + return false; + + if ( m_status[1] ) + return false; + + // make returned strings NULL-terminated + XSQLVAR *var = m_xsqlda->sqlvar; + for ( int i = 0; i < m_xsqlda->sqld; var++, i++ ) + { + if ( *var->sqlind == 0 && (var->sqltype & (~1)) != SQL_BLOB ) + { + short len = * (short *) var->sqldata; + var->sqldata[len + sizeof(short)] = 0; + } + } + + return true; +} + +const char * CSphSource_FBSQL::SqlColumn ( int iIndex ) +{ + if (!m_xsqlda) + return 0; + + XSQLVAR &var = m_xsqlda->sqlvar[iIndex]; + if ( *var.sqlind != 0 ) + return NULL; + + if ( (var.sqltype & (~1)) != SQL_BLOB ) + return var.sqldata + sizeof(short); + + + // blob to string + ISC_QUAD * blob_id = (ISC_QUAD *) var.sqldata; + + // empty (NULL) blob + if ( !blob_id->gds_quad_high && !blob_id->gds_quad_low ) + return NULL; + + isc_blob_handle blob = {0}; + if ( isc_open_blob ( m_status, &m_database, &m_transaction, &blob, blob_id ) ) + return NULL; + + const char info[] = {isc_info_blob_total_length}; + char resp[16]; + if ( isc_blob_info ( m_status, &blob, sizeof(info), info, sizeof(resp), resp ) ) + return NULL; + + if ( info[0] != resp[0] ) + return NULL; + + int len = isc_vax_integer ( &resp[1], 2 ); + len = isc_vax_integer ( &resp[3], (short) len ); + + if (len + 4 > m_blobsize) + { + delete[] m_blob; + + m_blobsize = len + 4; + m_blob = new char[m_blobsize]; + } + + char *p = m_blob; + while ( true ) + { + const unsigned short MAX_SEGMENT = 64*1024 - 3; + unsigned short reads = 0; + + const ISC_STATUS ret = isc_get_segment ( m_status, &blob, &reads, + (unsigned short) Min(len, MAX_SEGMENT), p); + + if ( ret == 0 || ret == isc_segment ) + { + p += reads; + len -= reads; + } + else + { + if ( ret != isc_segstr_eof ) + p = NULL; + + break; + } + } + isc_close_blob ( m_status, &blob ); + + if ( !p ) + return NULL; + + // make string NULL-terminated for multi-byte encodings too + const char * end = p + 4; + while ( p < end && p < m_blob + m_blobsize ) + *p++ = 0; + + return m_blob; +} + +const char * CSphSource_FBSQL::SqlFieldName ( int iIndex ) +{ + if ( !m_xsqlda ) + return 0; + + return m_xsqlda->sqlvar[iIndex].aliasname; +} + +char * CSphSource_FBSQL::putSphStringInDPB ( char * dpb, char clump, CSphString &str ) +{ + if ( str.IsEmpty() ) + return dpb; + + *dpb++ = clump; + + const size_t len = strlen( str.cstr() ); + *dpb++ = (char) len; + + memcpy( dpb, str.cstr(), len ); + + return dpb + len; +} + +size_t CSphSource_FBSQL::parseSQLDA ( XSQLDA * xsqlda, char * buff ) +{ + // on the first pass (buff == NULL) convert all SQL_xxx into + // SQL_VARYING and make room for NULL-terminated string + + size_t offset = 0; + int i = 0; + XSQLVAR* var = xsqlda->sqlvar; + for ( ; i < xsqlda->sqld; var++, i++ ) + { + // round up to sizeof(short) + offset = (offset + 1) & ~(1); + + short length = var->sqllen; + const int type = var->sqltype & (~1); + switch ( type ) + { + case SQL_TEXT: + case SQL_VARYING: + if ( !buff ) + length += sizeof(short) + 1; + break; + + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: + case SQL_FLOAT: + case SQL_DOUBLE: + length = 26; + break; + + case SQL_TIMESTAMP: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + length = 34; + break; + + case SQL_BLOB: + { + // round up to sizeof(ISC_QUAD) + const int quad_size = sizeof(ISC_QUAD) - 1; + offset = (offset + quad_size) & ~(quad_size); + } + break; + + default: + break; + } + + if ( type != SQL_BLOB ) + { + var->sqltype = SQL_VARYING | 1; + var->sqllen = length; + } + else + { + var->sqltype = SQL_BLOB | 1; + } + if ( buff ) { + var->sqldata = &buff[offset]; + } + offset += length; + } + + // round up to sizeof(short) + offset = (offset + 1) & ~(1); + + // room for null-indicators (short's) + if ( buff ) { + for ( i = 0, var = xsqlda->sqlvar; i < xsqlda->sqld; var++, i++ ) + { + var->sqlind = (short*) (&buff[offset]); + offset += sizeof(short); + } + } + else { + offset += sizeof(short) * xsqlda->sqld; + } + + return offset; +} + +#endif // USE_FIREBIRD ///////////////////////////////////////////////////////////////////////////// // XMLPIPE diff U3wB ./src/sphinx.h ./src/sphinx.h --- ./src/sphinx.h Thu Oct 30 06:59:44 2008 +++ ./src/sphinx.h Sun Nov 02 15:56:55 2008 @@ -17,11 +17,12 @@ ///////////////////////////////////////////////////////////////////////////// #ifdef _WIN32 - #define USE_MYSQL 1 /// whether to compile MySQL support - #define USE_LIBEXPAT 1 /// whether to compile libexpat support - #define USE_LIBICONV 1 /// whether to compile iconv support + #define USE_MYSQL 0 /// whether to compile MySQL support + #define USE_LIBEXPAT 0 /// whether to compile libexpat support + #define USE_LIBICONV 0 /// whether to compile iconv support #define USE_LIBXML 0 /// whether to compile libxml support #define USE_WINDOWS 1 /// whether to compile for Windows + #define USE_FIREBIRD 1 /// whether to compile Firebird support #else #define USE_WINDOWS 0 /// whether to compile for Windows #endif @@ -43,6 +44,10 @@ #include #endif +#if USE_FIREBIRD +#include +#endif + #if USE_WINDOWS #include #else @@ -1162,6 +1167,65 @@ virtual const char * SqlFieldName ( int iIndex ); }; #endif // USE_PGSQL + +#if USE_FIREBIRD +/// Firebird specific source params +struct CSphSourceParams_FBSQL : CSphSourceParams_SQL +{ + CSphString m_sCharset; + CSphString m_sRole; + CSphSourceParams_FBSQL (); +}; + + +/// Firebird source implementation +/// multi-field plain-text documents fetched from given query +struct CSphSource_FBSQL : CSphSource_SQL +{ + CSphSource_FBSQL ( const char * sName ); + virtual ~CSphSource_FBSQL (); + bool Setup ( const CSphSourceParams_FBSQL & pParams ); + +protected: + /// config values + CSphString m_sCharset; + CSphString m_sRole; + + /// API handles + ISC_STATUS_ARRAY m_status; + isc_db_handle m_database; + isc_tr_handle m_transaction; + isc_stmt_handle m_statement; + + // error and record holders + static const int ERROR_BUFFER_SIZE = 1024; + char m_error[ERROR_BUFFER_SIZE]; + + XSQLDA * m_xsqlda; + char * m_record; + size_t m_recsize; + bool m_selectable; + char * m_blob; + size_t m_blobsize; + +protected: + virtual void SqlDismissResult (); + virtual bool SqlQuery ( const char * sQuery ); + virtual bool SqlIsError (); + virtual const char * SqlError (); + virtual bool SqlConnect (); + virtual void SqlDisconnect (); + virtual int SqlNumFields (); + virtual bool SqlFetchRow (); + virtual const char * SqlColumn ( int iIndex ); + virtual const char * SqlFieldName ( int iIndex ); + +private: + char * putSphStringInDPB ( char * dpb, char clump, CSphString &str ); + static size_t parseSQLDA ( XSQLDA * xsqlda, char * buff ); +}; + +#endif // USE_FIREBIRD /// XML pipe source implementation diff U3wB ./src/sphinxutils.cpp ./src/sphinxutils.cpp --- ./src/sphinxutils.cpp Thu Mar 13 15:38:36 2008 +++ ./src/sphinxutils.cpp Sun Nov 02 15:57:05 2008 @@ -109,6 +109,10 @@ { "sql_group_column", KEY_LIST | KEY_DEPRECATED, "sql_attr_uint" }, { "sql_date_column", KEY_LIST | KEY_DEPRECATED, "sql_attr_timestamp" }, { "sql_str2ordinal_column", KEY_LIST | KEY_DEPRECATED, "sql_attr_str2ordinal" }, +#if USE_FIREBIRD + { "sql_charset", 0, NULL }, + { "sql_role", 0, NULL }, +#endif { NULL, 0, NULL } };