Selaa lähdekoodia

fixed #56 JSON array double and mixed at IN and aggregate functions

Stanislav Klinov 8 vuotta sitten
vanhempi
sitoutus
d9da4efd68
3 muutettua tiedostoa jossa 342 lisäystä ja 97 poistoa
  1. 291 97
      src/sphinxexpr.cpp
  2. 3 0
      test/test_325/model.bin
  3. 48 0
      test/test_325/test.xml

+ 291 - 97
src/sphinxexpr.cpp

@@ -1258,6 +1258,136 @@ public:
 };
 
 
+template <typename T>
+T JsonAggr ( ESphJsonType eJson, const BYTE * pVal, ESphAggrFunc eFunc, CSphString * pBuf )
+{
+	if ( !pVal || ( eFunc!=SPH_AGGR_MIN && eFunc!=SPH_AGGR_MAX ) )
+		return 0;
+
+	switch ( eJson )
+	{
+	case JSON_INT32_VECTOR:
+		{
+			int iVals = sphJsonUnpackInt ( &pVal );
+			if ( iVals==0 )
+				return 0;
+
+			const int * p = (const int*) pVal;
+			int iRes = *p; // first value
+
+			switch ( eFunc )
+			{
+			case SPH_AGGR_MIN: while ( --iVals ) if ( *++p<iRes ) iRes = *p; break;
+			case SPH_AGGR_MAX: while ( --iVals ) if ( *++p>iRes ) iRes = *p; break;
+			default: return 0;
+			}
+			return (T)iRes;
+		}
+	case JSON_DOUBLE_VECTOR:
+		{
+			int iLen = sphJsonUnpackInt ( &pVal );
+			if ( !iLen || ( eFunc!=SPH_AGGR_MIN && eFunc!=SPH_AGGR_MAX ) )
+				return 0;
+
+			double fRes = ( eFunc==SPH_AGGR_MIN ? FLT_MAX : FLT_MIN );
+			const BYTE * p = pVal;
+			for ( int i=0; i<iLen; i++ )
+			{
+				double fStored = sphQW2D ( sphJsonLoadBigint ( &p ) );
+				switch ( eFunc )
+				{
+				case SPH_AGGR_MIN:
+					fRes = Min ( fRes, fStored );
+					break;
+				case SPH_AGGR_MAX:
+					fRes = Max ( fRes, fStored );
+					break;
+				default: return 0;
+				}
+			}
+			return (T)fRes;
+		}
+	case JSON_STRING_VECTOR:
+		{
+			if ( !pBuf )
+				return 0;
+
+			sphJsonUnpackInt ( &pVal ); // skip node length
+
+			int iVals = sphJsonUnpackInt ( &pVal );
+			if ( iVals==0 )
+				return 0;
+
+			// first value
+			int iLen = sphJsonUnpackInt ( &pVal );
+			const char * pRes = (const char* )pVal;
+			int iResLen = iLen;
+
+			while ( --iVals )
+			{
+				pVal += iLen;
+				iLen = sphJsonUnpackInt ( &pVal );
+
+				// binary string comparison
+				int iCmp = memcmp ( pRes, (const char*)pVal, iLen<iResLen ? iLen : iResLen );
+				if ( iCmp==0 && iLen!=iResLen )
+					iCmp = iResLen-iLen;
+
+				if ( ( eFunc==SPH_AGGR_MIN && iCmp>0 ) || ( eFunc==SPH_AGGR_MAX && iCmp<0 ) )
+				{
+					pRes = (const char*)pVal;
+					iResLen = iLen;
+				}
+			}
+
+			pBuf->SetBinary ( pRes, iResLen );
+			return (T)iResLen;
+		}
+	case JSON_MIXED_VECTOR:
+		{
+			sphJsonUnpackInt ( &pVal ); // skip node length
+			int iLen = sphJsonUnpackInt ( &pVal );
+			if ( !iLen || ( eFunc!=SPH_AGGR_MIN && eFunc!=SPH_AGGR_MAX ) )
+				return 0;
+
+			double fRes = ( eFunc==SPH_AGGR_MIN ? FLT_MAX : FLT_MIN );
+			for ( int i=0; i<iLen; i++ )
+			{
+				double fVal = ( eFunc==SPH_AGGR_MIN ? FLT_MAX : FLT_MIN );
+
+				ESphJsonType eType = (ESphJsonType)*pVal++;
+				switch (eType)
+				{
+					case JSON_INT32:
+					case JSON_INT64:
+						fVal = (double)( eType==JSON_INT32 ? sphJsonLoadInt ( &pVal ) : sphJsonLoadBigint ( &pVal ) );
+					break;
+					case JSON_DOUBLE:
+						fVal = sphQW2D ( sphJsonLoadBigint ( &pVal ) );
+						break;
+					default:
+						sphJsonSkipNode ( eType, &pVal );
+						break; // for weird subobjects, just let min
+				}
+
+				switch ( eFunc )
+				{
+				case SPH_AGGR_MIN:
+					fRes = Min ( fRes, fVal );
+					break;
+				case SPH_AGGR_MAX:
+					fRes = Max ( fRes, fVal );
+					break;
+				default: return 0;
+				}
+			}
+			return (T)fRes;
+		}
+	default: return 0;
+	}
+}
+
+
 struct Expr_JsonFieldAggr_c : public Expr_JsonFieldConv_c
 {
 protected:
@@ -1273,29 +1403,7 @@ public:
 	{
 		const BYTE * pVal = NULL;
 		ESphJsonType eJson = GetKey ( &pVal, tMatch );
-		switch ( eJson )
-		{
-		case JSON_INT32_VECTOR:
-			{
-				int iVals = sphJsonUnpackInt ( &pVal );
-				if ( iVals==0 )
-					return 0;
-
-				const int * p = (const int*) pVal;
-				int iRes = *p; // first value
-
-				switch ( m_eFunc )
-				{
-				case SPH_AGGR_MIN: while ( --iVals ) if ( *++p<iRes ) iRes = *p; break;
-				case SPH_AGGR_MAX: while ( --iVals ) if ( *++p>iRes ) iRes = *p; break;
-				default:
-					return 0;
-				}
-				return iRes;
-			}
-		default:
-			return 0;
-		}
+		return JsonAggr<int> ( eJson, pVal, m_eFunc, NULL );
 	}
 
 	virtual int StringEval ( const CSphMatch & tMatch, const BYTE ** ppStr ) const
@@ -1304,63 +1412,56 @@ public:
 		*ppStr = NULL;
 		const BYTE * pVal = NULL;
 		ESphJsonType eJson = GetKey ( &pVal, tMatch );
+
+		int iLen = 0;
+		int iVal = 0;
+		float fVal = 0.0f;
+		
 		switch ( eJson )
 		{
 		case JSON_INT32_VECTOR:
-			sBuf.SetSprintf ( "%u", IntEval ( tMatch ) );
+			iVal = JsonAggr<int> ( eJson, pVal, m_eFunc, NULL );
+			sBuf.SetSprintf ( "%u", iVal );
+			iLen = sBuf.Length();
 			*ppStr = (const BYTE *) sBuf.Leak();
-			return strlen ( (const char*) *ppStr );
+			return iLen;
 
 		case JSON_STRING_VECTOR:
-			{
-				sphJsonUnpackInt ( &pVal ); // skip node length
-
-				int iVals = sphJsonUnpackInt ( &pVal );
-				if ( iVals==0 )
-					return 0;
+			JsonAggr<int> ( eJson, pVal, m_eFunc, &sBuf );
+			iLen = sBuf.Length();
+			*ppStr = (const BYTE *) sBuf.Leak();
+			return iLen;
 
-				switch ( m_eFunc )
-				{
-				case SPH_AGGR_MIN:
-				case SPH_AGGR_MAX:
-					{
-						// first value
-						int iLen = sphJsonUnpackInt ( &pVal );
-						const char *pRes = (const char*) pVal;
-						int iResLen = iLen;
+		case JSON_DOUBLE_VECTOR:
+			fVal = JsonAggr<float> ( eJson, pVal, m_eFunc, NULL );
+			sBuf.SetSprintf ( "%f", fVal );
+			iLen = sBuf.Length();
+			*ppStr = (const BYTE *) sBuf.Leak();
+			return iLen;
 
-						while ( --iVals )
-						{
-							pVal += iLen;
-							iLen = sphJsonUnpackInt ( &pVal );
-
-							// binary string comparison
-							int iCmp = memcmp ( pRes, (const char*)pVal, iLen<iResLen ? iLen : iResLen );
-							if ( iCmp==0 && iLen!=iResLen )
-								iCmp = iResLen-iLen;
-
-							if ( ( m_eFunc==SPH_AGGR_MIN && iCmp>0 ) || ( m_eFunc==SPH_AGGR_MAX && iCmp<0 ) )
-							{
-								pRes = (const char*)pVal;
-								iResLen = iLen;
-							}
-						}
+		case JSON_MIXED_VECTOR:
+			fVal = JsonAggr<float> ( eJson, pVal, m_eFunc, NULL );
+			sBuf.SetSprintf ( "%f", fVal );
+			iLen = sBuf.Length();
+			*ppStr = (const BYTE *) sBuf.Leak();
+			return iLen;
 
-						sBuf.SetBinary ( pRes, iResLen );
-						*ppStr = (const BYTE *) sBuf.Leak();
-						return iResLen;
-					}
-				default:
-					return 0;
-				}
-			}
-		default:
-			return 0;
+		default: return 0;
 		}
 	}
 
-	virtual float	Eval ( const CSphMatch & tMatch ) const { return (float)IntEval ( tMatch ); }
-	virtual int64_t Int64Eval ( const CSphMatch & tMatch ) const { return (int64_t)IntEval ( tMatch ); }
+	virtual float Eval ( const CSphMatch & tMatch ) const
+	{
+		const BYTE * pVal = NULL;
+		ESphJsonType eJson = GetKey ( &pVal, tMatch );
+		return JsonAggr<float> ( eJson, pVal, m_eFunc, NULL );
+	}
+	virtual int64_t Int64Eval ( const CSphMatch & tMatch ) const
+	{
+		const BYTE * pVal = NULL;
+		ESphJsonType eJson = GetKey ( &pVal, tMatch );
+		return JsonAggr<int64_t> ( eJson, pVal, m_eFunc, NULL );
+	}
 	virtual bool IsStringPtr() const { return true; }
 
 	virtual uint64_t GetHash ( const ISphSchema & tSorterSchema, uint64_t uPrevHash, bool & bDisable )
@@ -4818,6 +4919,7 @@ public:
 	/// take ownership of arg, pre-evaluate and dismiss turn points
 	Expr_ArgVsConstSet_c ( ISphExpr * pArg, CSphVector<ISphExpr *> & dArgs, int iSkip )
 		: Expr_ArgVsSet_c<T> ( pArg )
+		, m_bFloat ( false )
 	{
 		CSphMatch tDummy;
 		for ( int i=iSkip; i<dArgs.GetLength(); i++ )
@@ -4830,16 +4932,25 @@ public:
 	}
 
 	/// take ownership of arg, and copy that constlist
-	Expr_ArgVsConstSet_c ( ISphExpr * pArg, ConstList_c * pConsts )
+	Expr_ArgVsConstSet_c ( ISphExpr * pArg, ConstList_c * pConsts, bool bKeepFloat )
 		: Expr_ArgVsSet_c<T> ( pArg )
+		, m_bFloat ( false )
 	{
 		if ( !pConsts )
 			return; // can happen on uservar path
 		if ( pConsts->m_eRetType==SPH_ATTR_FLOAT )
 		{
 			m_dValues.Reserve ( pConsts->m_dFloats.GetLength() );
-			ARRAY_FOREACH ( i, pConsts->m_dFloats )
-				m_dValues.Add ( (T)pConsts->m_dFloats[i] );
+			if ( !bKeepFloat )
+			{
+				ARRAY_FOREACH ( i, pConsts->m_dFloats )
+					m_dValues.Add ( (T)pConsts->m_dFloats[i] );
+			} else
+			{
+				m_bFloat = true;
+				ARRAY_FOREACH ( i, pConsts->m_dFloats )
+					m_dValues.Add ( (T) sphF2DW ( pConsts->m_dFloats[i] ) );
+			}
 		} else
 		{
 			m_dValues.Reserve ( pConsts->m_dInts.GetLength() );
@@ -4850,6 +4961,20 @@ public:
 		CalcValueHash();
 	}
 
+	/// take ownership of arg, and copy that uservar
+	Expr_ArgVsConstSet_c ( ISphExpr * pArg, UservarIntSet_c * pUservar )
+		: Expr_ArgVsSet_c<T> ( pArg )
+		, m_bFloat ( false )
+	{
+		if ( !pUservar )
+			return; // can happen on uservar path
+		m_dValues.Reserve ( pUservar->GetLength() );
+		for ( int i=0; i<pUservar->GetLength(); i++ )
+			m_dValues.Add ( (T)*(pUservar->Begin() + i) );
+
+		CalcValueHash();
+	}
+
 	virtual uint64_t GetHash ( const ISphSchema & tSorterSchema, uint64_t uPrevHash, bool & bDisable )
 	{
 		EXPR_CLASS_NAME("Expr_ArgVsConstSet_c");
@@ -4859,6 +4984,7 @@ public:
 protected:
 	CSphVector<T>	m_dValues;
 	uint64_t		m_uValueHash;
+	bool			m_bFloat;
 
 	void CalcValueHash()
 	{
@@ -4952,7 +5078,7 @@ class Expr_In_c : public Expr_ArgVsConstSet_c<T>
 public:
 	/// pre-sort values for binary search
 	Expr_In_c ( ISphExpr * pArg, ConstList_c * pConsts ) :
-		Expr_ArgVsConstSet_c<T> ( pArg, pConsts )
+		Expr_ArgVsConstSet_c<T> ( pArg, pConsts, false )
 	{
 		this->m_dValues.Sort();
 	}
@@ -5018,7 +5144,7 @@ class Expr_MVAIn_c : public Expr_ArgVsConstSet_c<int64_t>, public ExprLocatorTra
 public:
 	/// pre-sort values for binary search
 	Expr_MVAIn_c ( const CSphAttrLocator & tLoc, int iLocator, ConstList_c * pConsts, UservarIntSet_c * pUservar )
-		: Expr_ArgVsConstSet_c<int64_t> ( NULL, pConsts )
+		: Expr_ArgVsConstSet_c<int64_t> ( NULL, pConsts, false )
 		, ExprLocatorTraits_t ( tLoc, iLocator )
 		, m_pMvaPool ( NULL )
 		, m_pUservar ( pUservar )
@@ -5288,18 +5414,17 @@ int64_t Expr_MVAAggr_c<true>::MvaAggr ( const DWORD * pMva, ESphAggrFunc eFunc )
 class Expr_JsonFieldIn_c : public Expr_ArgVsConstSet_c<int64_t>
 {
 public:
-	Expr_JsonFieldIn_c ( ConstList_c * pConsts, UservarIntSet_c * pUservar, ISphExpr * pArg )
-		: Expr_ArgVsConstSet_c<int64_t> ( pArg, pConsts )
-		, m_pUservar ( pUservar )
+	Expr_JsonFieldIn_c ( ConstList_c * pConsts, ISphExpr * pArg )
+		: Expr_ArgVsConstSet_c<int64_t> ( pArg, pConsts, true )
 		, m_pStrings ( NULL )
 	{
-		assert ( !pConsts || !pUservar );
+		assert ( pConsts );
 
 		const char * sExpr = pConsts->m_sExpr.cstr();
 		int iExprLen = pConsts->m_sExpr.Length();
 
-		const int64_t * pFilter = m_pUservar ? m_pUservar->Begin() : m_dValues.Begin();
-		const int64_t * pFilterMax = pFilter + ( m_pUservar ? m_pUservar->GetLength() : m_dValues.GetLength() );
+		const int64_t * pFilter = m_dValues.Begin();
+		const int64_t * pFilterMax = pFilter + m_dValues.GetLength();
 
 		for ( const int64_t * pCur=pFilter; pCur<pFilterMax; pCur++ )
 		{
@@ -5314,16 +5439,19 @@ public:
 			}
 		}
 
-		// consts are handled in Expr_ArgVsConstSet_c, we only need uservars
-		if ( m_pUservar )
-			m_uValueHash = sphFNV64 ( pFilter, (pFilterMax-pFilter)*sizeof(*pFilter) );
+		m_dHashes.Sort();
+	}
 
+	Expr_JsonFieldIn_c ( UservarIntSet_c * pUserVar, ISphExpr * pArg )
+		: Expr_ArgVsConstSet_c<int64_t> ( pArg, pUserVar )
+		, m_pStrings ( NULL )
+	{
+		assert ( pUserVar );
 		m_dHashes.Sort();
 	}
 
 	~Expr_JsonFieldIn_c()
 	{
-		SafeRelease ( m_pUservar );
 	}
 
 	virtual void Command ( ESphExprCommand eCmd, void * pArg )
@@ -5339,14 +5467,28 @@ public:
 	{
 		const BYTE * pVal = NULL;
 		ESphJsonType eJson = GetKey ( &pVal, tMatch );
+		int64_t iVal = 0;
 		switch ( eJson )
 		{
 			case JSON_INT32_VECTOR:		return ArrayEval<int> ( pVal );
 			case JSON_INT64_VECTOR:		return ArrayEval<int64_t> ( pVal );
 			case JSON_STRING_VECTOR:	return StringArrayEval ( pVal, false );
+			case JSON_DOUBLE_VECTOR:	return ArrayFloatEval ( pVal );
 			case JSON_STRING:			return StringArrayEval ( pVal, true );
-			case JSON_INT32:			return ValueEval ( (int64_t) sphJsonLoadInt ( &pVal ) );
-			case JSON_INT64:			return ValueEval ( sphJsonLoadBigint ( &pVal ) );
+			case JSON_INT32:
+			case JSON_INT64:
+				iVal = ( eJson==JSON_INT32 ? sphJsonLoadInt ( &pVal ) : sphJsonLoadBigint ( &pVal ) );
+				if ( m_bFloat )
+					return FloatEval ( (float)iVal );
+				else
+					return ValueEval ( iVal  );
+			case JSON_DOUBLE:
+				iVal = sphJsonLoadBigint ( &pVal );
+				if ( m_bFloat )
+					return FloatEval ( sphQW2D ( iVal ) );
+				else
+					return ValueEval ( iVal  );
+
 			case JSON_MIXED_VECTOR:
 				{
 					const BYTE * p = pVal;
@@ -5359,10 +5501,24 @@ public:
 						int iRes = 0;
 						switch (eType)
 						{
-							case JSON_STRING: iRes =  StringArrayEval ( pVal, true ); break;
-							case JSON_INT32: iRes = ValueEval ( (int64_t) sphJsonLoadInt ( &pVal ) ); break;
-							case JSON_INT64: iRes = ValueEval ( sphJsonLoadBigint ( &pVal ) ); break;
-							case JSON_DOUBLE: iRes = ValueEval ( (int64_t)sphQW2D ( sphJsonLoadBigint ( &pVal ) ) ); break;
+							case JSON_STRING:
+								iRes =  StringArrayEval ( pVal, true );
+							break;
+							case JSON_INT32:
+							case JSON_INT64:
+								iVal = ( eType==JSON_INT32 ? sphJsonLoadInt ( &pVal ) : sphJsonLoadBigint ( &pVal ) );
+								if ( m_bFloat )
+									iRes = FloatEval ( (float)iVal );
+								else
+									iRes = ValueEval ( iVal );
+							break;
+							case JSON_DOUBLE:
+								iVal = sphJsonLoadBigint ( &pVal );
+								if ( m_bFloat )
+									iRes = FloatEval ( sphQW2D ( iVal ) );
+								else
+									iRes = ValueEval ( iVal  );
+								break;
 							default: break; // for weird subobjects, just let IN() return false
 						}
 						if ( iRes )
@@ -5382,7 +5538,6 @@ public:
 	}
 
 protected:
-	UservarIntSet_c *	m_pUservar;
 	const BYTE *		m_pStrings;
 	CSphVector<int64_t>	m_dHashes;
 
@@ -5398,20 +5553,36 @@ protected:
 
 	int ValueEval ( const int64_t iVal ) const
 	{
-		const int64_t * pFilter = m_pUservar ? m_pUservar->Begin() : m_dValues.Begin();
-		const int64_t * pFilterMax = pFilter + ( m_pUservar ? m_pUservar->GetLength() : m_dValues.GetLength() );
+		const int64_t * pFilter = m_dValues.Begin();
+		const int64_t * pFilterMax = pFilter + m_dValues.GetLength();
 		for ( ; pFilter<pFilterMax; pFilter++ )
 			if ( iVal==*pFilter )
 				return 1;
 		return 0;
 	}
 
+	int FloatEval ( const double fVal ) const
+	{
+		assert ( m_bFloat );
+		const int64_t * pFilter = m_dValues.Begin();
+		const int64_t * pFilterMax = pFilter + m_dValues.GetLength();
+		for ( ; pFilter<pFilterMax; pFilter++ )
+		{
+			int64_t iFilterVal = *pFilter;
+			double fFilterVal = sphDW2F ( (DWORD)iFilterVal );
+
+			if ( fabs ( fVal - fFilterVal )<=1e-6 )
+				return 1;
+		}
+		return 0;
+	}
+
 	// cannot apply MvaEval() on unordered JSON arrays, using linear search
 	template <typename T>
 	int ArrayEval ( const BYTE * pVal ) const
 	{
-		const int64_t * pFilter = m_pUservar ? m_pUservar->Begin() : m_dValues.Begin();
-		const int64_t * pFilterMax = pFilter + ( m_pUservar ? m_pUservar->GetLength() : m_dValues.GetLength() );
+		const int64_t * pFilter = m_dValues.Begin();
+		const int64_t * pFilterMax = pFilter + m_dValues.GetLength();
 
 		int iLen = sphJsonUnpackInt ( &pVal );
 		const T * pArray = (const T *)pVal;
@@ -5441,6 +5612,29 @@ protected:
 		}
 		return 0;
 	}
+
+	int ArrayFloatEval ( const BYTE * pVal ) const
+	{
+		const int64_t * pFilter = m_dValues.Begin();
+		const int64_t * pFilterMax = pFilter + m_dValues.GetLength();
+
+		int iLen = sphJsonUnpackInt ( &pVal );
+
+		for ( ; pFilter<pFilterMax; pFilter++ )
+		{
+			int64_t iFilterVal = *pFilter;
+			double fFilterVal = ( m_bFloat ? sphDW2F ( (DWORD)iFilterVal ) : iFilterVal );
+
+			const BYTE * p = pVal;
+			for ( int i=0; i<iLen; i++ )
+			{
+				double fStored = sphQW2D ( sphJsonLoadBigint ( &p ) );
+				if ( fabs ( fStored - fFilterVal )<=1e-6 )
+					return 1;
+			}
+		}
+		return 0;
+	}
 };
 
 
@@ -5454,7 +5648,7 @@ protected:
 
 public:
 	Expr_StrIn_c ( const CSphAttrLocator & tLoc, int iLocator, ConstList_c * pConsts, UservarIntSet_c * pUservar, ESphCollation eCollation )
-		: Expr_ArgVsConstSet_c<int64_t> ( NULL, pConsts )
+		: Expr_ArgVsConstSet_c<int64_t> ( NULL, pConsts, false )
 		, ExprLocatorTraits_t ( tLoc, iLocator )
 		, m_pStrings ( NULL )
 		, m_pUservar ( pUservar )
@@ -5966,7 +6160,7 @@ ISphExpr * ExprParser_t::CreateInNode ( int iNode )
 				case TOK_ATTR_STRING:
 					return new Expr_StrIn_c ( tLeft.m_tLocator, tLeft.m_iLocator, tRight.m_pConsts, NULL, m_eCollation );
 				case TOK_ATTR_JSON:
-					return new Expr_JsonFieldIn_c ( tRight.m_pConsts, NULL, CreateTree ( m_dNodes [ iNode ].m_iLeft ) );
+					return new Expr_JsonFieldIn_c ( tRight.m_pConsts, CreateTree ( m_dNodes [ iNode ].m_iLeft ) );
 				default:
 				{
 					ISphExpr * pArg = CreateTree ( m_dNodes[iNode].m_iLeft );
@@ -6005,7 +6199,7 @@ ISphExpr * ExprParser_t::CreateInNode ( int iNode )
 				case TOK_ATTR_STRING:
 					return new Expr_StrIn_c ( tLeft.m_tLocator, tLeft.m_iLocator, NULL, pUservar, m_eCollation );
 				case TOK_ATTR_JSON:
-					return new Expr_JsonFieldIn_c ( NULL, pUservar, CreateTree ( m_dNodes[iNode].m_iLeft ) );
+					return new Expr_JsonFieldIn_c ( pUservar, CreateTree ( m_dNodes[iNode].m_iLeft ) );
 				default:
 					return new Expr_InUservar_c ( CreateTree ( m_dNodes[iNode].m_iLeft ), pUservar );
 			}

+ 3 - 0
test/test_325/model.bin

@@ -0,0 +1,3 @@
+a:1:{i:0;a:5:{i:0;a:2:{s:8:"sphinxql";s:163:"
+insert into rt values (1, 'test fixed', '{"a":[2.1, 3.5, 7.2]}' ), ( 2, 'test notfixed', '{"a":[2.5, 5.1, 7.25]}' ), ( 3, 'test mixed', '{"a":["3.1", 5, 7.7]}' )
+";s:14:"total_affected";i:3;}i:1;a:3:{s:8:"sphinxql";s:32:"select *, in(j.a, 2.1) c from rt";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:2:"id";s:1:"1";s:1:"j";s:34:"{"a":[2.100000,3.500000,7.200000]}";s:1:"c";s:1:"1";}i:1;a:3:{s:2:"id";s:1:"2";s:1:"j";s:34:"{"a":[2.500000,5.100000,7.250000]}";s:1:"c";s:1:"0";}i:2;a:3:{s:2:"id";s:1:"3";s:1:"j";s:24:"{"a":["3.1",5,7.700000]}";s:1:"c";s:1:"0";}}}i:2;a:3:{s:8:"sphinxql";s:47:"select *, greatest(j.a) g, least(j.a) l from rt";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:4:{s:2:"id";s:1:"1";s:1:"j";s:34:"{"a":[2.100000,3.500000,7.200000]}";s:1:"g";s:8:"7.200000";s:1:"l";s:8:"2.100000";}i:1;a:4:{s:2:"id";s:1:"2";s:1:"j";s:34:"{"a":[2.500000,5.100000,7.250000]}";s:1:"g";s:8:"7.250000";s:1:"l";s:8:"2.500000";}i:2;a:4:{s:2:"id";s:1:"3";s:1:"j";s:24:"{"a":["3.1",5,7.700000]}";s:1:"g";s:8:"7.700000";s:1:"l";s:8:"5.000000";}}}i:3;a:3:{s:8:"sphinxql";s:43:"select *, any(f>7.2 for f in j.a) c from rt";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:2:"id";s:1:"1";s:1:"j";s:34:"{"a":[2.100000,3.500000,7.200000]}";s:1:"c";s:1:"0";}i:1;a:3:{s:2:"id";s:1:"2";s:1:"j";s:34:"{"a":[2.500000,5.100000,7.250000]}";s:1:"c";s:1:"1";}i:2;a:3:{s:2:"id";s:1:"3";s:1:"j";s:24:"{"a":["3.1",5,7.700000]}";s:1:"c";s:1:"1";}}}i:4;a:3:{s:8:"sphinxql";s:48:"select *, in(j.a, 7.7) c1, in(j.a, 5) c2 from rt";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:4:{s:2:"id";s:1:"1";s:1:"j";s:34:"{"a":[2.100000,3.500000,7.200000]}";s:2:"c1";s:1:"0";s:2:"c2";s:1:"0";}i:1;a:4:{s:2:"id";s:1:"2";s:1:"j";s:34:"{"a":[2.500000,5.100000,7.250000]}";s:2:"c1";s:1:"0";s:2:"c2";s:1:"0";}i:2;a:4:{s:2:"id";s:1:"3";s:1:"j";s:24:"{"a":["3.1",5,7.700000]}";s:2:"c1";s:1:"1";s:2:"c2";s:1:"1";}}}}}

+ 48 - 0
test/test_325/test.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<test>
+<name>json array double and mixed</name>
+<config>
+searchd
+{
+	<searchd_settings/>
+	workers					= threads
+}
+
+source test
+{
+	type = mysql
+	<sql_settings/>
+	sql_query = SELECT 1 id, "test" text, 11 idd
+	sql_attr_uint = idd
+}
+
+index test
+{
+	source	= test
+	path	= <data_path/>/test
+}
+
+index rt
+{
+	type              = rt
+	path              = <data_path/>/rt
+	rt_field          = title
+	rt_attr_json      = j
+}
+
+</config>
+
+<sphqueries>
+
+<sphinxql>
+insert into rt values (1, 'test fixed', '{"a":[2.1, 3.5, 7.2]}' ), ( 2, 'test notfixed', '{"a":[2.5, 5.1, 7.25]}' ), ( 3, 'test mixed', '{"a":["3.1", 5, 7.7]}' )
+</sphinxql>
+
+<sphinxql>select *, in(j.a, 2.1) c from rt</sphinxql>
+<sphinxql>select *, greatest(j.a) g, least(j.a) l from rt</sphinxql>
+<sphinxql>select *, any(f&gt;7.2 for f in j.a) c from rt</sphinxql>
+<sphinxql>select *, in(j.a, 7.7) c1, in(j.a, 5) c2 from rt</sphinxql>
+
+</sphqueries>
+
+</test>