Эх сурвалжийг харах

merged from rel098 upto r1321


git-svn-id: svn://svn.sphinxsearch.com/sphinx/trunk@1322 406a0c4d-033a-0410-8de8-e80135713968
shodan 17 жил өмнө
parent
commit
e95e86ca3e

+ 2 - 2
Makefile.am

@@ -4,8 +4,8 @@ else
 SUBDIRS = src test
 endif
 
-EXTRA_DIST = api storage sphinx.conf.in example.sql
-sysconf_DATA = sphinx.conf.dist example.sql
+EXTRA_DIST = api storage sphinx.conf.in sphinx-min.conf.in example.sql
+sysconf_DATA = sphinx.conf.dist sphinx-min.conf.dist example.sql
 
 install-data-hook:
 	mkdir -p $(DESTDIR)$(localstatedir)/data && mkdir -p $(DESTDIR)$(localstatedir)/log

+ 9 - 7
Makefile.in

@@ -36,10 +36,10 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 subdir = .
 DIST_COMMON = $(am__configure_deps) $(srcdir)/Makefile.am \
-	$(srcdir)/Makefile.in $(srcdir)/sphinx.conf.in \
-	$(top_srcdir)/config/config.h.in $(top_srcdir)/configure \
-	COPYING INSTALL config/depcomp config/install-sh \
-	config/missing
+	$(srcdir)/Makefile.in $(srcdir)/sphinx-min.conf.in \
+	$(srcdir)/sphinx.conf.in $(top_srcdir)/config/config.h.in \
+	$(top_srcdir)/configure COPYING INSTALL config/depcomp \
+	config/install-sh config/missing
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
 	$(top_srcdir)/configure.ac
@@ -49,7 +49,7 @@ am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
  configure.lineno configure.status.lineno
 mkinstalldirs = $(install_sh) -d
 CONFIG_HEADER = $(top_builddir)/config/config.h
-CONFIG_CLEAN_FILES = sphinx.conf.dist
+CONFIG_CLEAN_FILES = sphinx.conf.dist sphinx-min.conf.dist
 SOURCES =
 DIST_SOURCES =
 RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
@@ -177,8 +177,8 @@ sysconfdir = @sysconfdir@
 target_alias = @target_alias@
 @USE_LIBSTEMMER_FALSE@SUBDIRS = src test
 @USE_LIBSTEMMER_TRUE@SUBDIRS = libstemmer_c src test
-EXTRA_DIST = api storage sphinx.conf.in example.sql
-sysconf_DATA = sphinx.conf.dist example.sql
+EXTRA_DIST = api storage sphinx.conf.in sphinx-min.conf.in example.sql
+sysconf_DATA = sphinx.conf.dist sphinx-min.conf.dist example.sql
 all: all-recursive
 
 .SUFFIXES:
@@ -234,6 +234,8 @@ distclean-hdr:
 	-rm -f config/config.h config/stamp-h1
 sphinx.conf.dist: $(top_builddir)/config.status $(srcdir)/sphinx.conf.in
 	cd $(top_builddir) && $(SHELL) ./config.status $@
+sphinx-min.conf.dist: $(top_builddir)/config.status $(srcdir)/sphinx-min.conf.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
 uninstall-info-am:
 install-sysconfDATA: $(sysconf_DATA)
 	@$(NORMAL_INSTALL)

+ 1 - 0
api/sphinxapi.php

@@ -415,6 +415,7 @@ class SphinxClient
 			|| $mode==SPH_MATCH_PHRASE
 			|| $mode==SPH_MATCH_BOOLEAN
 			|| $mode==SPH_MATCH_EXTENDED
+			|| $mode==SPH_MATCH_FULLSCAN
 			|| $mode==SPH_MATCH_EXTENDED2 );
 		$this->_mode = $mode;
 	}

+ 27 - 4
configure

@@ -876,6 +876,7 @@ Optional Packages:
   --with-pgsql-libs       path to PostgreSQL libraries
   --with-libstemmer       compile with libstemmer support (default is
                           disabled)
+  --with-iconv            compile with iconv support (default is autodetect)
 
 Some influential environment variables:
   CC          C compiler command
@@ -8171,9 +8172,22 @@ echo "$as_me: WARNING: xmlpipe2 will NOT be available" >&2;}
 fi
 
 
+
+# Check whether --with-iconv or --without-iconv was given.
+if test "${with_iconv+set}" = set; then
+  withval="$with_iconv"
+  ac_cv_use_iconv=$withval
+else
+  ac_cv_use_iconv=yes
+
+fi;
 echo "$as_me:$LINENO: checking for libiconv" >&5
 echo $ECHO_N "checking for libiconv... $ECHO_C" >&6
-if test  $have_iconv_h = yes -a $have_libiconv = yes ; then
+if test  $have_iconv_h = yes \
+	-a $have_libiconv = yes \
+	-a $got_expat -eq 1 \
+	-a $ac_cv_use_iconv != no ; \
+then
 
 cat >>confdefs.h <<\_ACEOF
 #define USE_LIBICONV 1
@@ -8265,11 +8279,19 @@ _ACEOF
 echo "${ECHO_T}char **" >&6
 	fi
 else
-	echo "$as_me:$LINENO: result: not found" >&5
-echo "${ECHO_T}not found" >&6
 	if test  $got_expat -eq 1 ; then
+		if test  $ac_cv_use_iconv = no ; then
+			echo "$as_me:$LINENO: result: disabled" >&5
+echo "${ECHO_T}disabled" >&6
+		else
+			echo "$as_me:$LINENO: result: not found" >&5
+echo "${ECHO_T}not found" >&6
+		fi
 		{ echo "$as_me:$LINENO: WARNING: xmlpipe2 will only support default encodings (latin-1, utf-8)" >&5
 echo "$as_me: WARNING: xmlpipe2 will only support default encodings (latin-1, utf-8)" >&2;}
+	else
+		echo "$as_me:$LINENO: result: not required" >&5
+echo "${ECHO_T}not required" >&6
 	fi
 fi
 
@@ -8372,7 +8394,7 @@ fi
 CONFDIR=`eval echo "${my_op_prefix}"`
 
 
-                                        ac_config_files="$ac_config_files Makefile src/Makefile libstemmer_c/Makefile sphinx.conf.dist:sphinx.conf.in"
+                                                  ac_config_files="$ac_config_files Makefile src/Makefile libstemmer_c/Makefile sphinx.conf.dist:sphinx.conf.in sphinx-min.conf.dist:sphinx-min.conf.in"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -8962,6 +8984,7 @@ do
   "src/Makefile" ) CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
   "libstemmer_c/Makefile" ) CONFIG_FILES="$CONFIG_FILES libstemmer_c/Makefile" ;;
   "sphinx.conf.dist" ) CONFIG_FILES="$CONFIG_FILES sphinx.conf.dist:sphinx.conf.in" ;;
+  "sphinx-min.conf.dist" ) CONFIG_FILES="$CONFIG_FILES sphinx-min.conf.dist:sphinx-min.conf.in" ;;
   "depfiles" ) CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
   "config/config.h" ) CONFIG_HEADERS="$CONFIG_HEADERS config/config.h" ;;
   *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5

+ 18 - 3
configure.ac

@@ -201,8 +201,16 @@ fi
 
 dnl ---
 
+AC_ARG_WITH([iconv],
+	AC_HELP_STRING([--with-iconv], [compile with iconv support (default is autodetect)]),
+	[ac_cv_use_iconv=$withval], [ac_cv_use_iconv=yes]
+)
 AC_MSG_CHECKING([for libiconv])
-if test [ $have_iconv_h = yes -a $have_libiconv = yes ]; then
+if test [ $have_iconv_h = yes \
+	-a $have_libiconv = yes \
+	-a $got_expat -eq 1 \
+	-a $ac_cv_use_iconv != no ]; \
+then
 	AC_DEFINE([USE_LIBICONV],1,[define to use iconv library])
 	AC_MSG_RESULT([found])
 
@@ -227,9 +235,15 @@ if test [ $have_iconv_h = yes -a $have_libiconv = yes ]; then
 		AC_MSG_RESULT([char **])
 	fi
 else
-	AC_MSG_RESULT([not found])
 	if test [ $got_expat -eq 1 ]; then
+		if test [ $ac_cv_use_iconv = no ]; then
+			AC_MSG_RESULT([disabled])
+		else
+			AC_MSG_RESULT([not found])
+		fi
 		AC_MSG_WARN([xmlpipe2 will only support default encodings (latin-1, utf-8)])
+	else
+		AC_MSG_RESULT([not required])
 	fi
 fi
 
@@ -271,7 +285,8 @@ fi
 CONFDIR=`eval echo "${my_op_prefix}"`
 AC_SUBST(CONFDIR)
 
-AC_CONFIG_FILES([Makefile src/Makefile libstemmer_c/Makefile sphinx.conf.dist:sphinx.conf.in])
+AC_CONFIG_FILES([Makefile src/Makefile libstemmer_c/Makefile sphinx.conf.dist:sphinx.conf.in \
+	sphinx-min.conf.dist:sphinx-min.conf.in])
 AC_OUTPUT
 
 dnl --------------------------------------------------------------------------

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
doc/sphinx.html


+ 1 - 1
doc/sphinx.txt

@@ -2733,7 +2733,7 @@ results. That could be achieved with:
 
 Example:
 
-   | sql_query_pre = SET NAMES=utf8
+   | sql_query_pre = SET NAMES utf8
    | sql_query_pre = SET SESSION query_cache_type=OFF
 
 8.1.10. sql_query

+ 1 - 1
doc/sphinx.xml

@@ -2962,7 +2962,7 @@ sql_query_pre = SET SESSION query_cache_type=OFF
 </para>
 <bridgehead>Example:</bridgehead>
 <programlisting>
-sql_query_pre = SET NAMES=utf8
+sql_query_pre = SET NAMES utf8
 sql_query_pre = SET SESSION query_cache_type=OFF
 </programlisting>
 </sect3>

+ 53 - 0
sphinx-min.conf.in

@@ -0,0 +1,53 @@
+#
+# Minimal Sphinx configuration sample (clean, simple, functional)
+#
+
+source src1
+{
+	type					= mysql
+
+	sql_host				= localhost
+	sql_user				= test
+	sql_pass				=
+	sql_db					= test
+	sql_port				= 3306	# optional, default is 3306
+
+	sql_query				= \
+		SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \
+		FROM documents
+
+	sql_attr_uint			= group_id
+	sql_attr_timestamp		= date_added
+
+	sql_query_info			= SELECT * FROM documents WHERE id=$id
+}
+
+
+index test1
+{
+	source					= src1
+	path					= @CONFDIR@/data/test1
+	docinfo					= extern
+	charset_type			= sbcs
+}
+
+
+indexer
+{
+	mem_limit				= 32M
+}
+
+
+searchd
+{
+	port					= 3312
+	log						= @CONFDIR@/log/searchd.log
+	query_log				= @CONFDIR@/log/query.log
+	read_timeout			= 5
+	max_children			= 30
+	pid_file				= @CONFDIR@/log/searchd.pid
+	max_matches				= 1000
+	seamless_rotate			= 1
+	preopen_indexes			= 0
+	unlink_old				= 1
+}

+ 46 - 5
src/searchd.cpp

@@ -2190,7 +2190,7 @@ bool SearchReplyParser_t::ParseReply ( MemInputBuffer_c & tReq, Agent_t & tAgent
 
 /////////////////////////////////////////////////////////////////////////////
 
-// returns true both schemas were equal; false otherwise
+// returns true if incoming schema (src) is equal to existing (dst); false otherwise
 bool MinimizeSchema ( CSphSchema & tDst, const CSphSchema & tSrc )
 {
 	// if dst is empty, result is also empty
@@ -2207,10 +2207,30 @@ bool MinimizeSchema ( CSphSchema & tDst, const CSphSchema & tSrc )
 	{
 		int iSrcIdx = tSrc.GetAttrIndex ( dDst[i].m_sName.cstr() );
 
+		// check for index mismatch
 		if ( iSrcIdx!=i )
 			bEqual = false;
 
-		if ( iSrcIdx==-1 )
+		// check for type/size mismatch (and fixup if needed)
+		if ( iSrcIdx>=0 )
+		{
+			const CSphColumnInfo & tSrcAttr = tSrc.GetAttr(iSrcIdx);
+			if ( tSrcAttr.m_eAttrType!=dDst[i].m_eAttrType )
+			{
+				// different types? remove the attr
+				iSrcIdx = -1;
+				bEqual = false;
+
+			} else if ( tSrcAttr.m_tLocator.m_iBitCount!=dDst[i].m_tLocator.m_iBitCount )
+			{
+				// different bit sizes? choose the max one
+				dDst[i].m_tLocator.m_iBitCount = Max ( dDst[i].m_tLocator.m_iBitCount, tSrcAttr.m_tLocator.m_iBitCount );
+				bEqual = false;
+			}
+		}
+
+		// check for presence
+		if ( iSrcIdx<0 )
 		{
 			dDst.Remove ( i );
 			i--;
@@ -2994,8 +3014,6 @@ bool MinimizeAggrResult ( AggrResult_t & tRes, const CSphQuery & tQuery )
 
 		ARRAY_FOREACH ( iSchema, tRes.m_dSchemas )
 		{
-			assert ( tRow.m_iRowitems<=tRes.m_dSchemas[iSchema].GetRowSize() );
-
 			for ( int i=0; i<tRes.m_tSchema.GetAttrsCount(); i++ ) 
 			{
 				dMapFrom[i] = tRes.m_dSchemas[iSchema].GetAttrIndex ( tRes.m_tSchema.GetAttr(i).m_sName.cstr() );
@@ -3005,10 +3023,10 @@ bool MinimizeAggrResult ( AggrResult_t & tRes, const CSphQuery & tQuery )
 			for ( int i=iCur; i<iCur+tRes.m_dMatchCounts[iSchema]; i++ )
 			{
 				CSphMatch & tMatch = tRes.m_dMatches[i];
-				assert ( tMatch.m_iRowitems>=tRow.m_iRowitems );
 
 				if ( tRow.m_iRowitems )
 				{
+					// remap attrs
 					for ( int j=0; j<tRes.m_tSchema.GetAttrsCount(); j++ ) 
 					{
 						const CSphColumnInfo & tDst = tRes.m_tSchema.GetAttr(j);
@@ -3016,6 +3034,15 @@ bool MinimizeAggrResult ( AggrResult_t & tRes, const CSphQuery & tQuery )
 						tRow.SetAttr ( tDst.m_tLocator, tMatch.GetAttr(tSrc.m_tLocator) );
 					}
 
+					// remapped row might need *more* space because of unpacked attributes; allocate if so
+					if ( tMatch.m_iRowitems<tRow.m_iRowitems )
+					{
+						SafeDeleteArray ( tMatch.m_pRowitems );
+						tMatch.m_iRowitems = tRow.m_iRowitems;
+						tMatch.m_pRowitems = new CSphRowitem [ tRow.m_iRowitems ];
+					}
+
+					// copy remapped row
 					for ( int j=0; j<tRow.m_iRowitems; j++ )
 						tMatch.m_pRowitems[j] = tRow.m_pRowitems[j];
 				}
@@ -4076,7 +4103,10 @@ void HandleCommandUpdate ( int iSock, int iVer, InputBuffer_c & tReq, int iPipeF
 	CSphAttrUpdate_t tUpd;
 	tUpd.m_dAttrs.Resize ( tReq.GetDword() ); // FIXME! check this
 	ARRAY_FOREACH ( i, tUpd.m_dAttrs )
+	{
 		tUpd.m_dAttrs[i].m_sName = tReq.GetString ();
+		tUpd.m_dAttrs[i].m_sName.ToLower ();
+	}
 
 	int iStride = DOCINFO_IDSIZE + tUpd.m_dAttrs.GetLength();
 
@@ -6181,6 +6211,17 @@ int WINAPI ServiceMain ( int argc, char **argv )
 			}
 		}
 
+		if ( ( hIndex.GetInt ( "min_prefix_len", 0 ) > 0 || hIndex.GetInt ( "min_infix_len", 0 ) > 0 ) 
+			&& hIndex.GetInt ( "enable_star" ) == 0 )
+		{
+			const char * szMorph = hIndex.GetStr ( "morphology", "" );
+			if ( szMorph && *szMorph && strcmp ( szMorph, "none" ) )
+			{
+				sphWarning ( "index '%s': infixes and morphology are enabled, enable_star=0; NOT SERVING", sIndexName );
+				continue;
+			}
+		}
+
 		iValidIndexes++;
 	}
 	if ( !iValidIndexes )

+ 2 - 2
src/spelldump.cpp

@@ -658,7 +658,7 @@ bool CISpellAffix::AddToCharset ( char * szRangeL, char * szRangeU )
 		if ( cMaxU - cMinU != cMaxL - cMinL )
 			return false;
 
-		for ( char i = 0; i < cMaxL - cMinL; ++i )
+		for ( char i = 0; i <= cMaxL - cMinL; ++i )
 			if ( IsInSet ( cMinL + i, szRangeL ) && IsInSet ( cMinU + i, szRangeU ) )
 				AddCharPair ( cMinL + i, cMinU + i );
 	}
@@ -676,7 +676,7 @@ bool CISpellAffix::AddToCharset ( char * szRangeL, char * szRangeU )
 
 void CISpellAffix::AddCharPair ( char cCharL, char cCharU )
 {
-	m_dCharset [(BYTE)cCharL] = cCharU;
+	m_dCharset [(BYTE)cCharU] = cCharL;
 }
 
 

+ 58 - 27
src/sphinx.cpp

@@ -10351,7 +10351,7 @@ public:
 								ExtNode_i ();
 	virtual						~ExtNode_i () {}
 
-	static ExtNode_i *			Create ( const CSphExtendedQueryNode * pNode, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning );
+	static ExtNode_i *			Create ( const CSphExtendedQueryNode * pNode, const CSphTermSetup & tSetup, CSphString * pWarning );
 	static ExtNode_i *			Create ( const CSphString & sTerm, DWORD uFields, int iMaxFieldPos, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning );
 
 	virtual const ExtDoc_t *	GetDocsChunk ( SphDocID_t * pMaxID ) = 0;
@@ -10483,7 +10483,7 @@ protected:
 class ExtPhrase_c : public ExtNode_i
 {
 public:
-								ExtPhrase_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning );
+								ExtPhrase_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning );
 								~ExtPhrase_c ();
 	virtual const ExtDoc_t *	GetDocsChunk ( SphDocID_t * pMaxID );
 	virtual const ExtHit_t *	GetHitsChunk ( const ExtDoc_t * pDocs, SphDocID_t uMaxID );
@@ -10517,7 +10517,7 @@ protected:
 class ExtProximity_c : public ExtPhrase_c
 {
 public:
-								ExtProximity_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning );
+								ExtProximity_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning );
 	virtual const ExtDoc_t *	GetDocsChunk ( SphDocID_t * pMaxID );
 
 protected:
@@ -10530,7 +10530,7 @@ protected:
 class ExtQuorum_c : public ExtNode_i
 {
 public:
-								ExtQuorum_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning );
+								ExtQuorum_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning );
 	virtual						~ExtQuorum_c ();
 
 	virtual const ExtDoc_t *	GetDocsChunk ( SphDocID_t * pMaxID );
@@ -10617,7 +10617,7 @@ ExtNode_i * ExtNode_i::Create ( const CSphString & sTerm, DWORD uFields, int iMa
 }
 
 
-ExtNode_i * ExtNode_i::Create ( const CSphExtendedQueryNode * pNode, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning )
+ExtNode_i * ExtNode_i::Create ( const CSphExtendedQueryNode * pNode, const CSphTermSetup & tSetup, CSphString * pWarning )
 {
 	if ( pNode->IsPlain() )
 	{
@@ -10630,11 +10630,11 @@ ExtNode_i * ExtNode_i::Create ( const CSphExtendedQueryNode * pNode, const CSphT
 		DWORD uFields = tAtom.m_uFields;
 
 		if ( iWords==1 )
-			return Create ( tAtom.m_dWords[0].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, uQuerypos, pWarning );
+			return Create ( tAtom.m_dWords[0].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, tAtom.m_dWords[0].m_iAtomPos, pWarning );
 
 		assert ( tAtom.m_iMaxDistance>=0 );
 		if ( tAtom.m_iMaxDistance==0 )
-			return new ExtPhrase_c ( pNode, uFields, tSetup, uQuerypos, pWarning );
+			return new ExtPhrase_c ( pNode, uFields, tSetup, pWarning );
 
 		else if ( tAtom.m_bQuorum )
 		{
@@ -10648,32 +10648,32 @@ ExtNode_i * ExtNode_i::Create ( const CSphExtendedQueryNode * pNode, const CSphT
 
 				// create AND node
 				const CSphVector<CSphExtendedQueryAtomWord> & dWords = pNode->m_tAtom.m_dWords;
-				ExtNode_i * pCur = Create ( dWords[0].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, uQuerypos+dWords[0].m_iAtomPos, pWarning );
+				ExtNode_i * pCur = Create ( dWords[0].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, dWords[0].m_iAtomPos, pWarning );
 				for ( int i=1; i<dWords.GetLength(); i++ )
-					pCur = new ExtAnd_c ( pCur, Create ( dWords[i].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, uQuerypos+dWords[i].m_iAtomPos, pWarning ) );
+					pCur = new ExtAnd_c ( pCur, Create ( dWords[i].m_sWord, uFields, tAtom.m_iMaxFieldPos, tSetup, dWords[i].m_iAtomPos, pWarning ) );
 				return pCur;
 
 			} else
 			{
 				// threshold is ok; create quorum node
-				return new ExtQuorum_c ( pNode, uFields, tSetup, uQuerypos, pWarning );
+				return new ExtQuorum_c ( pNode, uFields, tSetup, pWarning );
 			}
 
 		} else
-			return new ExtProximity_c ( pNode, uFields, tSetup, uQuerypos, pWarning );
+			return new ExtProximity_c ( pNode, uFields, tSetup, pWarning );
 
 	} else
 	{
 		int iChildren = pNode->m_dChildren.GetLength ();
 		assert ( iChildren>0 );
 
-		ExtNode_i * pCur = ExtNode_i::Create ( pNode->m_dChildren[0], tSetup, uQuerypos, pWarning );
+		ExtNode_i * pCur = ExtNode_i::Create ( pNode->m_dChildren[0], tSetup, pWarning );
 		for ( int i=1; i<iChildren; i++ )
 		{
 			if ( pNode->m_bAny )
-				pCur = new ExtOr_c ( pCur, ExtNode_i::Create ( pNode->m_dChildren[i], tSetup, uQuerypos, pWarning ) );
+				pCur = new ExtOr_c ( pCur, ExtNode_i::Create ( pNode->m_dChildren[i], tSetup, pWarning ) );
 			else
-				pCur = new ExtAnd_c ( pCur, ExtNode_i::Create ( pNode->m_dChildren[i], tSetup, uQuerypos+i, pWarning ) );
+				pCur = new ExtAnd_c ( pCur, ExtNode_i::Create ( pNode->m_dChildren[i], tSetup, pWarning ) );
 		}
 		return pCur;
 	}
@@ -11435,7 +11435,7 @@ const ExtHit_t * ExtAndNot_c::GetHitsChunk ( const ExtDoc_t * pDocs, SphDocID_t
 
 //////////////////////////////////////////////////////////////////////////
 
-ExtPhrase_c::ExtPhrase_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning )
+ExtPhrase_c::ExtPhrase_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning )
 	: m_pDocs ( NULL )
 	, m_pHits ( NULL )
 	, m_pDoc ( NULL )
@@ -11460,17 +11460,17 @@ ExtPhrase_c::ExtPhrase_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, c
 	int iWords = dWords.GetLength();
 	assert ( iWords>1 );
 
-	m_uMinQpos = uQuerypos+dWords[0].m_iAtomPos;
-	m_uMaxQpos = uQuerypos+dWords.Last().m_iAtomPos;
+	m_uMinQpos = dWords[0].m_iAtomPos;
+	m_uMaxQpos = dWords.Last().m_iAtomPos;
 
 	m_dQposDelta.Resize ( m_uMaxQpos-m_uMinQpos+1 );
 	ARRAY_FOREACH ( i, m_dQposDelta )
 		m_dQposDelta[i] = -INT_MAX;
 
-	ExtNode_i * pCur = Create ( dWords[0].m_sWord, uFields, pNode->m_tAtom.m_iMaxFieldPos, tSetup, uQuerypos+dWords[0].m_iAtomPos, pWarning );
+	ExtNode_i * pCur = Create ( dWords[0].m_sWord, uFields, pNode->m_tAtom.m_iMaxFieldPos, tSetup, dWords[0].m_iAtomPos, pWarning );
 	for ( int i=1; i<iWords; i++ )
 	{
-		pCur = new ExtAnd_c ( pCur, Create ( dWords[i].m_sWord, uFields, pNode->m_tAtom.m_iMaxFieldPos, tSetup, uQuerypos+dWords[i].m_iAtomPos, pWarning ) );
+		pCur = new ExtAnd_c ( pCur, Create ( dWords[i].m_sWord, uFields, pNode->m_tAtom.m_iMaxFieldPos, tSetup, dWords[i].m_iAtomPos, pWarning ) );
 		m_dQposDelta [ dWords[i-1].m_iAtomPos - dWords[0].m_iAtomPos ] = dWords[i].m_iAtomPos - dWords[i-1].m_iAtomPos;
 	}
 	m_pNode = pCur;
@@ -11843,8 +11843,8 @@ void ExtPhrase_c::SetQwordsIDF ( const ExtQwordsHash_t & hQwords )
 
 //////////////////////////////////////////////////////////////////////////
 
-ExtProximity_c::ExtProximity_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning )
-	: ExtPhrase_c ( pNode, uFields, tSetup, uQuerypos, pWarning )
+ExtProximity_c::ExtProximity_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning )
+	: ExtPhrase_c ( pNode, uFields, tSetup, pWarning )
 	, m_iMaxDistance ( pNode->m_tAtom.m_iMaxDistance )
 	, m_iNumWords ( pNode->m_tAtom.m_dWords.GetLength() )
 {
@@ -12026,7 +12026,7 @@ const ExtDoc_t * ExtProximity_c::GetDocsChunk ( SphDocID_t * pMaxID )
 
 //////////////////////////////////////////////////////////////////////////
 
-ExtQuorum_c::ExtQuorum_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, DWORD uQuerypos, CSphString * pWarning )
+ExtQuorum_c::ExtQuorum_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, const CSphTermSetup & tSetup, CSphString * pWarning )
 {
 	assert ( pNode );
 	assert ( pNode->IsPlain() );
@@ -12043,7 +12043,7 @@ ExtQuorum_c::ExtQuorum_c ( const CSphExtendedQueryNode * pNode, DWORD uFields, c
 
 	ARRAY_FOREACH ( i, dWords )
 	{
-		m_dChildren.Add ( new ExtTerm_c ( dWords[i].m_sWord, uFields, tSetup, uQuerypos+dWords[i].m_iAtomPos, pWarning ) );
+		m_dChildren.Add ( new ExtTerm_c ( dWords[i].m_sWord, uFields, tSetup, dWords[i].m_iAtomPos, pWarning ) );
 		m_pCurDoc.Add ( NULL );
 		m_pCurHit.Add ( NULL );
 	}
@@ -12234,11 +12234,11 @@ ExtRanker_c::ExtRanker_c ( const CSphExtendedQueryNode * pAccept, const CSphExte
 	m_tTestMatch.Reset ( tSetup.m_tMin.m_iRowitems + tSetup.m_iToCalc );
 
 	assert ( pAccept );
-	m_pRoot = ExtNode_i::Create ( pAccept, tSetup, 1, pWarning );
+	m_pRoot = ExtNode_i::Create ( pAccept, tSetup, pWarning );
 
 	if ( m_pRoot && pReject )
 	{
-		ExtNode_i * pRej = ExtNode_i::Create ( pReject, tSetup, 1, pWarning );
+		ExtNode_i * pRej = ExtNode_i::Create ( pReject, tSetup, pWarning );
 		if ( pRej )
 			m_pRoot = new ExtAndNot_c ( m_pRoot, pRej );
 	}
@@ -13065,6 +13065,9 @@ void CSphIndex_VLN::MatchFullScan ( const CSphQuery * pQuery, int iSorters, ISph
 
 			SPH_SUBMIT_MATCH ( tMatch );
 		}
+
+		if ( iCutoff==0 )
+			return;
 	}
 }
 
@@ -16979,7 +16982,7 @@ bool CSphSource_SQL::SetupRanges ( const char * sRangeQuery, const char * sQuery
 		return false;
 	}
 
-	if ( SqlColumn(0)==NULL && SqlColumn(1)==NULL )
+	if ( ( SqlColumn(0)==NULL || !SqlColumn(0)[0] ) && ( SqlColumn(1)==NULL || !SqlColumn(1)[0] ) )
 	{
 		// the source seems to be empty; workaround
 		m_uMinID = 1;
@@ -17659,6 +17662,30 @@ bool CSphSource_PgSQL::Setup ( const CSphSourceParams_PgSQL & tParams )
 }
 
 
+bool CSphSource_PgSQL::IterateHitsStart ( CSphString & sError )
+{
+	bool bResult = CSphSource_SQL::IterateHitsStart ( sError );
+	if ( !bResult )
+		return false;
+
+	int iMaxIndex = 0;
+	for ( int i = 0; i < m_tSchema.GetAttrsCount(); i++ )
+		iMaxIndex = Max ( iMaxIndex, m_tSchema.GetAttr(i).m_iIndex );
+
+	ARRAY_FOREACH ( i, m_tSchema.m_dFields )
+		iMaxIndex = Max ( iMaxIndex, m_tSchema.m_dFields[i].m_iIndex );
+
+	m_dIsColumnBool.Resize ( iMaxIndex + 1 );
+	ARRAY_FOREACH ( i, m_dIsColumnBool )
+		m_dIsColumnBool[i] = false;
+
+	for ( int i = 0; i < m_tSchema.GetAttrsCount(); i++ )
+		m_dIsColumnBool[m_tSchema.GetAttr(i).m_iIndex] = m_tSchema.GetAttr(i).m_eAttrType == SPH_ATTR_BOOL;
+
+	return true;
+}
+
+
 bool CSphSource_PgSQL::SqlConnect ()
 {
 	char sPort[64];
@@ -17727,7 +17754,11 @@ const char * CSphSource_PgSQL::SqlColumn ( int iIndex )
 	if ( !m_pPgResult )
 		return NULL;
 
-	return PQgetvalue ( m_pPgResult, m_iPgRow, iIndex );
+	const char * szValue = PQgetvalue ( m_pPgResult, m_iPgRow, iIndex );
+	if ( m_dIsColumnBool.GetLength() && m_dIsColumnBool[iIndex] && szValue[0]=='t' && !szValue[1] )
+		return "1";
+
+	return szValue;
 }
 
 

+ 2 - 0
src/sphinx.h

@@ -1231,6 +1231,7 @@ struct CSphSource_PgSQL : CSphSource_SQL
 {
 							CSphSource_PgSQL ( const char * sName );
 	bool					Setup ( const CSphSourceParams_PgSQL & pParams );
+	virtual bool			IterateHitsStart ( CSphString & sError );
 
 protected:
 	PGresult * 				m_pPgResult;	///< postgresql execution restult context
@@ -1240,6 +1241,7 @@ protected:
 	int						m_iPgRow;		///< current row (0 based, as in PQgetvalue)
 
 	CSphString				m_sPgClientEncoding;
+	CSphVector<bool>		m_dIsColumnBool;
 
 protected:
 	virtual void			SqlDismissResult ();

+ 9 - 22
src/sphinxexpr.cpp

@@ -392,6 +392,7 @@ static int ParseNumeric ( YYSTYPE * lvalp, const char ** ppStr )
 	}
 }
 
+
 static bool IsNumericAttrType ( DWORD eType )
 {
 	return eType==SPH_ATTR_INTEGER
@@ -401,6 +402,7 @@ static bool IsNumericAttrType ( DWORD eType )
 		|| eType==SPH_ATTR_BIGINT;
 }
 
+
 /// a lexer of my own
 /// returns token id and fills lvalp on success
 /// returns -1 and fills sError on failure
@@ -415,36 +417,21 @@ int ExprParser_t::GetToken ( YYSTYPE * lvalp )
 	if ( isdigit(*m_pCur) )
 		return ParseNumeric ( lvalp, &m_pCur );
 
-	// check for magic names
-	if ( *m_pCur=='@' && sphIsAttr(m_pCur[1]) && !isdigit(m_pCur[1]) )
+	// check for field, function, or magic name
+	if ( sphIsAttr(m_pCur[0]) 
+		|| ( m_pCur[0]=='@' && sphIsAttr(m_pCur[1]) && !isdigit(m_pCur[1]) ) )
 	{
 		// get token
-		const char * pStart = ++m_pCur;
+		const char * pStart = m_pCur++;
 		while ( sphIsAttr(*m_pCur) ) m_pCur++;
 
 		CSphString sTok;
 		sTok.SetBinary ( pStart, m_pCur-pStart );
 		sTok.ToLower ();
 
-		if ( sTok=="id" )		{ lvalp->eDocinfo = DI_ID; return TOK_DOCINFO; }
-		if ( sTok=="weight" )	{ lvalp->eDocinfo = DI_WEIGHT; return TOK_DOCINFO; }
-
-		m_sLexerError.SetSprintf ( "unknown magic name '@%s'", sTok.cstr() );
-		return -1;
-	}
-
-	// check for field or function name
-	if ( sphIsAttr(*m_pCur) )
-	{
-		// get token
-		const char * pStart = m_pCur;
-		assert ( !isdigit(*m_pCur) ); // can't start with a digit, right?
-
-		while ( sphIsAttr(*m_pCur) ) m_pCur++;
-		assert ( !sphIsAttr(*m_pCur) );
-
-		CSphString sTok;
-		sTok.SetBinary ( pStart, m_pCur-pStart );
+		// check for magic name
+		if ( sTok=="@id" )		{ lvalp->eDocinfo = DI_ID; return TOK_DOCINFO; }
+		if ( sTok=="@weight" )	{ lvalp->eDocinfo = DI_WEIGHT; return TOK_DOCINFO; }
 
 		// check for attribute
 		int iAttr = m_pSchema->GetAttrIndex ( sTok.cstr() );

+ 1 - 1
src/sphinxquery.cpp

@@ -1227,7 +1227,7 @@ bool CSphExtendedQueryParser::Parse ( CSphExtendedQuery & tParsed, const char *
 			if ( dState.Last()==XQS_NEGTEXT )
 				dState.Pop ();
 
-			CSphExtendedQueryAtomWord tAW ( sToken, -1 );
+			CSphExtendedQueryAtomWord tAW ( sToken, iAtomPos );
 			PushNode ();
 			m_dStack.Last().m_bAny = bAny;
 			m_dStack.Last().m_pNode->m_tAtom.m_uFields = uFields;

+ 2 - 0
test/stopwords.txt

@@ -1,2 +1,4 @@
 a
 the
+and
+of

+ 93 - 0
test/test_28/model.aff

@@ -0,0 +1,93 @@
+wordchars	a	A
+wordchars	[bc]	[BC]
+wordchars	[de]	[DE]
+wordchars	[f-i]	[F-I]
+wordchars	[j-n]	[J-N]
+wordchars	o	O
+wordchars	[p-s]	[P-S]
+wordchars	[tu]	[TU]
+wordchars	[v-y]	[V-Y]
+wordchars	z	Z
+
+prefixes
+
+flag *A:
+    .		>	RE			# As in enter > reenter
+
+flag *I:
+    .		>	IN			# As in disposed > indisposed
+
+flag *U:
+    .		>	UN			# As in natural > unnatural
+
+suffixes
+
+flag V:
+    E		>	-E,IVE		# As in create > creative
+    [^E]	>	IVE			# As in prevent > preventive
+
+flag *N:
+    E		>	-E,ION		# As in create > creation
+    Y		>	-Y,ICATION	# As in multiply > multiplication
+    [^EY]	>	EN			# As in fall > fallen
+
+flag *X:
+    E		>	-E,IONS		# As in create > creations
+    Y		>	-Y,ICATIONS	# As in multiply > multiplications
+    [^EY]	>	ENS			# As in weak > weakens
+
+flag H:
+    Y		>	-Y,IETH		# As in twenty > twentieth
+    [^Y]	>	TH			# As in hundred > hundredth
+
+flag *Y:
+    Y		>	-Y,ILY		# As in messy > messily
+    [^Y]	>	LY			# As in quick > quickly
+
+flag *G:
+    E		>	-E,ING		# As in file > filing
+    [^E]	>	ING			# As in cross > crossing
+
+flag *J:
+    E		>	-E,INGS		# As in file > filings
+    [^E]	>	INGS		# As in cross > crossings
+
+flag *D:
+    E		>	D			# As in create > created
+    [^AEIOU]Y	>	-Y,IED	# As in imply > implied
+    [^EY]	>	ED			# As in cross > crossed
+    [AEIOU]Y	>	ED		# As in convey > conveyed
+
+flag T:
+    E		>	ST			# As in late > latest
+    [^AEIOU]Y	>	-Y,IEST	# As in dirty > dirtiest
+    [AEIOU]Y	>	EST		# As in gray > grayest
+    [^EY]	>	EST			# As in small > smallest
+
+flag *R:
+    E		>	R			# As in skate > skater
+    [^AEIOU]Y	>	-Y,IER	# As in multiply > multiplier
+    [AEIOU]Y	>	ER		# As in convey > conveyer
+    [^EY]	>	ER			# As in build > builder
+
+flag *Z:
+    E		>	RS			# As in skate > skaters
+    [^AEIOU]Y	>	-Y,IERS	# As in multiply > multipliers
+    [AEIOU]Y	>	ERS		# As in convey > conveyers
+    [^EY]	>	ERS			# As in build > builders
+
+flag *S:
+    [^AEIOU]Y	>	-Y,IES	# As in imply > implies
+    [AEIOU]Y	>	S		# As in convey > conveys
+    [CST]H	>	ES			# As in lash > lashes  (some TH's...)
+    [^CST]H	>	S			# As in cough > coughs
+    [SXZ]	>	ES			# As in fix > fixes
+    [^SXZHY]	>	S		# As in bat > bats
+
+flag *P:
+    [^AEIOU]Y	>	-Y,INESS	# As in cloudy > cloudiness
+    [AEIOU]Y	>	NESS	# As in gray > grayness
+    [^Y]	>	NESS		# As in late > lateness
+
+flag *M:
+    .		>	'S			# As in dog > dog's

+ 30 - 0
test/test_28/model.dict

@@ -0,0 +1,30 @@
+ANSI
+enter/DGRS
+disposed/I
+natural/PSY
+create/ADGNSVX
+prevent/DGRSV
+multiply/DGNRSXZ
+fall/GMNRS
+weak/NPRTXY
+twenty/HS
+hundred/HS
+messy/PRTY
+quick/NPRTXY
+file/DGJMRSZ
+cross/DGJRSYZ
+imply/DGNSX
+convey/DGRSZ
+late/DPRTY
+dirty/DGPRSTY
+gray/DGPRSTY
+small/PRT
+skate/DGRSZ
+build/DGJRSZ
+lash/DGJRS
+cough/DGRS
+fix/DGJRSZ
+bat/DGMRS
+cloudy/PRTY
+dog/MS
+anorexia

+ 170 - 0
test/test_28/model.spell

@@ -0,0 +1,170 @@
+ANSI > ANSI
+anorexia > anorexia
+bat > bat
+bated > bat
+bating > bat
+bat's > bat
+bater > bat
+bats > bat
+build > build
+builded > build
+building > build
+buildings > build
+builder > build
+builds > build
+builders > build
+cloudy > cloudy
+cloudiness > cloudy
+cloudier > cloudy
+cloudiest > cloudy
+cloudily > cloudy
+convey > convey
+conveyed > convey
+conveying > convey
+conveyer > convey
+conveys > convey
+conveyers > convey
+cough > cough
+coughed > cough
+coughing > cough
+cougher > cough
+coughs > cough
+create > create
+recreate > create
+recreated > create
+recreating > create
+recreation > create
+recreates > create
+recreations > create
+created > create
+creating > create
+creation > create
+creates > create
+creative > create
+creations > create
+cross > cross
+crossed > cross
+crossing > cross
+crossings > cross
+crosser > cross
+crosses > cross
+crossly > cross
+crossers > cross
+dirty > dirty
+dirtied > dirty
+dirtying > dirty
+dirtiness > dirty
+dirtier > dirty
+dirties > dirty
+dirtiest > dirty
+dirtily > dirty
+disposed > disposed
+indisposed > disposed
+dog > dog
+dog's > dog
+dogs > dog
+enter > enter
+entered > enter
+entering > enter
+enterer > enter
+enters > enter
+fall > fall
+falling > fall
+fall's > fall
+fallen > fall
+faller > fall
+falls > fall
+file > file
+filed > file
+filing > file
+filings > file
+file's > file
+filer > file
+files > file
+filers > file
+fix > fix
+fixed > fix
+fixing > fix
+fixings > fix
+fixer > fix
+fixes > fix
+fixers > fix
+gray > gray
+grayed > gray
+graying > gray
+grayness > gray
+grayer > gray
+grays > gray
+grayest > gray
+graily > gray
+hundred > hundred
+hundredth > hundred
+hundreds > hundred
+imply > imply
+implied > imply
+implying > imply
+implication > imply
+implies > imply
+implications > imply
+lash > lash
+lashed > lash
+lashing > lash
+lashings > lash
+lasher > lash
+lashes > lash
+late > late
+lated > late
+lateness > late
+later > late
+latest > late
+lately > late
+messy > messy
+messiness > messy
+messier > messy
+messiest > messy
+messily > messy
+multiply > multiply
+multiplied > multiply
+multiplying > multiply
+multiplication > multiply
+multiplier > multiply
+multiplies > multiply
+multiplications > multiply
+multipliers > multiply
+natural > natural
+naturalness > natural
+naturals > natural
+naturally > natural
+prevent > prevent
+prevented > prevent
+preventing > prevent
+preventer > prevent
+prevents > prevent
+preventive > prevent
+quick > quick
+quicken > quick
+quickness > quick
+quicker > quick
+quickest > quick
+quickens > quick
+quickly > quick
+skate > skate
+skated > skate
+skating > skate
+skater > skate
+skates > skate
+skaters > skate
+small > small
+smallness > small
+smaller > small
+smallest > small
+twenty > twenty
+twentieth > twenty
+twenties > twenty
+weak > weak
+weaken > weak
+weakness > weak
+weaker > weak
+weakest > weak
+weakens > weak
+weakly > weak

+ 43 - 0
test/test_28/test.inc

@@ -0,0 +1,43 @@
+printf ( "testing $test_path, spelldump... " );
+
+$windows = isset($_SERVER["WINDIR"]) || isset($_SERVER["windir"]) || isset($_SERVER["HOMEDRIVE"]);
+
+if ( $windows )
+	$spelldump_path = "..\\bin\\debug\\spelldump";
+else
+	$spelldump_path = "../src/spelldump";
+
+global $g_model;
+if ( $g_model )
+{
+	exec ( "$spelldump_path $test_path/model.dict $test_path/model.aff $test_path/model.spell", $error, $retval );
+
+	if ( !file_exists ( "$test_path/model.spell" ) )
+		return false;
+
+	printf ( "done; 1/1 subtests OK\n" );
+	return true;
+}
+
+exec ( "$spelldump_path $test_path/model.dict $test_path/model.aff $test_path/current.spell", $error, $retval );
+
+if ( !file_exists ( "$test_path/current.spell" ) )
+	return false;
+
+$model = file_get_contents ( "$test_path/model.spell" );
+$result = file_get_contents ( "$test_path/current.spell" );
+if ( $model != $result )
+{
+	if ( $windows )
+		system ( "diff -u3 $test_path/model.spell $test_path/current.spell > $test_path/report.txt" );
+	else
+		system ( "diff $test_path/model.spell $test_path/current.spell > $test_path/report.txt" );
+
+	printf ( "FAILED\n" );
+	return false;
+}
+
+unlink ( "$test_path/current.spell" );
+printf ( "done; 1/1 subtests OK\n" );
+
+return true;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
test/test_29/model.bin


+ 127 - 0
test/test_29/test.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<test>
+
+<name>merging results</name>
+
+<config>
+indexer
+{
+	mem_limit		= 16M
+}
+
+searchd
+{
+	<searchd_settings/>
+}
+
+source srctest1
+{
+	type			= mysql
+	<sql_settings/>
+
+	sql_query		= SELECT document_id, attr1, attr2, body FROM test_table
+	sql_attr_uint	= attr1
+	sql_attr_uint	= attr2
+}
+
+source srctest2
+{
+	type			= mysql
+	<sql_settings/>
+
+	sql_query		= SELECT document_id, attr1, attr2, body FROM test_table
+	sql_attr_uint	= attr1:5
+	sql_attr_uint	= attr2:5
+}
+
+source srctest3
+{
+	type			= mysql
+	<sql_settings/>
+
+	sql_query		= SELECT document_id, attr2, attr3, body FROM test_table
+	sql_attr_uint	= attr2
+	sql_attr_uint	= attr3
+}
+
+source srctest4
+{
+	type			= mysql
+	<sql_settings/>
+
+	sql_query		= SELECT document_id, attr1, attr2, body FROM test_table
+	sql_attr_uint	= attr1
+	sql_attr_uint	= attr2
+}
+
+index test1
+{
+	source			= srctest1
+	path			= <data_path/>/test1
+    charset_type 	= utf-8
+	min_prefix_len	= 1
+}
+
+index test2
+{
+	source			= srctest2
+	path			= <data_path/>/test2
+    charset_type 	= utf-8
+	min_prefix_len	= 1
+}
+
+index test3
+{
+	source			= srctest3
+	path			= <data_path/>/test3
+    charset_type 	= utf-8
+	min_prefix_len	= 1
+}
+
+index test4
+{
+	source			= srctest4
+	path			= <data_path/>/test4
+    charset_type 	= utf-8
+	min_prefix_len	= 1
+}
+
+</config>
+
+<queries>
+<query index="test1 test2">word</query>
+<query index="test1 test2 test3">word</query>
+<query index="test1 test2 test3 test4">word</query>
+<query index="test2 test3">word</query>
+<query index="test2 test3 test4">word</query>
+<query index="test3 test4">word</query>
+<query index="test1 test3">word</query>
+<query index="test1 test4">word</query>
+<query index="test2 test4">word</query>
+</queries>
+
+<db_create>
+CREATE TABLE `test_table`
+(
+	`document_id` int(11) NOT NULL default '0',
+	`attr1` int(11) NOT NULL default '0',
+	`attr2` int(11) NOT NULL default '0',
+	`attr3` int(11) NOT NULL default '0',
+	`attr4` int(11) NOT NULL default '0',
+	`body` varchar(255) NOT NULL default ''
+)
+</db_create>
+
+<db_drop>
+DROP TABLE IF EXISTS `test_table`
+</db_drop>
+
+<db_insert>
+INSERT INTO `test_table` VALUES
+( 1,	1, 	5,	9,	13,	'wordforms' ),
+( 2, 	2,	6,	10,	14,	'wordies' ),
+( 3,	3,	7,	11,	15,	'words' ),
+( 4, 	4,	8,	12,	16,	'word' )
+</db_insert>
+
+</test>

+ 1 - 0
test/test_30/model.bin

@@ -0,0 +1 @@
+a:1:{i:0;a:2:{i:0;a:11:{s:5:"error";s:0:"";s:7:"warning";s:0:"";s:6:"status";i:0;s:6:"fields";a:1:{i:0;s:4:"body";}s:5:"attrs";a:0:{}s:7:"matches";a:3:{i:1;a:2:{s:6:"weight";s:4:"3500";s:5:"attrs";a:0:{}}i:2;a:2:{s:6:"weight";s:4:"3500";s:5:"attrs";a:0:{}}i:3;a:2:{s:6:"weight";s:4:"1500";s:5:"attrs";a:0:{}}}s:5:"total";s:1:"3";s:11:"total_found";s:1:"3";s:4:"time";s:5:"0.033";s:5:"words";a:3:{s:3:"one";a:2:{s:4:"docs";s:1:"3";s:4:"hits";s:1:"3";}s:3:"two";a:2:{s:4:"docs";s:1:"3";s:4:"hits";s:1:"3";}s:5:"three";a:2:{s:4:"docs";s:1:"3";s:4:"hits";s:1:"3";}}s:5:"query";s:13:"one two three";}i:1;a:11:{s:5:"error";s:0:"";s:7:"warning";s:0:"";s:6:"status";i:0;s:6:"fields";a:1:{i:0;s:4:"body";}s:5:"attrs";a:0:{}s:7:"matches";a:2:{i:4;a:2:{s:6:"weight";s:4:"4587";s:5:"attrs";a:0:{}}i:5;a:2:{s:6:"weight";s:4:"2587";s:5:"attrs";a:0:{}}}s:5:"total";s:1:"2";s:11:"total_found";s:1:"2";s:4:"time";s:5:"0.004";s:5:"words";a:4:{s:6:"senior";a:2:{s:4:"docs";s:1:"2";s:4:"hits";s:1:"2";}s:6:"pastor";a:2:{s:4:"docs";s:1:"2";s:4:"hits";s:1:"2";}s:9:"riverside";a:2:{s:4:"docs";s:1:"2";s:4:"hits";s:1:"2";}s:6:"church";a:2:{s:4:"docs";s:1:"2";s:4:"hits";s:1:"2";}}s:5:"query";s:33:"senior pastor of riverside church";}}}

+ 59 - 0
test/test_30/test.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<test>
+
+<name>ext2 ranking</name>
+
+<config>
+indexer
+{
+	mem_limit		= 16M
+}
+
+searchd
+{
+	<searchd_settings/>
+}
+
+source srctest
+{
+	type			= mysql
+	<sql_settings/>
+	sql_query		= SELECT document_id, body FROM test_table
+}
+
+index test
+{
+	source			= srctest
+	path			= <data_path/>/test
+	stopwords		= stopwords.txt
+	min_word_len	= 3
+}
+</config>
+
+<queries>
+<query mode="extended2">one two three</query>
+<query mode="extended2">senior pastor of riverside church</query>
+</queries>
+
+<db_create>
+CREATE TABLE `test_table`
+(
+	`document_id` int(11) NOT NULL default '0',
+	`body` varchar(255) NOT NULL default ''
+)
+</db_create>
+
+<db_drop>
+DROP TABLE IF EXISTS `test_table`
+</db_drop>
+
+<db_insert>
+INSERT INTO `test_table` VALUES
+( 1, 'one two three' ),
+( 2, 'one two three four' ),
+( 3, 'one then two then three then four' ),
+( 4, 'senior pastor of Riverside church' ),
+( 5, 'senior pastor and the Riverside church' )
+</db_insert>
+
+</test>

+ 25 - 9
test/ubertest.php

@@ -133,17 +133,33 @@ $total_subtests = 0;
 $total_subtests_failed = 0;
 foreach ( $tests as $test )
 {
-	$res = RunTest ( $test );
+	if ( file_exists ( $test."/test.xml" ) )
+	{
+		$res = RunTest ( $test );
+
+		if ( !is_array($res) )
+			continue; // failed to run that test at all
+
+		$total_tests++;
+		$total_subtests += $res["tests_total"];
+		if ( $res["tests_failed"] )
+		{
+			$total_tests_failed++;
+			$total_subtests_failed += $res["tests_failed"];
+		}
+	}
+	elseif ( file_exists ( $test."/test.inc" ) )
+	{
+		$run_func = create_function ( '$test_path', file_get_contents ( $test."/test.inc" ) );
 
-	if ( !is_array($res) )
-		continue; // failed to run that test at all
+		$total_tests++;
+		$total_subtests++;
 
-	$total_tests++;
-	$total_subtests += $res["tests_total"];
-	if ( $res["tests_failed"] )
-	{
-		$total_tests_failed++;
-		$total_subtests_failed += $res["tests_failed"];
+		if ( !$run_func ( $test ) )
+		{
+			$total_tests_failed++;
+			$total_subtests_failed++;
+    	}
 	}
 }
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно