1*0Syaroslav@ivinco.com<?php
2*0Syaroslav@ivinco.com
3*0Syaroslav@ivinco.com//
4*0Syaroslav@ivinco.com// $Id: sphinxapi.php 2055 2009-11-06 23:09:58Z shodan $
5*0Syaroslav@ivinco.com//
6*0Syaroslav@ivinco.com
7*0Syaroslav@ivinco.com//
8*0Syaroslav@ivinco.com// Copyright (c) 2001-2008, Andrew Aksyonoff. All rights reserved.
9*0Syaroslav@ivinco.com//
10*0Syaroslav@ivinco.com// This program is free software; you can redistribute it and/or modify
11*0Syaroslav@ivinco.com// it under the terms of the GNU General Public License. You should have
12*0Syaroslav@ivinco.com// received a copy of the GPL license along with this program; if you
13*0Syaroslav@ivinco.com// did not, you can find it at http://www.gnu.org/
14*0Syaroslav@ivinco.com//
15*0Syaroslav@ivinco.com
16*0Syaroslav@ivinco.com/////////////////////////////////////////////////////////////////////////////
17*0Syaroslav@ivinco.com// PHP version of Sphinx searchd client (PHP API)
18*0Syaroslav@ivinco.com/////////////////////////////////////////////////////////////////////////////
19*0Syaroslav@ivinco.com
20*0Syaroslav@ivinco.com/// known searchd commands
21*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_SEARCH",	0 );
22*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_EXCERPT",	1 );
23*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_UPDATE",	2 );
24*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_KEYWORDS",3 );
25*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_PERSIST",	4 );
26*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_STATUS",	5 );
27*0Syaroslav@ivinco.comdefine ( "SEARCHD_COMMAND_QUERY",	6 );
28*0Syaroslav@ivinco.com
29*0Syaroslav@ivinco.com/// current client-side command implementation versions
30*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_SEARCH",		0x116 );
31*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_EXCERPT",		0x100 );
32*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_UPDATE",		0x102 );
33*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_KEYWORDS",	0x100 );
34*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_STATUS",		0x100 );
35*0Syaroslav@ivinco.comdefine ( "VER_COMMAND_QUERY",		0x100 );
36*0Syaroslav@ivinco.com
37*0Syaroslav@ivinco.com/// known searchd status codes
38*0Syaroslav@ivinco.comdefine ( "SEARCHD_OK",				0 );
39*0Syaroslav@ivinco.comdefine ( "SEARCHD_ERROR",			1 );
40*0Syaroslav@ivinco.comdefine ( "SEARCHD_RETRY",			2 );
41*0Syaroslav@ivinco.comdefine ( "SEARCHD_WARNING",			3 );
42*0Syaroslav@ivinco.com
43*0Syaroslav@ivinco.com/// known match modes
44*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_ALL",			0 );
45*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_ANY",			1 );
46*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_PHRASE",		2 );
47*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_BOOLEAN",		3 );
48*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_EXTENDED",		4 );
49*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_FULLSCAN",		5 );
50*0Syaroslav@ivinco.comdefine ( "SPH_MATCH_EXTENDED2",		6 );	// extended engine V2 (TEMPORARY, WILL BE REMOVED)
51*0Syaroslav@ivinco.com
52*0Syaroslav@ivinco.com/// known ranking modes (ext2 only)
53*0Syaroslav@ivinco.comdefine ( "SPH_RANK_PROXIMITY_BM25",	0 );	///< default mode, phrase proximity major factor and BM25 minor one
54*0Syaroslav@ivinco.comdefine ( "SPH_RANK_BM25",			1 );	///< statistical mode, BM25 ranking only (faster but worse quality)
55*0Syaroslav@ivinco.comdefine ( "SPH_RANK_NONE",			2 );	///< no ranking, all matches get a weight of 1
56*0Syaroslav@ivinco.comdefine ( "SPH_RANK_WORDCOUNT",		3 );	///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
57*0Syaroslav@ivinco.comdefine ( "SPH_RANK_PROXIMITY",		4 );
58*0Syaroslav@ivinco.comdefine ( "SPH_RANK_MATCHANY",		5 );
59*0Syaroslav@ivinco.comdefine ( "SPH_RANK_FIELDMASK",		6 );
60*0Syaroslav@ivinco.com
61*0Syaroslav@ivinco.com/// known sort modes
62*0Syaroslav@ivinco.comdefine ( "SPH_SORT_RELEVANCE",		0 );
63*0Syaroslav@ivinco.comdefine ( "SPH_SORT_ATTR_DESC",		1 );
64*0Syaroslav@ivinco.comdefine ( "SPH_SORT_ATTR_ASC",		2 );
65*0Syaroslav@ivinco.comdefine ( "SPH_SORT_TIME_SEGMENTS", 	3 );
66*0Syaroslav@ivinco.comdefine ( "SPH_SORT_EXTENDED", 		4 );
67*0Syaroslav@ivinco.comdefine ( "SPH_SORT_EXPR", 			5 );
68*0Syaroslav@ivinco.com
69*0Syaroslav@ivinco.com/// known filter types
70*0Syaroslav@ivinco.comdefine ( "SPH_FILTER_VALUES",		0 );
71*0Syaroslav@ivinco.comdefine ( "SPH_FILTER_RANGE",		1 );
72*0Syaroslav@ivinco.comdefine ( "SPH_FILTER_FLOATRANGE",	2 );
73*0Syaroslav@ivinco.com
74*0Syaroslav@ivinco.com/// known attribute types
75*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_INTEGER",		1 );
76*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_TIMESTAMP",		2 );
77*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_ORDINAL",		3 );
78*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_BOOL",			4 );
79*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_FLOAT",			5 );
80*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_BIGINT",			6 );
81*0Syaroslav@ivinco.comdefine ( "SPH_ATTR_MULTI",			0x40000000 );
82*0Syaroslav@ivinco.com
83*0Syaroslav@ivinco.com/// known grouping functions
84*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_DAY",			0 );
85*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_WEEK",		1 );
86*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_MONTH",		2 );
87*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_YEAR",		3 );
88*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_ATTR",		4 );
89*0Syaroslav@ivinco.comdefine ( "SPH_GROUPBY_ATTRPAIR",	5 );
90*0Syaroslav@ivinco.com
91*0Syaroslav@ivinco.com// important properties of PHP's integers:
92*0Syaroslav@ivinco.com//  - always signed (one bit short of PHP_INT_SIZE)
93*0Syaroslav@ivinco.com//  - conversion from string to int is saturated
94*0Syaroslav@ivinco.com//  - float is double
95*0Syaroslav@ivinco.com//  - div converts arguments to floats
96*0Syaroslav@ivinco.com//  - mod converts arguments to ints
97*0Syaroslav@ivinco.com
98*0Syaroslav@ivinco.com// the packing code below works as follows:
99*0Syaroslav@ivinco.com//  - when we got an int, just pack it
100*0Syaroslav@ivinco.com//    if performance is a problem, this is the branch users should aim for
101*0Syaroslav@ivinco.com//
102*0Syaroslav@ivinco.com//  - otherwise, we got a number in string form
103*0Syaroslav@ivinco.com//    this might be due to different reasons, but we assume that this is
104*0Syaroslav@ivinco.com//    because it didn't fit into PHP int
105*0Syaroslav@ivinco.com//
106*0Syaroslav@ivinco.com//  - factor the string into high and low ints for packing
107*0Syaroslav@ivinco.com//    - if we have bcmath, then it is used
108*0Syaroslav@ivinco.com//    - if we don't, we have to do it manually (this is the fun part)
109*0Syaroslav@ivinco.com//
110*0Syaroslav@ivinco.com//    - x64 branch does factoring using ints
111*0Syaroslav@ivinco.com//    - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
112*0Syaroslav@ivinco.com//
113*0Syaroslav@ivinco.com// unpacking routines are pretty much the same.
114*0Syaroslav@ivinco.com//  - return ints if we can
115*0Syaroslav@ivinco.com//  - otherwise format number into a string
116*0Syaroslav@ivinco.com
117*0Syaroslav@ivinco.com/// pack 64-bit signed
118*0Syaroslav@ivinco.comfunction sphPackI64 ( $v )
119*0Syaroslav@ivinco.com{
120*0Syaroslav@ivinco.com	assert ( is_numeric($v) );
121*0Syaroslav@ivinco.com
122*0Syaroslav@ivinco.com	// x64
123*0Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
124*0Syaroslav@ivinco.com	{
125*0Syaroslav@ivinco.com		$v = (int)$v;
126*0Syaroslav@ivinco.com		return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
127*0Syaroslav@ivinco.com	}
128*0Syaroslav@ivinco.com
129*0Syaroslav@ivinco.com	// x32, int
130*0Syaroslav@ivinco.com	if ( is_int($v) )
131*0Syaroslav@ivinco.com		return pack ( "NN", $v < 0 ? -1 : 0, $v );
132*0Syaroslav@ivinco.com
133*0Syaroslav@ivinco.com	// x32, bcmath
134*0Syaroslav@ivinco.com	if ( function_exists("bcmul") )
135*0Syaroslav@ivinco.com	{
136*0Syaroslav@ivinco.com		if ( bccomp ( $v, 0 ) == -1 )
137*0Syaroslav@ivinco.com			$v = bcadd ( "18446744073709551616", $v );
138*0Syaroslav@ivinco.com		$h = bcdiv ( $v, "4294967296", 0 );
139*0Syaroslav@ivinco.com		$l = bcmod ( $v, "4294967296" );
140*0Syaroslav@ivinco.com		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
141*0Syaroslav@ivinco.com	}
142*0Syaroslav@ivinco.com
143*0Syaroslav@ivinco.com	// x32, no-bcmath
144*0Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
145*0Syaroslav@ivinco.com	$lo = abs((float)substr($v, $p));
146*0Syaroslav@ivinco.com	$hi = abs((float)substr($v, 0, $p));
147*0Syaroslav@ivinco.com
148*0Syaroslav@ivinco.com	$m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
149*0Syaroslav@ivinco.com	$q = floor($m/4294967296.0);
150*0Syaroslav@ivinco.com	$l = $m - ($q*4294967296.0);
151*0Syaroslav@ivinco.com	$h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
152*0Syaroslav@ivinco.com
153*0Syaroslav@ivinco.com	if ( $v<0 )
154*0Syaroslav@ivinco.com	{
155*0Syaroslav@ivinco.com		if ( $l==0 )
156*0Syaroslav@ivinco.com			$h = 4294967296.0 - $h;
157*0Syaroslav@ivinco.com		else
158*0Syaroslav@ivinco.com		{
159*0Syaroslav@ivinco.com			$h = 4294967295.0 - $h;
160*0Syaroslav@ivinco.com			$l = 4294967296.0 - $l;
161*0Syaroslav@ivinco.com		}
162*0Syaroslav@ivinco.com	}
163*0Syaroslav@ivinco.com	return pack ( "NN", $h, $l );
164*0Syaroslav@ivinco.com}
165*0Syaroslav@ivinco.com
166*0Syaroslav@ivinco.com/// pack 64-bit unsigned
167*0Syaroslav@ivinco.comfunction sphPackU64 ( $v )
168*0Syaroslav@ivinco.com{
169*0Syaroslav@ivinco.com	assert ( is_numeric($v) );
170*0Syaroslav@ivinco.com
171*0Syaroslav@ivinco.com	// x64
172*0Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
173*0Syaroslav@ivinco.com	{
174*0Syaroslav@ivinco.com		assert ( $v>=0 );
175*0Syaroslav@ivinco.com
176*0Syaroslav@ivinco.com		// x64, int
177*0Syaroslav@ivinco.com		if ( is_int($v) )
178*0Syaroslav@ivinco.com			return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
179*0Syaroslav@ivinco.com
180*0Syaroslav@ivinco.com		// x64, bcmath
181*0Syaroslav@ivinco.com		if ( function_exists("bcmul") )
182*0Syaroslav@ivinco.com		{
183*0Syaroslav@ivinco.com			$h = bcdiv ( $v, 4294967296, 0 );
184*0Syaroslav@ivinco.com			$l = bcmod ( $v, 4294967296 );
185*0Syaroslav@ivinco.com			return pack ( "NN", $h, $l );
186*0Syaroslav@ivinco.com		}
187*0Syaroslav@ivinco.com
188*0Syaroslav@ivinco.com		// x64, no-bcmath
189*0Syaroslav@ivinco.com		$p = max ( 0, strlen($v) - 13 );
190*0Syaroslav@ivinco.com		$lo = (int)substr ( $v, $p );
191*0Syaroslav@ivinco.com		$hi = (int)substr ( $v, 0, $p );
192*0Syaroslav@ivinco.com
193*0Syaroslav@ivinco.com		$m = $lo + $hi*1316134912;
194*0Syaroslav@ivinco.com		$l = $m % 4294967296;
195*0Syaroslav@ivinco.com		$h = $hi*2328 + (int)($m/4294967296);
196*0Syaroslav@ivinco.com
197*0Syaroslav@ivinco.com		return pack ( "NN", $h, $l );
198*0Syaroslav@ivinco.com	}
199*0Syaroslav@ivinco.com
200*0Syaroslav@ivinco.com	// x32, int
201*0Syaroslav@ivinco.com	if ( is_int($v) )
202*0Syaroslav@ivinco.com		return pack ( "NN", 0, $v );
203*0Syaroslav@ivinco.com
204*0Syaroslav@ivinco.com	// x32, bcmath
205*0Syaroslav@ivinco.com	if ( function_exists("bcmul") )
206*0Syaroslav@ivinco.com	{
207*0Syaroslav@ivinco.com		$h = bcdiv ( $v, "4294967296", 0 );
208*0Syaroslav@ivinco.com		$l = bcmod ( $v, "4294967296" );
209*0Syaroslav@ivinco.com		return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
210*0Syaroslav@ivinco.com	}
211*0Syaroslav@ivinco.com
212*0Syaroslav@ivinco.com	// x32, no-bcmath
213*0Syaroslav@ivinco.com	$p = max(0, strlen($v) - 13);
214*0Syaroslav@ivinco.com	$lo = (float)substr($v, $p);
215*0Syaroslav@ivinco.com	$hi = (float)substr($v, 0, $p);
216*0Syaroslav@ivinco.com
217*0Syaroslav@ivinco.com	$m = $lo + $hi*1316134912.0;
218*0Syaroslav@ivinco.com	$q = floor($m / 4294967296.0);
219*0Syaroslav@ivinco.com	$l = $m - ($q * 4294967296.0);
220*0Syaroslav@ivinco.com	$h = $hi*2328.0 + $q;
221*0Syaroslav@ivinco.com
222*0Syaroslav@ivinco.com	return pack ( "NN", $h, $l );
223*0Syaroslav@ivinco.com}
224*0Syaroslav@ivinco.com
225*0Syaroslav@ivinco.com// unpack 64-bit unsigned
226*0Syaroslav@ivinco.comfunction sphUnpackU64 ( $v )
227*0Syaroslav@ivinco.com{
228*0Syaroslav@ivinco.com	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
229*0Syaroslav@ivinco.com
230*0Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
231*0Syaroslav@ivinco.com	{
232*0Syaroslav@ivinco.com		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
233*0Syaroslav@ivinco.com		if ( $lo<0 ) $lo += (1<<32);
234*0Syaroslav@ivinco.com
235*0Syaroslav@ivinco.com		// x64, int
236*0Syaroslav@ivinco.com		if ( $hi<=2147483647 )
237*0Syaroslav@ivinco.com			return ($hi<<32) + $lo;
238*0Syaroslav@ivinco.com
239*0Syaroslav@ivinco.com		// x64, bcmath
240*0Syaroslav@ivinco.com		if ( function_exists("bcmul") )
241*0Syaroslav@ivinco.com			return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
242*0Syaroslav@ivinco.com
243*0Syaroslav@ivinco.com		// x64, no-bcmath
244*0Syaroslav@ivinco.com		$C = 100000;
245*0Syaroslav@ivinco.com		$h = ((int)($hi / $C) << 32) + (int)($lo / $C);
246*0Syaroslav@ivinco.com		$l = (($hi % $C) << 32) + ($lo % $C);
247*0Syaroslav@ivinco.com		if ( $l>$C )
248*0Syaroslav@ivinco.com		{
249*0Syaroslav@ivinco.com			$h += (int)($l / $C);
250*0Syaroslav@ivinco.com			$l  = $l % $C;
251*0Syaroslav@ivinco.com		}
252*0Syaroslav@ivinco.com
253*0Syaroslav@ivinco.com		if ( $h==0 )
254*0Syaroslav@ivinco.com			return $l;
255*0Syaroslav@ivinco.com		return sprintf ( "%d%05d", $h, $l );
256*0Syaroslav@ivinco.com	}
257*0Syaroslav@ivinco.com
258*0Syaroslav@ivinco.com	// x32, int
259*0Syaroslav@ivinco.com	if ( $hi==0 )
260*0Syaroslav@ivinco.com	{
261*0Syaroslav@ivinco.com		if ( $lo>0 )
262*0Syaroslav@ivinco.com			return $lo;
263*0Syaroslav@ivinco.com		return sprintf ( "%u", $lo );
264*0Syaroslav@ivinco.com	}
265*0Syaroslav@ivinco.com
266*0Syaroslav@ivinco.com	$hi = sprintf ( "%u", $hi );
267*0Syaroslav@ivinco.com	$lo = sprintf ( "%u", $lo );
268*0Syaroslav@ivinco.com
269*0Syaroslav@ivinco.com	// x32, bcmath
270*0Syaroslav@ivinco.com	if ( function_exists("bcmul") )
271*0Syaroslav@ivinco.com		return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
272*0Syaroslav@ivinco.com
273*0Syaroslav@ivinco.com	// x32, no-bcmath
274*0Syaroslav@ivinco.com	$hi = (float)$hi;
275*0Syaroslav@ivinco.com	$lo = (float)$lo;
276*0Syaroslav@ivinco.com
277*0Syaroslav@ivinco.com	$q = floor($hi/10000000.0);
278*0Syaroslav@ivinco.com	$r = $hi - $q*10000000.0;
279*0Syaroslav@ivinco.com	$m = $lo + $r*4967296.0;
280*0Syaroslav@ivinco.com	$mq = floor($m/10000000.0);
281*0Syaroslav@ivinco.com	$l = $m - $mq*10000000.0;
282*0Syaroslav@ivinco.com	$h = $q*4294967296.0 + $r*429.0 + $mq;
283*0Syaroslav@ivinco.com
284*0Syaroslav@ivinco.com	$h = sprintf ( "%.0f", $h );
285*0Syaroslav@ivinco.com	$l = sprintf ( "%07.0f", $l );
286*0Syaroslav@ivinco.com	if ( $h=="0" )
287*0Syaroslav@ivinco.com		return sprintf( "%.0f", (float)$l );
288*0Syaroslav@ivinco.com	return $h . $l;
289*0Syaroslav@ivinco.com}
290*0Syaroslav@ivinco.com
291*0Syaroslav@ivinco.com// unpack 64-bit signed
292*0Syaroslav@ivinco.comfunction sphUnpackI64 ( $v )
293*0Syaroslav@ivinco.com{
294*0Syaroslav@ivinco.com	list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
295*0Syaroslav@ivinco.com
296*0Syaroslav@ivinco.com	// x64
297*0Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
298*0Syaroslav@ivinco.com	{
299*0Syaroslav@ivinco.com		if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
300*0Syaroslav@ivinco.com		if ( $lo<0 ) $lo += (1<<32);
301*0Syaroslav@ivinco.com
302*0Syaroslav@ivinco.com		return ($hi<<32) + $lo;
303*0Syaroslav@ivinco.com	}
304*0Syaroslav@ivinco.com
305*0Syaroslav@ivinco.com	// x32, int
306*0Syaroslav@ivinco.com	if ( $hi==0 )
307*0Syaroslav@ivinco.com	{
308*0Syaroslav@ivinco.com		if ( $lo>0 )
309*0Syaroslav@ivinco.com			return $lo;
310*0Syaroslav@ivinco.com		return sprintf ( "%u", $lo );
311*0Syaroslav@ivinco.com	}
312*0Syaroslav@ivinco.com	// x32, int
313*0Syaroslav@ivinco.com	elseif ( $hi==-1 )
314*0Syaroslav@ivinco.com	{
315*0Syaroslav@ivinco.com		if ( $lo<0 )
316*0Syaroslav@ivinco.com			return $lo;
317*0Syaroslav@ivinco.com		return sprintf ( "%.0f", $lo - 4294967296.0 );
318*0Syaroslav@ivinco.com	}
319*0Syaroslav@ivinco.com
320*0Syaroslav@ivinco.com	$neg = "";
321*0Syaroslav@ivinco.com	$c = 0;
322*0Syaroslav@ivinco.com	if ( $hi<0 )
323*0Syaroslav@ivinco.com	{
324*0Syaroslav@ivinco.com		$hi = ~$hi;
325*0Syaroslav@ivinco.com		$lo = ~$lo;
326*0Syaroslav@ivinco.com		$c = 1;
327*0Syaroslav@ivinco.com		$neg = "-";
328*0Syaroslav@ivinco.com	}
329*0Syaroslav@ivinco.com
330*0Syaroslav@ivinco.com	$hi = sprintf ( "%u", $hi );
331*0Syaroslav@ivinco.com	$lo = sprintf ( "%u", $lo );
332*0Syaroslav@ivinco.com
333*0Syaroslav@ivinco.com	// x32, bcmath
334*0Syaroslav@ivinco.com	if ( function_exists("bcmul") )
335*0Syaroslav@ivinco.com		return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
336*0Syaroslav@ivinco.com
337*0Syaroslav@ivinco.com	// x32, no-bcmath
338*0Syaroslav@ivinco.com	$hi = (float)$hi;
339*0Syaroslav@ivinco.com	$lo = (float)$lo;
340*0Syaroslav@ivinco.com
341*0Syaroslav@ivinco.com	$q = floor($hi/10000000.0);
342*0Syaroslav@ivinco.com	$r = $hi - $q*10000000.0;
343*0Syaroslav@ivinco.com	$m = $lo + $r*4967296.0;
344*0Syaroslav@ivinco.com	$mq = floor($m/10000000.0);
345*0Syaroslav@ivinco.com	$l = $m - $mq*10000000.0 + $c;
346*0Syaroslav@ivinco.com	$h = $q*4294967296.0 + $r*429.0 + $mq;
347*0Syaroslav@ivinco.com	if ( $l==10000000 )
348*0Syaroslav@ivinco.com	{
349*0Syaroslav@ivinco.com		$l = 0;
350*0Syaroslav@ivinco.com		$h += 1;
351*0Syaroslav@ivinco.com	}
352*0Syaroslav@ivinco.com
353*0Syaroslav@ivinco.com	$h = sprintf ( "%.0f", $h );
354*0Syaroslav@ivinco.com	$l = sprintf ( "%07.0f", $l );
355*0Syaroslav@ivinco.com	if ( $h=="0" )
356*0Syaroslav@ivinco.com		return $neg . sprintf( "%.0f", (float)$l );
357*0Syaroslav@ivinco.com	return $neg . $h . $l;
358*0Syaroslav@ivinco.com}
359*0Syaroslav@ivinco.com
360*0Syaroslav@ivinco.com
361*0Syaroslav@ivinco.comfunction sphFixUint ( $value )
362*0Syaroslav@ivinco.com{
363*0Syaroslav@ivinco.com	if ( PHP_INT_SIZE>=8 )
364*0Syaroslav@ivinco.com	{
365*0Syaroslav@ivinco.com		// x64 route, workaround broken unpack() in 5.2.2+
366*0Syaroslav@ivinco.com		if ( $value<0 ) $value += (1<<32);
367*0Syaroslav@ivinco.com		return $value;
368*0Syaroslav@ivinco.com	}
369*0Syaroslav@ivinco.com	else
370*0Syaroslav@ivinco.com	{
371*0Syaroslav@ivinco.com		// x32 route, workaround php signed/unsigned braindamage
372*0Syaroslav@ivinco.com		return sprintf ( "%u", $value );
373*0Syaroslav@ivinco.com	}
374*0Syaroslav@ivinco.com}
375*0Syaroslav@ivinco.com
376*0Syaroslav@ivinco.com
377*0Syaroslav@ivinco.com/// sphinx searchd client class
378*0Syaroslav@ivinco.comclass SphinxClient
379*0Syaroslav@ivinco.com{
380*0Syaroslav@ivinco.com	var $_host;			///< searchd host (default is "localhost")
381*0Syaroslav@ivinco.com	var $_port;			///< searchd port (default is 9312)
382*0Syaroslav@ivinco.com	var $_offset;		///< how many records to seek from result-set start (default is 0)
383*0Syaroslav@ivinco.com	var $_limit;		///< how many records to return from result-set starting at offset (default is 20)
384*0Syaroslav@ivinco.com	var $_mode;			///< query matching mode (default is SPH_MATCH_ALL)
385*0Syaroslav@ivinco.com	var $_weights;		///< per-field weights (default is 1 for all fields)
386*0Syaroslav@ivinco.com	var $_sort;			///< match sorting mode (default is SPH_SORT_RELEVANCE)
387*0Syaroslav@ivinco.com	var $_sortby;		///< attribute to sort by (defualt is "")
388*0Syaroslav@ivinco.com	var $_min_id;		///< min ID to match (default is 0, which means no limit)
389*0Syaroslav@ivinco.com	var $_max_id;		///< max ID to match (default is 0, which means no limit)
390*0Syaroslav@ivinco.com	var $_filters;		///< search filters
391*0Syaroslav@ivinco.com	var $_groupby;		///< group-by attribute name
392*0Syaroslav@ivinco.com	var $_groupfunc;	///< group-by function (to pre-process group-by attribute value with)
393*0Syaroslav@ivinco.com	var $_groupsort;	///< group-by sorting clause (to sort groups in result set with)
394*0Syaroslav@ivinco.com	var $_groupdistinct;///< group-by count-distinct attribute
395*0Syaroslav@ivinco.com	var $_maxmatches;	///< max matches to retrieve
396*0Syaroslav@ivinco.com	var $_cutoff;		///< cutoff to stop searching at (default is 0)
397*0Syaroslav@ivinco.com	var $_retrycount;	///< distributed retries count
398*0Syaroslav@ivinco.com	var $_retrydelay;	///< distributed retries delay
399*0Syaroslav@ivinco.com	var $_anchor;		///< geographical anchor point
400*0Syaroslav@ivinco.com	var $_indexweights;	///< per-index weights
401*0Syaroslav@ivinco.com	var $_ranker;		///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
402*0Syaroslav@ivinco.com	var $_maxquerytime;	///< max query time, milliseconds (default is 0, do not limit)
403*0Syaroslav@ivinco.com	var $_fieldweights;	///< per-field-name weights
404*0Syaroslav@ivinco.com	var $_overrides;	///< per-query attribute values overrides
405*0Syaroslav@ivinco.com	var $_select;		///< select-list (attributes or expressions, with optional aliases)
406*0Syaroslav@ivinco.com
407*0Syaroslav@ivinco.com	var $_error;		///< last error message
408*0Syaroslav@ivinco.com	var $_warning;		///< last warning message
409*0Syaroslav@ivinco.com	var $_connerror;		///< connection error vs remote error flag
410*0Syaroslav@ivinco.com
411*0Syaroslav@ivinco.com	var $_reqs;			///< requests array for multi-query
412*0Syaroslav@ivinco.com	var $_mbenc;		///< stored mbstring encoding
413*0Syaroslav@ivinco.com	var $_arrayresult;	///< whether $result["matches"] should be a hash or an array
414*0Syaroslav@ivinco.com	var $_timeout;		///< connect timeout
415*0Syaroslav@ivinco.com
416*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
417*0Syaroslav@ivinco.com	// common stuff
418*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
419*0Syaroslav@ivinco.com
420*0Syaroslav@ivinco.com	/// create a new client object and fill defaults
421*0Syaroslav@ivinco.com	function SphinxClient ()
422*0Syaroslav@ivinco.com	{
423*0Syaroslav@ivinco.com		// per-client-object settings
424*0Syaroslav@ivinco.com		$this->_host		= "localhost";
425*0Syaroslav@ivinco.com		$this->_port		= 9312;
426*0Syaroslav@ivinco.com		$this->_path		= false;
427*0Syaroslav@ivinco.com		$this->_socket		= false;
428*0Syaroslav@ivinco.com
429*0Syaroslav@ivinco.com		// per-query settings
430*0Syaroslav@ivinco.com		$this->_offset		= 0;
431*0Syaroslav@ivinco.com		$this->_limit		= 20;
432*0Syaroslav@ivinco.com		$this->_mode		= SPH_MATCH_ALL;
433*0Syaroslav@ivinco.com		$this->_weights		= array ();
434*0Syaroslav@ivinco.com		$this->_sort		= SPH_SORT_RELEVANCE;
435*0Syaroslav@ivinco.com		$this->_sortby		= "";
436*0Syaroslav@ivinco.com		$this->_min_id		= 0;
437*0Syaroslav@ivinco.com		$this->_max_id		= 0;
438*0Syaroslav@ivinco.com		$this->_filters		= array ();
439*0Syaroslav@ivinco.com		$this->_groupby		= "";
440*0Syaroslav@ivinco.com		$this->_groupfunc	= SPH_GROUPBY_DAY;
441*0Syaroslav@ivinco.com		$this->_groupsort	= "@group desc";
442*0Syaroslav@ivinco.com		$this->_groupdistinct= "";
443*0Syaroslav@ivinco.com		$this->_maxmatches	= 1000;
444*0Syaroslav@ivinco.com		$this->_cutoff		= 0;
445*0Syaroslav@ivinco.com		$this->_retrycount	= 0;
446*0Syaroslav@ivinco.com		$this->_retrydelay	= 0;
447*0Syaroslav@ivinco.com		$this->_anchor		= array ();
448*0Syaroslav@ivinco.com		$this->_indexweights= array ();
449*0Syaroslav@ivinco.com		$this->_ranker		= SPH_RANK_PROXIMITY_BM25;
450*0Syaroslav@ivinco.com		$this->_maxquerytime= 0;
451*0Syaroslav@ivinco.com		$this->_fieldweights= array();
452*0Syaroslav@ivinco.com		$this->_overrides 	= array();
453*0Syaroslav@ivinco.com		$this->_select		= "*";
454*0Syaroslav@ivinco.com
455*0Syaroslav@ivinco.com		$this->_error		= ""; // per-reply fields (for single-query case)
456*0Syaroslav@ivinco.com		$this->_warning		= "";
457*0Syaroslav@ivinco.com		$this->_connerror	= false;
458*0Syaroslav@ivinco.com
459*0Syaroslav@ivinco.com		$this->_reqs		= array ();	// requests storage (for multi-query case)
460*0Syaroslav@ivinco.com		$this->_mbenc		= "";
461*0Syaroslav@ivinco.com		$this->_arrayresult	= false;
462*0Syaroslav@ivinco.com		$this->_timeout		= 0;
463*0Syaroslav@ivinco.com	}
464*0Syaroslav@ivinco.com
465*0Syaroslav@ivinco.com	function __destruct()
466*0Syaroslav@ivinco.com	{
467*0Syaroslav@ivinco.com		if ( $this->_socket !== false )
468*0Syaroslav@ivinco.com			fclose ( $this->_socket );
469*0Syaroslav@ivinco.com	}
470*0Syaroslav@ivinco.com
471*0Syaroslav@ivinco.com	/// get last error message (string)
472*0Syaroslav@ivinco.com	function GetLastError ()
473*0Syaroslav@ivinco.com	{
474*0Syaroslav@ivinco.com		return $this->_error;
475*0Syaroslav@ivinco.com	}
476*0Syaroslav@ivinco.com
477*0Syaroslav@ivinco.com	/// get last warning message (string)
478*0Syaroslav@ivinco.com	function GetLastWarning ()
479*0Syaroslav@ivinco.com	{
480*0Syaroslav@ivinco.com		return $this->_warning;
481*0Syaroslav@ivinco.com	}
482*0Syaroslav@ivinco.com
483*0Syaroslav@ivinco.com	/// get last error flag (to tell network connection errors from searchd errors or broken responses)
484*0Syaroslav@ivinco.com	function IsConnectError()
485*0Syaroslav@ivinco.com	{
486*0Syaroslav@ivinco.com		return $this->_connerror;
487*0Syaroslav@ivinco.com	}
488*0Syaroslav@ivinco.com
489*0Syaroslav@ivinco.com	/// set searchd host name (string) and port (integer)
490*0Syaroslav@ivinco.com	function SetServer ( $host, $port = 0 )
491*0Syaroslav@ivinco.com	{
492*0Syaroslav@ivinco.com		assert ( is_string($host) );
493*0Syaroslav@ivinco.com		if ( $host[0] == '/')
494*0Syaroslav@ivinco.com		{
495*0Syaroslav@ivinco.com			$this->_path = 'unix://' . $host;
496*0Syaroslav@ivinco.com			return;
497*0Syaroslav@ivinco.com		}
498*0Syaroslav@ivinco.com		if ( substr ( $host, 0, 7 )=="unix://" )
499*0Syaroslav@ivinco.com		{
500*0Syaroslav@ivinco.com			$this->_path = $host;
501*0Syaroslav@ivinco.com			return;
502*0Syaroslav@ivinco.com		}
503*0Syaroslav@ivinco.com
504*0Syaroslav@ivinco.com		assert ( is_int($port) );
505*0Syaroslav@ivinco.com		$this->_host = $host;
506*0Syaroslav@ivinco.com		$this->_port = $port;
507*0Syaroslav@ivinco.com		$this->_path = '';
508*0Syaroslav@ivinco.com
509*0Syaroslav@ivinco.com	}
510*0Syaroslav@ivinco.com
511*0Syaroslav@ivinco.com	/// set server connection timeout (0 to remove)
512*0Syaroslav@ivinco.com	function SetConnectTimeout ( $timeout )
513*0Syaroslav@ivinco.com	{
514*0Syaroslav@ivinco.com		assert ( is_numeric($timeout) );
515*0Syaroslav@ivinco.com		$this->_timeout = $timeout;
516*0Syaroslav@ivinco.com	}
517*0Syaroslav@ivinco.com
518*0Syaroslav@ivinco.com
519*0Syaroslav@ivinco.com	function _Send ( $handle, $data, $length )
520*0Syaroslav@ivinco.com	{
521*0Syaroslav@ivinco.com		if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
522*0Syaroslav@ivinco.com		{
523*0Syaroslav@ivinco.com			$this->_error = 'connection unexpectedly closed (timed out?)';
524*0Syaroslav@ivinco.com			$this->_connerror = true;
525*0Syaroslav@ivinco.com			return false;
526*0Syaroslav@ivinco.com		}
527*0Syaroslav@ivinco.com		return true;
528*0Syaroslav@ivinco.com	}
529*0Syaroslav@ivinco.com
530*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
531*0Syaroslav@ivinco.com
532*0Syaroslav@ivinco.com	/// enter mbstring workaround mode
533*0Syaroslav@ivinco.com	function _MBPush ()
534*0Syaroslav@ivinco.com	{
535*0Syaroslav@ivinco.com		$this->_mbenc = "";
536*0Syaroslav@ivinco.com		if ( ini_get ( "mbstring.func_overload" ) & 2 )
537*0Syaroslav@ivinco.com		{
538*0Syaroslav@ivinco.com			$this->_mbenc = mb_internal_encoding();
539*0Syaroslav@ivinco.com			mb_internal_encoding ( "latin1" );
540*0Syaroslav@ivinco.com		}
541*0Syaroslav@ivinco.com    }
542*0Syaroslav@ivinco.com
543*0Syaroslav@ivinco.com	/// leave mbstring workaround mode
544*0Syaroslav@ivinco.com	function _MBPop ()
545*0Syaroslav@ivinco.com	{
546*0Syaroslav@ivinco.com		if ( $this->_mbenc )
547*0Syaroslav@ivinco.com			mb_internal_encoding ( $this->_mbenc );
548*0Syaroslav@ivinco.com	}
549*0Syaroslav@ivinco.com
550*0Syaroslav@ivinco.com	/// connect to searchd server
551*0Syaroslav@ivinco.com	function _Connect ()
552*0Syaroslav@ivinco.com	{
553*0Syaroslav@ivinco.com		if ( $this->_socket!==false )
554*0Syaroslav@ivinco.com		{
555*0Syaroslav@ivinco.com			// we are in persistent connection mode, so we have a socket
556*0Syaroslav@ivinco.com			// however, need to check whether it's still alive
557*0Syaroslav@ivinco.com			if ( !@feof ( $this->_socket ) )
558*0Syaroslav@ivinco.com				return $this->_socket;
559*0Syaroslav@ivinco.com
560*0Syaroslav@ivinco.com			// force reopen
561*0Syaroslav@ivinco.com			$this->_socket = false;
562*0Syaroslav@ivinco.com		}
563*0Syaroslav@ivinco.com
564*0Syaroslav@ivinco.com		$errno = 0;
565*0Syaroslav@ivinco.com		$errstr = "";
566*0Syaroslav@ivinco.com		$this->_connerror = false;
567*0Syaroslav@ivinco.com
568*0Syaroslav@ivinco.com		if ( $this->_path )
569*0Syaroslav@ivinco.com		{
570*0Syaroslav@ivinco.com			$host = $this->_path;
571*0Syaroslav@ivinco.com			$port = 0;
572*0Syaroslav@ivinco.com		}
573*0Syaroslav@ivinco.com		else
574*0Syaroslav@ivinco.com		{
575*0Syaroslav@ivinco.com			$host = $this->_host;
576*0Syaroslav@ivinco.com			$port = $this->_port;
577*0Syaroslav@ivinco.com		}
578*0Syaroslav@ivinco.com
579*0Syaroslav@ivinco.com		if ( $this->_timeout<=0 )
580*0Syaroslav@ivinco.com			$fp = @fsockopen ( $host, $port, $errno, $errstr );
581*0Syaroslav@ivinco.com		else
582*0Syaroslav@ivinco.com			$fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
583*0Syaroslav@ivinco.com
584*0Syaroslav@ivinco.com		if ( !$fp )
585*0Syaroslav@ivinco.com		{
586*0Syaroslav@ivinco.com			if ( $this->_path )
587*0Syaroslav@ivinco.com				$location = $this->_path;
588*0Syaroslav@ivinco.com			else
589*0Syaroslav@ivinco.com				$location = "{$this->_host}:{$this->_port}";
590*0Syaroslav@ivinco.com
591*0Syaroslav@ivinco.com			$errstr = trim ( $errstr );
592*0Syaroslav@ivinco.com			$this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
593*0Syaroslav@ivinco.com			$this->_connerror = true;
594*0Syaroslav@ivinco.com			return false;
595*0Syaroslav@ivinco.com		}
596*0Syaroslav@ivinco.com
597*0Syaroslav@ivinco.com		// send my version
598*0Syaroslav@ivinco.com		// this is a subtle part. we must do it before (!) reading back from searchd.
599*0Syaroslav@ivinco.com		// because otherwise under some conditions (reported on FreeBSD for instance)
600*0Syaroslav@ivinco.com		// TCP stack could throttle write-write-read pattern because of Nagle.
601*0Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
602*0Syaroslav@ivinco.com		{
603*0Syaroslav@ivinco.com			fclose ( $fp );
604*0Syaroslav@ivinco.com			$this->_error = "failed to send client protocol version";
605*0Syaroslav@ivinco.com			return false;
606*0Syaroslav@ivinco.com		}
607*0Syaroslav@ivinco.com
608*0Syaroslav@ivinco.com		// check version
609*0Syaroslav@ivinco.com		list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
610*0Syaroslav@ivinco.com		$v = (int)$v;
611*0Syaroslav@ivinco.com		if ( $v<1 )
612*0Syaroslav@ivinco.com		{
613*0Syaroslav@ivinco.com			fclose ( $fp );
614*0Syaroslav@ivinco.com			$this->_error = "expected searchd protocol version 1+, got version '$v'";
615*0Syaroslav@ivinco.com			return false;
616*0Syaroslav@ivinco.com		}
617*0Syaroslav@ivinco.com
618*0Syaroslav@ivinco.com		return $fp;
619*0Syaroslav@ivinco.com	}
620*0Syaroslav@ivinco.com
621*0Syaroslav@ivinco.com	/// get and check response packet from searchd server
622*0Syaroslav@ivinco.com	function _GetResponse ( $fp, $client_ver )
623*0Syaroslav@ivinco.com	{
624*0Syaroslav@ivinco.com		$response = "";
625*0Syaroslav@ivinco.com		$len = 0;
626*0Syaroslav@ivinco.com
627*0Syaroslav@ivinco.com		$header = fread ( $fp, 8 );
628*0Syaroslav@ivinco.com		if ( strlen($header)==8 )
629*0Syaroslav@ivinco.com		{
630*0Syaroslav@ivinco.com			list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
631*0Syaroslav@ivinco.com			$left = $len;
632*0Syaroslav@ivinco.com			while ( $left>0 && !feof($fp) )
633*0Syaroslav@ivinco.com			{
634*0Syaroslav@ivinco.com				$chunk = fread ( $fp, $left );
635*0Syaroslav@ivinco.com				if ( $chunk )
636*0Syaroslav@ivinco.com				{
637*0Syaroslav@ivinco.com					$response .= $chunk;
638*0Syaroslav@ivinco.com					$left -= strlen($chunk);
639*0Syaroslav@ivinco.com				}
640*0Syaroslav@ivinco.com			}
641*0Syaroslav@ivinco.com		}
642*0Syaroslav@ivinco.com		if ( $this->_socket === false )
643*0Syaroslav@ivinco.com			fclose ( $fp );
644*0Syaroslav@ivinco.com
645*0Syaroslav@ivinco.com		// check response
646*0Syaroslav@ivinco.com		$read = strlen ( $response );
647*0Syaroslav@ivinco.com		if ( !$response || $read!=$len )
648*0Syaroslav@ivinco.com		{
649*0Syaroslav@ivinco.com			$this->_error = $len
650*0Syaroslav@ivinco.com				? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
651*0Syaroslav@ivinco.com				: "received zero-sized searchd response";
652*0Syaroslav@ivinco.com			return false;
653*0Syaroslav@ivinco.com		}
654*0Syaroslav@ivinco.com
655*0Syaroslav@ivinco.com		// check status
656*0Syaroslav@ivinco.com		if ( $status==SEARCHD_WARNING )
657*0Syaroslav@ivinco.com		{
658*0Syaroslav@ivinco.com			list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
659*0Syaroslav@ivinco.com			$this->_warning = substr ( $response, 4, $wlen );
660*0Syaroslav@ivinco.com			return substr ( $response, 4+$wlen );
661*0Syaroslav@ivinco.com		}
662*0Syaroslav@ivinco.com		if ( $status==SEARCHD_ERROR )
663*0Syaroslav@ivinco.com		{
664*0Syaroslav@ivinco.com			$this->_error = "searchd error: " . substr ( $response, 4 );
665*0Syaroslav@ivinco.com			return false;
666*0Syaroslav@ivinco.com		}
667*0Syaroslav@ivinco.com		if ( $status==SEARCHD_RETRY )
668*0Syaroslav@ivinco.com		{
669*0Syaroslav@ivinco.com			$this->_error = "temporary searchd error: " . substr ( $response, 4 );
670*0Syaroslav@ivinco.com			return false;
671*0Syaroslav@ivinco.com		}
672*0Syaroslav@ivinco.com		if ( $status!=SEARCHD_OK )
673*0Syaroslav@ivinco.com		{
674*0Syaroslav@ivinco.com			$this->_error = "unknown status code '$status'";
675*0Syaroslav@ivinco.com			return false;
676*0Syaroslav@ivinco.com		}
677*0Syaroslav@ivinco.com
678*0Syaroslav@ivinco.com		// check version
679*0Syaroslav@ivinco.com		if ( $ver<$client_ver )
680*0Syaroslav@ivinco.com		{
681*0Syaroslav@ivinco.com			$this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
682*0Syaroslav@ivinco.com				$ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
683*0Syaroslav@ivinco.com		}
684*0Syaroslav@ivinco.com
685*0Syaroslav@ivinco.com		return $response;
686*0Syaroslav@ivinco.com	}
687*0Syaroslav@ivinco.com
688*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
689*0Syaroslav@ivinco.com	// searching
690*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
691*0Syaroslav@ivinco.com
692*0Syaroslav@ivinco.com	/// set offset and count into result set,
693*0Syaroslav@ivinco.com	/// and optionally set max-matches and cutoff limits
694*0Syaroslav@ivinco.com	function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
695*0Syaroslav@ivinco.com	{
696*0Syaroslav@ivinco.com		assert ( is_int($offset) );
697*0Syaroslav@ivinco.com		assert ( is_int($limit) );
698*0Syaroslav@ivinco.com		assert ( $offset>=0 );
699*0Syaroslav@ivinco.com		assert ( $limit>0 );
700*0Syaroslav@ivinco.com		assert ( $max>=0 );
701*0Syaroslav@ivinco.com		$this->_offset = $offset;
702*0Syaroslav@ivinco.com		$this->_limit = $limit;
703*0Syaroslav@ivinco.com		if ( $max>0 )
704*0Syaroslav@ivinco.com			$this->_maxmatches = $max;
705*0Syaroslav@ivinco.com		if ( $cutoff>0 )
706*0Syaroslav@ivinco.com			$this->_cutoff = $cutoff;
707*0Syaroslav@ivinco.com	}
708*0Syaroslav@ivinco.com
709*0Syaroslav@ivinco.com	/// set maximum query time, in milliseconds, per-index
710*0Syaroslav@ivinco.com	/// integer, 0 means "do not limit"
711*0Syaroslav@ivinco.com	function SetMaxQueryTime ( $max )
712*0Syaroslav@ivinco.com	{
713*0Syaroslav@ivinco.com		assert ( is_int($max) );
714*0Syaroslav@ivinco.com		assert ( $max>=0 );
715*0Syaroslav@ivinco.com		$this->_maxquerytime = $max;
716*0Syaroslav@ivinco.com	}
717*0Syaroslav@ivinco.com
718*0Syaroslav@ivinco.com	/// set matching mode
719*0Syaroslav@ivinco.com	function SetMatchMode ( $mode )
720*0Syaroslav@ivinco.com	{
721*0Syaroslav@ivinco.com		assert ( $mode==SPH_MATCH_ALL
722*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_ANY
723*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_PHRASE
724*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_BOOLEAN
725*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_EXTENDED
726*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_FULLSCAN
727*0Syaroslav@ivinco.com			|| $mode==SPH_MATCH_EXTENDED2 );
728*0Syaroslav@ivinco.com		$this->_mode = $mode;
729*0Syaroslav@ivinco.com	}
730*0Syaroslav@ivinco.com
731*0Syaroslav@ivinco.com	/// set ranking mode
732*0Syaroslav@ivinco.com	function SetRankingMode ( $ranker )
733*0Syaroslav@ivinco.com	{
734*0Syaroslav@ivinco.com		assert ( $ranker==SPH_RANK_PROXIMITY_BM25
735*0Syaroslav@ivinco.com			|| $ranker==SPH_RANK_BM25
736*0Syaroslav@ivinco.com			|| $ranker==SPH_RANK_NONE
737*0Syaroslav@ivinco.com			|| $ranker==SPH_RANK_WORDCOUNT
738*0Syaroslav@ivinco.com			|| $ranker==SPH_RANK_PROXIMITY );
739*0Syaroslav@ivinco.com		$this->_ranker = $ranker;
740*0Syaroslav@ivinco.com	}
741*0Syaroslav@ivinco.com
742*0Syaroslav@ivinco.com	/// set matches sorting mode
743*0Syaroslav@ivinco.com	function SetSortMode ( $mode, $sortby="" )
744*0Syaroslav@ivinco.com	{
745*0Syaroslav@ivinco.com		assert (
746*0Syaroslav@ivinco.com			$mode==SPH_SORT_RELEVANCE ||
747*0Syaroslav@ivinco.com			$mode==SPH_SORT_ATTR_DESC ||
748*0Syaroslav@ivinco.com			$mode==SPH_SORT_ATTR_ASC ||
749*0Syaroslav@ivinco.com			$mode==SPH_SORT_TIME_SEGMENTS ||
750*0Syaroslav@ivinco.com			$mode==SPH_SORT_EXTENDED ||
751*0Syaroslav@ivinco.com			$mode==SPH_SORT_EXPR );
752*0Syaroslav@ivinco.com		assert ( is_string($sortby) );
753*0Syaroslav@ivinco.com		assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
754*0Syaroslav@ivinco.com
755*0Syaroslav@ivinco.com		$this->_sort = $mode;
756*0Syaroslav@ivinco.com		$this->_sortby = $sortby;
757*0Syaroslav@ivinco.com	}
758*0Syaroslav@ivinco.com
759*0Syaroslav@ivinco.com	/// bind per-field weights by order
760*0Syaroslav@ivinco.com	/// DEPRECATED; use SetFieldWeights() instead
761*0Syaroslav@ivinco.com	function SetWeights ( $weights )
762*0Syaroslav@ivinco.com	{
763*0Syaroslav@ivinco.com		assert ( is_array($weights) );
764*0Syaroslav@ivinco.com		foreach ( $weights as $weight )
765*0Syaroslav@ivinco.com			assert ( is_int($weight) );
766*0Syaroslav@ivinco.com
767*0Syaroslav@ivinco.com		$this->_weights = $weights;
768*0Syaroslav@ivinco.com	}
769*0Syaroslav@ivinco.com
770*0Syaroslav@ivinco.com	/// bind per-field weights by name
771*0Syaroslav@ivinco.com	function SetFieldWeights ( $weights )
772*0Syaroslav@ivinco.com	{
773*0Syaroslav@ivinco.com		assert ( is_array($weights) );
774*0Syaroslav@ivinco.com		foreach ( $weights as $name=>$weight )
775*0Syaroslav@ivinco.com		{
776*0Syaroslav@ivinco.com			assert ( is_string($name) );
777*0Syaroslav@ivinco.com			assert ( is_int($weight) );
778*0Syaroslav@ivinco.com		}
779*0Syaroslav@ivinco.com		$this->_fieldweights = $weights;
780*0Syaroslav@ivinco.com	}
781*0Syaroslav@ivinco.com
782*0Syaroslav@ivinco.com	/// bind per-index weights by name
783*0Syaroslav@ivinco.com	function SetIndexWeights ( $weights )
784*0Syaroslav@ivinco.com	{
785*0Syaroslav@ivinco.com		assert ( is_array($weights) );
786*0Syaroslav@ivinco.com		foreach ( $weights as $index=>$weight )
787*0Syaroslav@ivinco.com		{
788*0Syaroslav@ivinco.com			assert ( is_string($index) );
789*0Syaroslav@ivinco.com			assert ( is_int($weight) );
790*0Syaroslav@ivinco.com		}
791*0Syaroslav@ivinco.com		$this->_indexweights = $weights;
792*0Syaroslav@ivinco.com	}
793*0Syaroslav@ivinco.com
794*0Syaroslav@ivinco.com	/// set IDs range to match
795*0Syaroslav@ivinco.com	/// only match records if document ID is beetwen $min and $max (inclusive)
796*0Syaroslav@ivinco.com	function SetIDRange ( $min, $max )
797*0Syaroslav@ivinco.com	{
798*0Syaroslav@ivinco.com		assert ( is_numeric($min) );
799*0Syaroslav@ivinco.com		assert ( is_numeric($max) );
800*0Syaroslav@ivinco.com		assert ( $min<=$max );
801*0Syaroslav@ivinco.com		$this->_min_id = $min;
802*0Syaroslav@ivinco.com		$this->_max_id = $max;
803*0Syaroslav@ivinco.com	}
804*0Syaroslav@ivinco.com
805*0Syaroslav@ivinco.com	/// set values set filter
806*0Syaroslav@ivinco.com	/// only match records where $attribute value is in given set
807*0Syaroslav@ivinco.com	function SetFilter ( $attribute, $values, $exclude=false )
808*0Syaroslav@ivinco.com	{
809*0Syaroslav@ivinco.com		assert ( is_string($attribute) );
810*0Syaroslav@ivinco.com		assert ( is_array($values) );
811*0Syaroslav@ivinco.com		assert ( count($values) );
812*0Syaroslav@ivinco.com
813*0Syaroslav@ivinco.com		if ( is_array($values) && count($values) )
814*0Syaroslav@ivinco.com		{
815*0Syaroslav@ivinco.com			foreach ( $values as $value )
816*0Syaroslav@ivinco.com				assert ( is_numeric($value) );
817*0Syaroslav@ivinco.com
818*0Syaroslav@ivinco.com			$this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
819*0Syaroslav@ivinco.com		}
820*0Syaroslav@ivinco.com	}
821*0Syaroslav@ivinco.com
822*0Syaroslav@ivinco.com	/// set range filter
823*0Syaroslav@ivinco.com	/// only match records if $attribute value is beetwen $min and $max (inclusive)
824*0Syaroslav@ivinco.com	function SetFilterRange ( $attribute, $min, $max, $exclude=false )
825*0Syaroslav@ivinco.com	{
826*0Syaroslav@ivinco.com		assert ( is_string($attribute) );
827*0Syaroslav@ivinco.com		assert ( is_numeric($min) );
828*0Syaroslav@ivinco.com		assert ( is_numeric($max) );
829*0Syaroslav@ivinco.com		assert ( $min<=$max );
830*0Syaroslav@ivinco.com
831*0Syaroslav@ivinco.com		$this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
832*0Syaroslav@ivinco.com	}
833*0Syaroslav@ivinco.com
834*0Syaroslav@ivinco.com	/// set float range filter
835*0Syaroslav@ivinco.com	/// only match records if $attribute value is beetwen $min and $max (inclusive)
836*0Syaroslav@ivinco.com	function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
837*0Syaroslav@ivinco.com	{
838*0Syaroslav@ivinco.com		assert ( is_string($attribute) );
839*0Syaroslav@ivinco.com		assert ( is_float($min) );
840*0Syaroslav@ivinco.com		assert ( is_float($max) );
841*0Syaroslav@ivinco.com		assert ( $min<=$max );
842*0Syaroslav@ivinco.com
843*0Syaroslav@ivinco.com		$this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
844*0Syaroslav@ivinco.com	}
845*0Syaroslav@ivinco.com
846*0Syaroslav@ivinco.com	/// setup anchor point for geosphere distance calculations
847*0Syaroslav@ivinco.com	/// required to use @geodist in filters and sorting
848*0Syaroslav@ivinco.com	/// latitude and longitude must be in radians
849*0Syaroslav@ivinco.com	function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
850*0Syaroslav@ivinco.com	{
851*0Syaroslav@ivinco.com		assert ( is_string($attrlat) );
852*0Syaroslav@ivinco.com		assert ( is_string($attrlong) );
853*0Syaroslav@ivinco.com		assert ( is_float($lat) );
854*0Syaroslav@ivinco.com		assert ( is_float($long) );
855*0Syaroslav@ivinco.com
856*0Syaroslav@ivinco.com		$this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
857*0Syaroslav@ivinco.com	}
858*0Syaroslav@ivinco.com
859*0Syaroslav@ivinco.com	/// set grouping attribute and function
860*0Syaroslav@ivinco.com	function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
861*0Syaroslav@ivinco.com	{
862*0Syaroslav@ivinco.com		assert ( is_string($attribute) );
863*0Syaroslav@ivinco.com		assert ( is_string($groupsort) );
864*0Syaroslav@ivinco.com		assert ( $func==SPH_GROUPBY_DAY
865*0Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_WEEK
866*0Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_MONTH
867*0Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_YEAR
868*0Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_ATTR
869*0Syaroslav@ivinco.com			|| $func==SPH_GROUPBY_ATTRPAIR );
870*0Syaroslav@ivinco.com
871*0Syaroslav@ivinco.com		$this->_groupby = $attribute;
872*0Syaroslav@ivinco.com		$this->_groupfunc = $func;
873*0Syaroslav@ivinco.com		$this->_groupsort = $groupsort;
874*0Syaroslav@ivinco.com	}
875*0Syaroslav@ivinco.com
876*0Syaroslav@ivinco.com	/// set count-distinct attribute for group-by queries
877*0Syaroslav@ivinco.com	function SetGroupDistinct ( $attribute )
878*0Syaroslav@ivinco.com	{
879*0Syaroslav@ivinco.com		assert ( is_string($attribute) );
880*0Syaroslav@ivinco.com		$this->_groupdistinct = $attribute;
881*0Syaroslav@ivinco.com	}
882*0Syaroslav@ivinco.com
883*0Syaroslav@ivinco.com	/// set distributed retries count and delay
884*0Syaroslav@ivinco.com	function SetRetries ( $count, $delay=0 )
885*0Syaroslav@ivinco.com	{
886*0Syaroslav@ivinco.com		assert ( is_int($count) && $count>=0 );
887*0Syaroslav@ivinco.com		assert ( is_int($delay) && $delay>=0 );
888*0Syaroslav@ivinco.com		$this->_retrycount = $count;
889*0Syaroslav@ivinco.com		$this->_retrydelay = $delay;
890*0Syaroslav@ivinco.com	}
891*0Syaroslav@ivinco.com
892*0Syaroslav@ivinco.com	/// set result set format (hash or array; hash by default)
893*0Syaroslav@ivinco.com	/// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
894*0Syaroslav@ivinco.com	function SetArrayResult ( $arrayresult )
895*0Syaroslav@ivinco.com	{
896*0Syaroslav@ivinco.com		assert ( is_bool($arrayresult) );
897*0Syaroslav@ivinco.com		$this->_arrayresult = $arrayresult;
898*0Syaroslav@ivinco.com	}
899*0Syaroslav@ivinco.com
900*0Syaroslav@ivinco.com	/// set attribute values override
901*0Syaroslav@ivinco.com	/// there can be only one override per attribute
902*0Syaroslav@ivinco.com	/// $values must be a hash that maps document IDs to attribute values
903*0Syaroslav@ivinco.com	function SetOverride ( $attrname, $attrtype, $values )
904*0Syaroslav@ivinco.com	{
905*0Syaroslav@ivinco.com		assert ( is_string ( $attrname ) );
906*0Syaroslav@ivinco.com		assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
907*0Syaroslav@ivinco.com		assert ( is_array ( $values ) );
908*0Syaroslav@ivinco.com
909*0Syaroslav@ivinco.com		$this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
910*0Syaroslav@ivinco.com	}
911*0Syaroslav@ivinco.com
912*0Syaroslav@ivinco.com	/// set select-list (attributes or expressions), SQL-like syntax
913*0Syaroslav@ivinco.com	function SetSelect ( $select )
914*0Syaroslav@ivinco.com	{
915*0Syaroslav@ivinco.com		assert ( is_string ( $select ) );
916*0Syaroslav@ivinco.com		$this->_select = $select;
917*0Syaroslav@ivinco.com	}
918*0Syaroslav@ivinco.com
919*0Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////////
920*0Syaroslav@ivinco.com
921*0Syaroslav@ivinco.com	/// clear all filters (for multi-queries)
922*0Syaroslav@ivinco.com	function ResetFilters ()
923*0Syaroslav@ivinco.com	{
924*0Syaroslav@ivinco.com		$this->_filters = array();
925*0Syaroslav@ivinco.com		$this->_anchor = array();
926*0Syaroslav@ivinco.com	}
927*0Syaroslav@ivinco.com
928*0Syaroslav@ivinco.com	/// clear groupby settings (for multi-queries)
929*0Syaroslav@ivinco.com	function ResetGroupBy ()
930*0Syaroslav@ivinco.com	{
931*0Syaroslav@ivinco.com		$this->_groupby		= "";
932*0Syaroslav@ivinco.com		$this->_groupfunc	= SPH_GROUPBY_DAY;
933*0Syaroslav@ivinco.com		$this->_groupsort	= "@group desc";
934*0Syaroslav@ivinco.com		$this->_groupdistinct= "";
935*0Syaroslav@ivinco.com	}
936*0Syaroslav@ivinco.com
937*0Syaroslav@ivinco.com	/// clear all attribute value overrides (for multi-queries)
938*0Syaroslav@ivinco.com	function ResetOverrides ()
939*0Syaroslav@ivinco.com    {
940*0Syaroslav@ivinco.com    	$this->_overrides = array ();
941*0Syaroslav@ivinco.com    }
942*0Syaroslav@ivinco.com
943*0Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////////
944*0Syaroslav@ivinco.com
945*0Syaroslav@ivinco.com	/// connect to searchd server, run given search query through given indexes,
946*0Syaroslav@ivinco.com	/// and return the search results
947*0Syaroslav@ivinco.com	function Query ( $query, $index="*", $comment="" )
948*0Syaroslav@ivinco.com	{
949*0Syaroslav@ivinco.com		assert ( empty($this->_reqs) );
950*0Syaroslav@ivinco.com
951*0Syaroslav@ivinco.com		$this->AddQuery ( $query, $index, $comment );
952*0Syaroslav@ivinco.com		$results = $this->RunQueries ();
953*0Syaroslav@ivinco.com		$this->_reqs = array (); // just in case it failed too early
954*0Syaroslav@ivinco.com
955*0Syaroslav@ivinco.com		if ( !is_array($results) )
956*0Syaroslav@ivinco.com			return false; // probably network error; error message should be already filled
957*0Syaroslav@ivinco.com
958*0Syaroslav@ivinco.com		$this->_error = $results[0]["error"];
959*0Syaroslav@ivinco.com		$this->_warning = $results[0]["warning"];
960*0Syaroslav@ivinco.com		if ( $results[0]["status"]==SEARCHD_ERROR )
961*0Syaroslav@ivinco.com			return false;
962*0Syaroslav@ivinco.com		else
963*0Syaroslav@ivinco.com			return $results[0];
964*0Syaroslav@ivinco.com	}
965*0Syaroslav@ivinco.com
966*0Syaroslav@ivinco.com	/// helper to pack floats in network byte order
967*0Syaroslav@ivinco.com	function _PackFloat ( $f )
968*0Syaroslav@ivinco.com	{
969*0Syaroslav@ivinco.com		$t1 = pack ( "f", $f ); // machine order
970*0Syaroslav@ivinco.com		list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
971*0Syaroslav@ivinco.com		return pack ( "N", $t2 );
972*0Syaroslav@ivinco.com	}
973*0Syaroslav@ivinco.com
974*0Syaroslav@ivinco.com	/// add query to multi-query batch
975*0Syaroslav@ivinco.com	/// returns index into results array from RunQueries() call
976*0Syaroslav@ivinco.com	function AddQuery ( $query, $index="*", $comment="" )
977*0Syaroslav@ivinco.com	{
978*0Syaroslav@ivinco.com		// mbstring workaround
979*0Syaroslav@ivinco.com		$this->_MBPush ();
980*0Syaroslav@ivinco.com
981*0Syaroslav@ivinco.com		// build request
982*0Syaroslav@ivinco.com		$req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits
983*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
984*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($query) ) . $query; // query itself
985*0Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_weights) ); // weights
986*0Syaroslav@ivinco.com		foreach ( $this->_weights as $weight )
987*0Syaroslav@ivinco.com			$req .= pack ( "N", (int)$weight );
988*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // indexes
989*0Syaroslav@ivinco.com		$req .= pack ( "N", 1 ); // id64 range marker
990*0Syaroslav@ivinco.com		$req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
991*0Syaroslav@ivinco.com
992*0Syaroslav@ivinco.com		// filters
993*0Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_filters) );
994*0Syaroslav@ivinco.com		foreach ( $this->_filters as $filter )
995*0Syaroslav@ivinco.com		{
996*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
997*0Syaroslav@ivinco.com			$req .= pack ( "N", $filter["type"] );
998*0Syaroslav@ivinco.com			switch ( $filter["type"] )
999*0Syaroslav@ivinco.com			{
1000*0Syaroslav@ivinco.com				case SPH_FILTER_VALUES:
1001*0Syaroslav@ivinco.com					$req .= pack ( "N", count($filter["values"]) );
1002*0Syaroslav@ivinco.com					foreach ( $filter["values"] as $value )
1003*0Syaroslav@ivinco.com						$req .= sphPackI64 ( $value );
1004*0Syaroslav@ivinco.com					break;
1005*0Syaroslav@ivinco.com
1006*0Syaroslav@ivinco.com				case SPH_FILTER_RANGE:
1007*0Syaroslav@ivinco.com					$req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
1008*0Syaroslav@ivinco.com					break;
1009*0Syaroslav@ivinco.com
1010*0Syaroslav@ivinco.com				case SPH_FILTER_FLOATRANGE:
1011*0Syaroslav@ivinco.com					$req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
1012*0Syaroslav@ivinco.com					break;
1013*0Syaroslav@ivinco.com
1014*0Syaroslav@ivinco.com				default:
1015*0Syaroslav@ivinco.com					assert ( 0 && "internal error: unhandled filter type" );
1016*0Syaroslav@ivinco.com			}
1017*0Syaroslav@ivinco.com			$req .= pack ( "N", $filter["exclude"] );
1018*0Syaroslav@ivinco.com		}
1019*0Syaroslav@ivinco.com
1020*0Syaroslav@ivinco.com		// group-by clause, max-matches count, group-sort clause, cutoff count
1021*0Syaroslav@ivinco.com		$req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
1022*0Syaroslav@ivinco.com		$req .= pack ( "N", $this->_maxmatches );
1023*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
1024*0Syaroslav@ivinco.com		$req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
1025*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
1026*0Syaroslav@ivinco.com
1027*0Syaroslav@ivinco.com		// anchor point
1028*0Syaroslav@ivinco.com		if ( empty($this->_anchor) )
1029*0Syaroslav@ivinco.com		{
1030*0Syaroslav@ivinco.com			$req .= pack ( "N", 0 );
1031*0Syaroslav@ivinco.com		} else
1032*0Syaroslav@ivinco.com		{
1033*0Syaroslav@ivinco.com			$a =& $this->_anchor;
1034*0Syaroslav@ivinco.com			$req .= pack ( "N", 1 );
1035*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
1036*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
1037*0Syaroslav@ivinco.com			$req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
1038*0Syaroslav@ivinco.com		}
1039*0Syaroslav@ivinco.com
1040*0Syaroslav@ivinco.com		// per-index weights
1041*0Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_indexweights) );
1042*0Syaroslav@ivinco.com		foreach ( $this->_indexweights as $idx=>$weight )
1043*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
1044*0Syaroslav@ivinco.com
1045*0Syaroslav@ivinco.com		// max query time
1046*0Syaroslav@ivinco.com		$req .= pack ( "N", $this->_maxquerytime );
1047*0Syaroslav@ivinco.com
1048*0Syaroslav@ivinco.com		// per-field weights
1049*0Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_fieldweights) );
1050*0Syaroslav@ivinco.com		foreach ( $this->_fieldweights as $field=>$weight )
1051*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
1052*0Syaroslav@ivinco.com
1053*0Syaroslav@ivinco.com		// comment
1054*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($comment) ) . $comment;
1055*0Syaroslav@ivinco.com
1056*0Syaroslav@ivinco.com		// attribute overrides
1057*0Syaroslav@ivinco.com		$req .= pack ( "N", count($this->_overrides) );
1058*0Syaroslav@ivinco.com		foreach ( $this->_overrides as $key => $entry )
1059*0Syaroslav@ivinco.com		{
1060*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
1061*0Syaroslav@ivinco.com			$req .= pack ( "NN", $entry["type"], count($entry["values"]) );
1062*0Syaroslav@ivinco.com			foreach ( $entry["values"] as $id=>$val )
1063*0Syaroslav@ivinco.com			{
1064*0Syaroslav@ivinco.com				assert ( is_numeric($id) );
1065*0Syaroslav@ivinco.com				assert ( is_numeric($val) );
1066*0Syaroslav@ivinco.com
1067*0Syaroslav@ivinco.com				$req .= sphPackU64 ( $id );
1068*0Syaroslav@ivinco.com				switch ( $entry["type"] )
1069*0Syaroslav@ivinco.com				{
1070*0Syaroslav@ivinco.com					case SPH_ATTR_FLOAT:	$req .= $this->_PackFloat ( $val ); break;
1071*0Syaroslav@ivinco.com					case SPH_ATTR_BIGINT:	$req .= sphPackI64 ( $val ); break;
1072*0Syaroslav@ivinco.com					default:				$req .= pack ( "N", $val ); break;
1073*0Syaroslav@ivinco.com				}
1074*0Syaroslav@ivinco.com			}
1075*0Syaroslav@ivinco.com		}
1076*0Syaroslav@ivinco.com
1077*0Syaroslav@ivinco.com		// select-list
1078*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($this->_select) ) . $this->_select;
1079*0Syaroslav@ivinco.com
1080*0Syaroslav@ivinco.com		// mbstring workaround
1081*0Syaroslav@ivinco.com		$this->_MBPop ();
1082*0Syaroslav@ivinco.com
1083*0Syaroslav@ivinco.com		// store request to requests array
1084*0Syaroslav@ivinco.com		$this->_reqs[] = $req;
1085*0Syaroslav@ivinco.com		return count($this->_reqs)-1;
1086*0Syaroslav@ivinco.com	}
1087*0Syaroslav@ivinco.com
1088*0Syaroslav@ivinco.com	/// connect to searchd, run queries batch, and return an array of result sets
1089*0Syaroslav@ivinco.com	function RunQueries ()
1090*0Syaroslav@ivinco.com	{
1091*0Syaroslav@ivinco.com		if ( empty($this->_reqs) )
1092*0Syaroslav@ivinco.com		{
1093*0Syaroslav@ivinco.com			$this->_error = "no queries defined, issue AddQuery() first";
1094*0Syaroslav@ivinco.com			return false;
1095*0Syaroslav@ivinco.com		}
1096*0Syaroslav@ivinco.com
1097*0Syaroslav@ivinco.com		// mbstring workaround
1098*0Syaroslav@ivinco.com		$this->_MBPush ();
1099*0Syaroslav@ivinco.com
1100*0Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
1101*0Syaroslav@ivinco.com		{
1102*0Syaroslav@ivinco.com			$this->_MBPop ();
1103*0Syaroslav@ivinco.com			return false;
1104*0Syaroslav@ivinco.com		}
1105*0Syaroslav@ivinco.com
1106*0Syaroslav@ivinco.com		// send query, get response
1107*0Syaroslav@ivinco.com		$nreqs = count($this->_reqs);
1108*0Syaroslav@ivinco.com		$req = join ( "", $this->_reqs );
1109*0Syaroslav@ivinco.com		$len = 4+strlen($req);
1110*0Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header
1111*0Syaroslav@ivinco.com
1112*0Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1113*0Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
1114*0Syaroslav@ivinco.com		{
1115*0Syaroslav@ivinco.com			$this->_MBPop ();
1116*0Syaroslav@ivinco.com			return false;
1117*0Syaroslav@ivinco.com		}
1118*0Syaroslav@ivinco.com
1119*0Syaroslav@ivinco.com		// query sent ok; we can reset reqs now
1120*0Syaroslav@ivinco.com		$this->_reqs = array ();
1121*0Syaroslav@ivinco.com
1122*0Syaroslav@ivinco.com		// parse and return response
1123*0Syaroslav@ivinco.com		return $this->_ParseSearchResponse ( $response, $nreqs );
1124*0Syaroslav@ivinco.com	}
1125*0Syaroslav@ivinco.com
1126*0Syaroslav@ivinco.com	/// parse and return search query (or queries) response
1127*0Syaroslav@ivinco.com	function _ParseSearchResponse ( $response, $nreqs )
1128*0Syaroslav@ivinco.com	{
1129*0Syaroslav@ivinco.com		$p = 0; // current position
1130*0Syaroslav@ivinco.com		$max = strlen($response); // max position for checks, to protect against broken responses
1131*0Syaroslav@ivinco.com
1132*0Syaroslav@ivinco.com		$results = array ();
1133*0Syaroslav@ivinco.com		for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
1134*0Syaroslav@ivinco.com		{
1135*0Syaroslav@ivinco.com			$results[] = array();
1136*0Syaroslav@ivinco.com			$result =& $results[$ires];
1137*0Syaroslav@ivinco.com
1138*0Syaroslav@ivinco.com			$result["error"] = "";
1139*0Syaroslav@ivinco.com			$result["warning"] = "";
1140*0Syaroslav@ivinco.com
1141*0Syaroslav@ivinco.com			// extract status
1142*0Syaroslav@ivinco.com			list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1143*0Syaroslav@ivinco.com			$result["status"] = $status;
1144*0Syaroslav@ivinco.com			if ( $status!=SEARCHD_OK )
1145*0Syaroslav@ivinco.com			{
1146*0Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1147*0Syaroslav@ivinco.com				$message = substr ( $response, $p, $len ); $p += $len;
1148*0Syaroslav@ivinco.com
1149*0Syaroslav@ivinco.com				if ( $status==SEARCHD_WARNING )
1150*0Syaroslav@ivinco.com				{
1151*0Syaroslav@ivinco.com					$result["warning"] = $message;
1152*0Syaroslav@ivinco.com				} else
1153*0Syaroslav@ivinco.com				{
1154*0Syaroslav@ivinco.com					$result["error"] = $message;
1155*0Syaroslav@ivinco.com					continue;
1156*0Syaroslav@ivinco.com				}
1157*0Syaroslav@ivinco.com			}
1158*0Syaroslav@ivinco.com
1159*0Syaroslav@ivinco.com			// read schema
1160*0Syaroslav@ivinco.com			$fields = array ();
1161*0Syaroslav@ivinco.com			$attrs = array ();
1162*0Syaroslav@ivinco.com
1163*0Syaroslav@ivinco.com			list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1164*0Syaroslav@ivinco.com			while ( $nfields-->0 && $p<$max )
1165*0Syaroslav@ivinco.com			{
1166*0Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1167*0Syaroslav@ivinco.com				$fields[] = substr ( $response, $p, $len ); $p += $len;
1168*0Syaroslav@ivinco.com			}
1169*0Syaroslav@ivinco.com			$result["fields"] = $fields;
1170*0Syaroslav@ivinco.com
1171*0Syaroslav@ivinco.com			list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1172*0Syaroslav@ivinco.com			while ( $nattrs-->0 && $p<$max  )
1173*0Syaroslav@ivinco.com			{
1174*0Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1175*0Syaroslav@ivinco.com				$attr = substr ( $response, $p, $len ); $p += $len;
1176*0Syaroslav@ivinco.com				list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1177*0Syaroslav@ivinco.com				$attrs[$attr] = $type;
1178*0Syaroslav@ivinco.com			}
1179*0Syaroslav@ivinco.com			$result["attrs"] = $attrs;
1180*0Syaroslav@ivinco.com
1181*0Syaroslav@ivinco.com			// read match count
1182*0Syaroslav@ivinco.com			list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1183*0Syaroslav@ivinco.com			list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1184*0Syaroslav@ivinco.com
1185*0Syaroslav@ivinco.com			// read matches
1186*0Syaroslav@ivinco.com			$idx = -1;
1187*0Syaroslav@ivinco.com			while ( $count-->0 && $p<$max )
1188*0Syaroslav@ivinco.com			{
1189*0Syaroslav@ivinco.com				// index into result array
1190*0Syaroslav@ivinco.com				$idx++;
1191*0Syaroslav@ivinco.com
1192*0Syaroslav@ivinco.com				// parse document id and weight
1193*0Syaroslav@ivinco.com				if ( $id64 )
1194*0Syaroslav@ivinco.com				{
1195*0Syaroslav@ivinco.com					$doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
1196*0Syaroslav@ivinco.com					list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1197*0Syaroslav@ivinco.com				}
1198*0Syaroslav@ivinco.com				else
1199*0Syaroslav@ivinco.com				{
1200*0Syaroslav@ivinco.com					list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
1201*0Syaroslav@ivinco.com						substr ( $response, $p, 8 ) ) );
1202*0Syaroslav@ivinco.com					$p += 8;
1203*0Syaroslav@ivinco.com					$doc = sphFixUint($doc);
1204*0Syaroslav@ivinco.com				}
1205*0Syaroslav@ivinco.com				$weight = sprintf ( "%u", $weight );
1206*0Syaroslav@ivinco.com
1207*0Syaroslav@ivinco.com				// create match entry
1208*0Syaroslav@ivinco.com				if ( $this->_arrayresult )
1209*0Syaroslav@ivinco.com					$result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
1210*0Syaroslav@ivinco.com				else
1211*0Syaroslav@ivinco.com					$result["matches"][$doc]["weight"] = $weight;
1212*0Syaroslav@ivinco.com
1213*0Syaroslav@ivinco.com				// parse and create attributes
1214*0Syaroslav@ivinco.com				$attrvals = array ();
1215*0Syaroslav@ivinco.com				foreach ( $attrs as $attr=>$type )
1216*0Syaroslav@ivinco.com				{
1217*0Syaroslav@ivinco.com					// handle 64bit ints
1218*0Syaroslav@ivinco.com					if ( $type==SPH_ATTR_BIGINT )
1219*0Syaroslav@ivinco.com					{
1220*0Syaroslav@ivinco.com						$attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
1221*0Syaroslav@ivinco.com						continue;
1222*0Syaroslav@ivinco.com					}
1223*0Syaroslav@ivinco.com
1224*0Syaroslav@ivinco.com					// handle floats
1225*0Syaroslav@ivinco.com					if ( $type==SPH_ATTR_FLOAT )
1226*0Syaroslav@ivinco.com					{
1227*0Syaroslav@ivinco.com						list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1228*0Syaroslav@ivinco.com						list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
1229*0Syaroslav@ivinco.com						$attrvals[$attr] = $fval;
1230*0Syaroslav@ivinco.com						continue;
1231*0Syaroslav@ivinco.com					}
1232*0Syaroslav@ivinco.com
1233*0Syaroslav@ivinco.com					// handle everything else as unsigned ints
1234*0Syaroslav@ivinco.com					list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1235*0Syaroslav@ivinco.com					if ( $type & SPH_ATTR_MULTI )
1236*0Syaroslav@ivinco.com					{
1237*0Syaroslav@ivinco.com						$attrvals[$attr] = array ();
1238*0Syaroslav@ivinco.com						$nvalues = $val;
1239*0Syaroslav@ivinco.com						while ( $nvalues-->0 && $p<$max )
1240*0Syaroslav@ivinco.com						{
1241*0Syaroslav@ivinco.com							list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1242*0Syaroslav@ivinco.com							$attrvals[$attr][] = sphFixUint($val);
1243*0Syaroslav@ivinco.com						}
1244*0Syaroslav@ivinco.com					} else
1245*0Syaroslav@ivinco.com					{
1246*0Syaroslav@ivinco.com						$attrvals[$attr] = sphFixUint($val);
1247*0Syaroslav@ivinco.com					}
1248*0Syaroslav@ivinco.com				}
1249*0Syaroslav@ivinco.com
1250*0Syaroslav@ivinco.com				if ( $this->_arrayresult )
1251*0Syaroslav@ivinco.com					$result["matches"][$idx]["attrs"] = $attrvals;
1252*0Syaroslav@ivinco.com				else
1253*0Syaroslav@ivinco.com					$result["matches"][$doc]["attrs"] = $attrvals;
1254*0Syaroslav@ivinco.com			}
1255*0Syaroslav@ivinco.com
1256*0Syaroslav@ivinco.com			list ( $total, $total_found, $msecs, $words ) =
1257*0Syaroslav@ivinco.com				array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
1258*0Syaroslav@ivinco.com			$result["total"] = sprintf ( "%u", $total );
1259*0Syaroslav@ivinco.com			$result["total_found"] = sprintf ( "%u", $total_found );
1260*0Syaroslav@ivinco.com			$result["time"] = sprintf ( "%.3f", $msecs/1000 );
1261*0Syaroslav@ivinco.com			$p += 16;
1262*0Syaroslav@ivinco.com
1263*0Syaroslav@ivinco.com			while ( $words-->0 && $p<$max )
1264*0Syaroslav@ivinco.com			{
1265*0Syaroslav@ivinco.com				list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1266*0Syaroslav@ivinco.com				$word = substr ( $response, $p, $len ); $p += $len;
1267*0Syaroslav@ivinco.com				list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1268*0Syaroslav@ivinco.com				$result["words"][$word] = array (
1269*0Syaroslav@ivinco.com					"docs"=>sprintf ( "%u", $docs ),
1270*0Syaroslav@ivinco.com					"hits"=>sprintf ( "%u", $hits ) );
1271*0Syaroslav@ivinco.com			}
1272*0Syaroslav@ivinco.com		}
1273*0Syaroslav@ivinco.com
1274*0Syaroslav@ivinco.com		$this->_MBPop ();
1275*0Syaroslav@ivinco.com		return $results;
1276*0Syaroslav@ivinco.com	}
1277*0Syaroslav@ivinco.com
1278*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1279*0Syaroslav@ivinco.com	// excerpts generation
1280*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1281*0Syaroslav@ivinco.com
1282*0Syaroslav@ivinco.com	/// connect to searchd server, and generate exceprts (snippets)
1283*0Syaroslav@ivinco.com	/// of given documents for given query. returns false on failure,
1284*0Syaroslav@ivinco.com	/// an array of snippets on success
1285*0Syaroslav@ivinco.com	function BuildExcerpts ( $docs, $index, $words, $opts=array() )
1286*0Syaroslav@ivinco.com	{
1287*0Syaroslav@ivinco.com		assert ( is_array($docs) );
1288*0Syaroslav@ivinco.com		assert ( is_string($index) );
1289*0Syaroslav@ivinco.com		assert ( is_string($words) );
1290*0Syaroslav@ivinco.com		assert ( is_array($opts) );
1291*0Syaroslav@ivinco.com
1292*0Syaroslav@ivinco.com		$this->_MBPush ();
1293*0Syaroslav@ivinco.com
1294*0Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
1295*0Syaroslav@ivinco.com		{
1296*0Syaroslav@ivinco.com			$this->_MBPop();
1297*0Syaroslav@ivinco.com			return false;
1298*0Syaroslav@ivinco.com		}
1299*0Syaroslav@ivinco.com
1300*0Syaroslav@ivinco.com		/////////////////
1301*0Syaroslav@ivinco.com		// fixup options
1302*0Syaroslav@ivinco.com		/////////////////
1303*0Syaroslav@ivinco.com
1304*0Syaroslav@ivinco.com		if ( !isset($opts["before_match"]) )		$opts["before_match"] = "<b>";
1305*0Syaroslav@ivinco.com		if ( !isset($opts["after_match"]) )			$opts["after_match"] = "</b>";
1306*0Syaroslav@ivinco.com		if ( !isset($opts["chunk_separator"]) )		$opts["chunk_separator"] = " ... ";
1307*0Syaroslav@ivinco.com		if ( !isset($opts["limit"]) )				$opts["limit"] = 256;
1308*0Syaroslav@ivinco.com		if ( !isset($opts["around"]) )				$opts["around"] = 5;
1309*0Syaroslav@ivinco.com		if ( !isset($opts["exact_phrase"]) )		$opts["exact_phrase"] = false;
1310*0Syaroslav@ivinco.com		if ( !isset($opts["single_passage"]) )		$opts["single_passage"] = false;
1311*0Syaroslav@ivinco.com		if ( !isset($opts["use_boundaries"]) )		$opts["use_boundaries"] = false;
1312*0Syaroslav@ivinco.com		if ( !isset($opts["weight_order"]) )		$opts["weight_order"] = false;
1313*0Syaroslav@ivinco.com
1314*0Syaroslav@ivinco.com		/////////////////
1315*0Syaroslav@ivinco.com		// build request
1316*0Syaroslav@ivinco.com		/////////////////
1317*0Syaroslav@ivinco.com
1318*0Syaroslav@ivinco.com		// v.1.0 req
1319*0Syaroslav@ivinco.com		$flags = 1; // remove spaces
1320*0Syaroslav@ivinco.com		if ( $opts["exact_phrase"] )	$flags |= 2;
1321*0Syaroslav@ivinco.com		if ( $opts["single_passage"] )	$flags |= 4;
1322*0Syaroslav@ivinco.com		if ( $opts["use_boundaries"] )	$flags |= 8;
1323*0Syaroslav@ivinco.com		if ( $opts["weight_order"] )	$flags |= 16;
1324*0Syaroslav@ivinco.com		$req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
1325*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // req index
1326*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($words) ) . $words; // req words
1327*0Syaroslav@ivinco.com
1328*0Syaroslav@ivinco.com		// options
1329*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
1330*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
1331*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
1332*0Syaroslav@ivinco.com		$req .= pack ( "N", (int)$opts["limit"] );
1333*0Syaroslav@ivinco.com		$req .= pack ( "N", (int)$opts["around"] );
1334*0Syaroslav@ivinco.com
1335*0Syaroslav@ivinco.com		// documents
1336*0Syaroslav@ivinco.com		$req .= pack ( "N", count($docs) );
1337*0Syaroslav@ivinco.com		foreach ( $docs as $doc )
1338*0Syaroslav@ivinco.com		{
1339*0Syaroslav@ivinco.com			assert ( is_string($doc) );
1340*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($doc) ) . $doc;
1341*0Syaroslav@ivinco.com		}
1342*0Syaroslav@ivinco.com
1343*0Syaroslav@ivinco.com		////////////////////////////
1344*0Syaroslav@ivinco.com		// send query, get response
1345*0Syaroslav@ivinco.com		////////////////////////////
1346*0Syaroslav@ivinco.com
1347*0Syaroslav@ivinco.com		$len = strlen($req);
1348*0Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
1349*0Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1350*0Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
1351*0Syaroslav@ivinco.com		{
1352*0Syaroslav@ivinco.com			$this->_MBPop ();
1353*0Syaroslav@ivinco.com			return false;
1354*0Syaroslav@ivinco.com		}
1355*0Syaroslav@ivinco.com
1356*0Syaroslav@ivinco.com		//////////////////
1357*0Syaroslav@ivinco.com		// parse response
1358*0Syaroslav@ivinco.com		//////////////////
1359*0Syaroslav@ivinco.com
1360*0Syaroslav@ivinco.com		$pos = 0;
1361*0Syaroslav@ivinco.com		$res = array ();
1362*0Syaroslav@ivinco.com		$rlen = strlen($response);
1363*0Syaroslav@ivinco.com		for ( $i=0; $i<count($docs); $i++ )
1364*0Syaroslav@ivinco.com		{
1365*0Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1366*0Syaroslav@ivinco.com			$pos += 4;
1367*0Syaroslav@ivinco.com
1368*0Syaroslav@ivinco.com			if ( $pos+$len > $rlen )
1369*0Syaroslav@ivinco.com			{
1370*0Syaroslav@ivinco.com				$this->_error = "incomplete reply";
1371*0Syaroslav@ivinco.com				$this->_MBPop ();
1372*0Syaroslav@ivinco.com				return false;
1373*0Syaroslav@ivinco.com			}
1374*0Syaroslav@ivinco.com			$res[] = $len ? substr ( $response, $pos, $len ) : "";
1375*0Syaroslav@ivinco.com			$pos += $len;
1376*0Syaroslav@ivinco.com		}
1377*0Syaroslav@ivinco.com
1378*0Syaroslav@ivinco.com		$this->_MBPop ();
1379*0Syaroslav@ivinco.com		return $res;
1380*0Syaroslav@ivinco.com	}
1381*0Syaroslav@ivinco.com
1382*0Syaroslav@ivinco.com
1383*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1384*0Syaroslav@ivinco.com	// keyword generation
1385*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1386*0Syaroslav@ivinco.com
1387*0Syaroslav@ivinco.com	/// connect to searchd server, and generate keyword list for a given query
1388*0Syaroslav@ivinco.com	/// returns false on failure,
1389*0Syaroslav@ivinco.com	/// an array of words on success
1390*0Syaroslav@ivinco.com	function BuildKeywords ( $query, $index, $hits )
1391*0Syaroslav@ivinco.com	{
1392*0Syaroslav@ivinco.com		assert ( is_string($query) );
1393*0Syaroslav@ivinco.com		assert ( is_string($index) );
1394*0Syaroslav@ivinco.com		assert ( is_bool($hits) );
1395*0Syaroslav@ivinco.com
1396*0Syaroslav@ivinco.com		$this->_MBPush ();
1397*0Syaroslav@ivinco.com
1398*0Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
1399*0Syaroslav@ivinco.com		{
1400*0Syaroslav@ivinco.com			$this->_MBPop();
1401*0Syaroslav@ivinco.com			return false;
1402*0Syaroslav@ivinco.com		}
1403*0Syaroslav@ivinco.com
1404*0Syaroslav@ivinco.com		/////////////////
1405*0Syaroslav@ivinco.com		// build request
1406*0Syaroslav@ivinco.com		/////////////////
1407*0Syaroslav@ivinco.com
1408*0Syaroslav@ivinco.com		// v.1.0 req
1409*0Syaroslav@ivinco.com		$req  = pack ( "N", strlen($query) ) . $query; // req query
1410*0Syaroslav@ivinco.com		$req .= pack ( "N", strlen($index) ) . $index; // req index
1411*0Syaroslav@ivinco.com		$req .= pack ( "N", (int)$hits );
1412*0Syaroslav@ivinco.com
1413*0Syaroslav@ivinco.com		////////////////////////////
1414*0Syaroslav@ivinco.com		// send query, get response
1415*0Syaroslav@ivinco.com		////////////////////////////
1416*0Syaroslav@ivinco.com
1417*0Syaroslav@ivinco.com		$len = strlen($req);
1418*0Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
1419*0Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1420*0Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
1421*0Syaroslav@ivinco.com		{
1422*0Syaroslav@ivinco.com			$this->_MBPop ();
1423*0Syaroslav@ivinco.com			return false;
1424*0Syaroslav@ivinco.com		}
1425*0Syaroslav@ivinco.com
1426*0Syaroslav@ivinco.com		//////////////////
1427*0Syaroslav@ivinco.com		// parse response
1428*0Syaroslav@ivinco.com		//////////////////
1429*0Syaroslav@ivinco.com
1430*0Syaroslav@ivinco.com		$pos = 0;
1431*0Syaroslav@ivinco.com		$res = array ();
1432*0Syaroslav@ivinco.com		$rlen = strlen($response);
1433*0Syaroslav@ivinco.com		list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1434*0Syaroslav@ivinco.com		$pos += 4;
1435*0Syaroslav@ivinco.com		for ( $i=0; $i<$nwords; $i++ )
1436*0Syaroslav@ivinco.com		{
1437*0Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
1438*0Syaroslav@ivinco.com			$tokenized = $len ? substr ( $response, $pos, $len ) : "";
1439*0Syaroslav@ivinco.com			$pos += $len;
1440*0Syaroslav@ivinco.com
1441*0Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );	$pos += 4;
1442*0Syaroslav@ivinco.com			$normalized = $len ? substr ( $response, $pos, $len ) : "";
1443*0Syaroslav@ivinco.com			$pos += $len;
1444*0Syaroslav@ivinco.com
1445*0Syaroslav@ivinco.com			$res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
1446*0Syaroslav@ivinco.com
1447*0Syaroslav@ivinco.com			if ( $hits )
1448*0Syaroslav@ivinco.com			{
1449*0Syaroslav@ivinco.com				list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
1450*0Syaroslav@ivinco.com				$pos += 8;
1451*0Syaroslav@ivinco.com				$res [$i]["docs"] = $ndocs;
1452*0Syaroslav@ivinco.com				$res [$i]["hits"] = $nhits;
1453*0Syaroslav@ivinco.com			}
1454*0Syaroslav@ivinco.com
1455*0Syaroslav@ivinco.com			if ( $pos > $rlen )
1456*0Syaroslav@ivinco.com			{
1457*0Syaroslav@ivinco.com				$this->_error = "incomplete reply";
1458*0Syaroslav@ivinco.com				$this->_MBPop ();
1459*0Syaroslav@ivinco.com				return false;
1460*0Syaroslav@ivinco.com			}
1461*0Syaroslav@ivinco.com		}
1462*0Syaroslav@ivinco.com
1463*0Syaroslav@ivinco.com		$this->_MBPop ();
1464*0Syaroslav@ivinco.com		return $res;
1465*0Syaroslav@ivinco.com	}
1466*0Syaroslav@ivinco.com
1467*0Syaroslav@ivinco.com	function EscapeString ( $string )
1468*0Syaroslav@ivinco.com	{
1469*0Syaroslav@ivinco.com		$from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
1470*0Syaroslav@ivinco.com		$to   = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
1471*0Syaroslav@ivinco.com
1472*0Syaroslav@ivinco.com		return str_replace ( $from, $to, $string );
1473*0Syaroslav@ivinco.com	}
1474*0Syaroslav@ivinco.com
1475*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1476*0Syaroslav@ivinco.com	// attribute updates
1477*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1478*0Syaroslav@ivinco.com
1479*0Syaroslav@ivinco.com	/// batch update given attributes in given rows in given indexes
1480*0Syaroslav@ivinco.com	/// returns amount of updated documents (0 or more) on success, or -1 on failure
1481*0Syaroslav@ivinco.com	function UpdateAttributes ( $index, $attrs, $values, $mva=false )
1482*0Syaroslav@ivinco.com	{
1483*0Syaroslav@ivinco.com		// verify everything
1484*0Syaroslav@ivinco.com		assert ( is_string($index) );
1485*0Syaroslav@ivinco.com		assert ( is_bool($mva) );
1486*0Syaroslav@ivinco.com
1487*0Syaroslav@ivinco.com		assert ( is_array($attrs) );
1488*0Syaroslav@ivinco.com		foreach ( $attrs as $attr )
1489*0Syaroslav@ivinco.com			assert ( is_string($attr) );
1490*0Syaroslav@ivinco.com
1491*0Syaroslav@ivinco.com		assert ( is_array($values) );
1492*0Syaroslav@ivinco.com		foreach ( $values as $id=>$entry )
1493*0Syaroslav@ivinco.com		{
1494*0Syaroslav@ivinco.com			assert ( is_numeric($id) );
1495*0Syaroslav@ivinco.com			assert ( is_array($entry) );
1496*0Syaroslav@ivinco.com			assert ( count($entry)==count($attrs) );
1497*0Syaroslav@ivinco.com			foreach ( $entry as $v )
1498*0Syaroslav@ivinco.com			{
1499*0Syaroslav@ivinco.com				if ( $mva )
1500*0Syaroslav@ivinco.com				{
1501*0Syaroslav@ivinco.com					assert ( is_array($v) );
1502*0Syaroslav@ivinco.com					foreach ( $v as $vv )
1503*0Syaroslav@ivinco.com						assert ( is_int($vv) );
1504*0Syaroslav@ivinco.com				} else
1505*0Syaroslav@ivinco.com					assert ( is_int($v) );
1506*0Syaroslav@ivinco.com			}
1507*0Syaroslav@ivinco.com		}
1508*0Syaroslav@ivinco.com
1509*0Syaroslav@ivinco.com		// build request
1510*0Syaroslav@ivinco.com		$req = pack ( "N", strlen($index) ) . $index;
1511*0Syaroslav@ivinco.com
1512*0Syaroslav@ivinco.com		$req .= pack ( "N", count($attrs) );
1513*0Syaroslav@ivinco.com		foreach ( $attrs as $attr )
1514*0Syaroslav@ivinco.com		{
1515*0Syaroslav@ivinco.com			$req .= pack ( "N", strlen($attr) ) . $attr;
1516*0Syaroslav@ivinco.com			$req .= pack ( "N", $mva ? 1 : 0 );
1517*0Syaroslav@ivinco.com		}
1518*0Syaroslav@ivinco.com
1519*0Syaroslav@ivinco.com		$req .= pack ( "N", count($values) );
1520*0Syaroslav@ivinco.com		foreach ( $values as $id=>$entry )
1521*0Syaroslav@ivinco.com		{
1522*0Syaroslav@ivinco.com			$req .= sphPackU64 ( $id );
1523*0Syaroslav@ivinco.com			foreach ( $entry as $v )
1524*0Syaroslav@ivinco.com			{
1525*0Syaroslav@ivinco.com				$req .= pack ( "N", $mva ? count($v) : $v );
1526*0Syaroslav@ivinco.com				if ( $mva )
1527*0Syaroslav@ivinco.com					foreach ( $v as $vv )
1528*0Syaroslav@ivinco.com						$req .= pack ( "N", $vv );
1529*0Syaroslav@ivinco.com			}
1530*0Syaroslav@ivinco.com		}
1531*0Syaroslav@ivinco.com
1532*0Syaroslav@ivinco.com		// connect, send query, get response
1533*0Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
1534*0Syaroslav@ivinco.com			return -1;
1535*0Syaroslav@ivinco.com
1536*0Syaroslav@ivinco.com		$len = strlen($req);
1537*0Syaroslav@ivinco.com		$req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
1538*0Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, $req, $len+8 ) )
1539*0Syaroslav@ivinco.com			return -1;
1540*0Syaroslav@ivinco.com
1541*0Syaroslav@ivinco.com		if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
1542*0Syaroslav@ivinco.com			return -1;
1543*0Syaroslav@ivinco.com
1544*0Syaroslav@ivinco.com		// parse response
1545*0Syaroslav@ivinco.com		list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
1546*0Syaroslav@ivinco.com		return $updated;
1547*0Syaroslav@ivinco.com	}
1548*0Syaroslav@ivinco.com
1549*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1550*0Syaroslav@ivinco.com	// persistent connections
1551*0Syaroslav@ivinco.com	/////////////////////////////////////////////////////////////////////////////
1552*0Syaroslav@ivinco.com
1553*0Syaroslav@ivinco.com	function Open()
1554*0Syaroslav@ivinco.com	{
1555*0Syaroslav@ivinco.com		if ( $this->_socket !== false )
1556*0Syaroslav@ivinco.com		{
1557*0Syaroslav@ivinco.com			$this->_error = 'already connected';
1558*0Syaroslav@ivinco.com			return false;
1559*0Syaroslav@ivinco.com		}
1560*0Syaroslav@ivinco.com		if ( !$fp = $this->_Connect() )
1561*0Syaroslav@ivinco.com			return false;
1562*0Syaroslav@ivinco.com
1563*0Syaroslav@ivinco.com		// command, command version = 0, body length = 4, body = 1
1564*0Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
1565*0Syaroslav@ivinco.com		if ( !$this->_Send ( $fp, $req, 12 ) )
1566*0Syaroslav@ivinco.com			return false;
1567*0Syaroslav@ivinco.com
1568*0Syaroslav@ivinco.com		$this->_socket = $fp;
1569*0Syaroslav@ivinco.com		return true;
1570*0Syaroslav@ivinco.com	}
1571*0Syaroslav@ivinco.com
1572*0Syaroslav@ivinco.com	function Close()
1573*0Syaroslav@ivinco.com	{
1574*0Syaroslav@ivinco.com		if ( $this->_socket === false )
1575*0Syaroslav@ivinco.com		{
1576*0Syaroslav@ivinco.com			$this->_error = 'not connected';
1577*0Syaroslav@ivinco.com			return false;
1578*0Syaroslav@ivinco.com		}
1579*0Syaroslav@ivinco.com
1580*0Syaroslav@ivinco.com		fclose ( $this->_socket );
1581*0Syaroslav@ivinco.com		$this->_socket = false;
1582*0Syaroslav@ivinco.com
1583*0Syaroslav@ivinco.com		return true;
1584*0Syaroslav@ivinco.com	}
1585*0Syaroslav@ivinco.com
1586*0Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////
1587*0Syaroslav@ivinco.com	// status
1588*0Syaroslav@ivinco.com	//////////////////////////////////////////////////////////////////////////
1589*0Syaroslav@ivinco.com
1590*0Syaroslav@ivinco.com	function Status ()
1591*0Syaroslav@ivinco.com	{
1592*0Syaroslav@ivinco.com		$this->_MBPush ();
1593*0Syaroslav@ivinco.com		if (!( $fp = $this->_Connect() ))
1594*0Syaroslav@ivinco.com		{
1595*0Syaroslav@ivinco.com			$this->_MBPop();
1596*0Syaroslav@ivinco.com			return false;
1597*0Syaroslav@ivinco.com		}
1598*0Syaroslav@ivinco.com
1599*0Syaroslav@ivinco.com		$req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
1600*0Syaroslav@ivinco.com		if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
1601*0Syaroslav@ivinco.com			 !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
1602*0Syaroslav@ivinco.com		{
1603*0Syaroslav@ivinco.com			$this->_MBPop ();
1604*0Syaroslav@ivinco.com			return false;
1605*0Syaroslav@ivinco.com		}
1606*0Syaroslav@ivinco.com
1607*0Syaroslav@ivinco.com		$res = substr ( $response, 4 ); // just ignore length, error handling, etc
1608*0Syaroslav@ivinco.com		$p = 0;
1609*0Syaroslav@ivinco.com		list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1610*0Syaroslav@ivinco.com
1611*0Syaroslav@ivinco.com		$res = array();
1612*0Syaroslav@ivinco.com		for ( $i=0; $i<$rows; $i++ )
1613*0Syaroslav@ivinco.com			for ( $j=0; $j<$cols; $j++ )
1614*0Syaroslav@ivinco.com		{
1615*0Syaroslav@ivinco.com			list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1616*0Syaroslav@ivinco.com			$res[$i][] = substr ( $response, $p, $len ); $p += $len;
1617*0Syaroslav@ivinco.com		}
1618*0Syaroslav@ivinco.com
1619*0Syaroslav@ivinco.com		$this->_MBPop ();
1620*0Syaroslav@ivinco.com		return $res;
1621*0Syaroslav@ivinco.com	}
1622*0Syaroslav@ivinco.com}
1623*0Syaroslav@ivinco.com
1624*0Syaroslav@ivinco.com//
1625*0Syaroslav@ivinco.com// $Id: sphinxapi.php 2055 2009-11-06 23:09:58Z shodan $
1626*0Syaroslav@ivinco.com//
1627