sphinxapi.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. <?php
  2. //
  3. // $Id$
  4. //
  5. //
  6. // Copyright (c) 2001-2007, Andrew Aksyonoff. All rights reserved.
  7. //
  8. // This program is free software; you can redistribute it and/or modify
  9. // it under the terms of the GNU General Public License. You should have
  10. // received a copy of the GPL license along with this program; if you
  11. // did not, you can find it at http://www.gnu.org/
  12. //
  13. /////////////////////////////////////////////////////////////////////////////
  14. // PHP version of Sphinx searchd client (PHP API)
  15. /////////////////////////////////////////////////////////////////////////////
  16. /// known searchd commands
  17. define ( "SEARCHD_COMMAND_SEARCH", 0 );
  18. define ( "SEARCHD_COMMAND_EXCERPT", 1 );
  19. define ( "SEARCHD_COMMAND_UPDATE", 2 );
  20. /// current client-side command implementation versions
  21. define ( "VER_COMMAND_SEARCH", 0x10B );
  22. define ( "VER_COMMAND_EXCERPT", 0x100 );
  23. define ( "VER_COMMAND_UPDATE", 0x100 );
  24. /// known searchd status codes
  25. define ( "SEARCHD_OK", 0 );
  26. define ( "SEARCHD_ERROR", 1 );
  27. define ( "SEARCHD_RETRY", 2 );
  28. define ( "SEARCHD_WARNING", 3 );
  29. /// known match modes
  30. define ( "SPH_MATCH_ALL", 0 );
  31. define ( "SPH_MATCH_ANY", 1 );
  32. define ( "SPH_MATCH_PHRASE", 2 );
  33. define ( "SPH_MATCH_BOOLEAN", 3 );
  34. define ( "SPH_MATCH_EXTENDED", 4 );
  35. /// known sort modes
  36. define ( "SPH_SORT_RELEVANCE", 0 );
  37. define ( "SPH_SORT_ATTR_DESC", 1 );
  38. define ( "SPH_SORT_ATTR_ASC", 2 );
  39. define ( "SPH_SORT_TIME_SEGMENTS", 3 );
  40. define ( "SPH_SORT_EXTENDED", 4 );
  41. /// known attribute types
  42. define ( "SPH_ATTR_INTEGER", 1 );
  43. define ( "SPH_ATTR_TIMESTAMP", 2 );
  44. /// known grouping functions
  45. define ( "SPH_GROUPBY_DAY", 0 );
  46. define ( "SPH_GROUPBY_WEEK", 1 );
  47. define ( "SPH_GROUPBY_MONTH", 2 );
  48. define ( "SPH_GROUPBY_YEAR", 3 );
  49. define ( "SPH_GROUPBY_ATTR", 4 );
  50. define ( "SPH_GROUPBY_ATTRPAIR", 5 );
  51. /// sphinx searchd client class
  52. class SphinxClient
  53. {
  54. var $_host; ///< searchd host (default is "localhost")
  55. var $_port; ///< searchd port (default is 3312)
  56. var $_offset; ///< how many records to seek from result-set start (default is 0)
  57. var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
  58. var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
  59. var $_weights; ///< per-field weights (default is 1 for all fields)
  60. var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
  61. var $_sortby; ///< attribute to sort by (defualt is "")
  62. var $_min_id; ///< min ID to match (default is 0, which means no limit)
  63. var $_max_id; ///< max ID to match (default is 0, which means no limit)
  64. var $_filters; ///< search filters
  65. var $_groupby; ///< group-by attribute name
  66. var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
  67. var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
  68. var $_groupdistinct;///< group-by count-distinct attribute
  69. var $_maxmatches; ///< max matches to retrieve
  70. var $_cutoff; ///< cutoff to stop searching at (default is 0)
  71. var $_retrycount; ///< distributed retries count
  72. var $_retrydelay; ///< distributed retries delay
  73. var $_error; ///< last error message
  74. var $_warning; ///< last warning message
  75. /////////////////////////////////////////////////////////////////////////////
  76. // common stuff
  77. /////////////////////////////////////////////////////////////////////////////
  78. /// create a new client object and fill defaults
  79. function SphinxClient ()
  80. {
  81. $this->_host = "localhost";
  82. $this->_port = 3312;
  83. $this->_offset = 0;
  84. $this->_limit = 20;
  85. $this->_mode = SPH_MATCH_ALL;
  86. $this->_weights = array ();
  87. $this->_sort = SPH_SORT_RELEVANCE;
  88. $this->_sortby = "";
  89. $this->_min_id = 0;
  90. $this->_max_id = 0;
  91. $this->_filters = array ();
  92. $this->_groupby = "";
  93. $this->_groupfunc = SPH_GROUPBY_DAY;
  94. $this->_groupsort = "@group desc";
  95. $this->_groupdistinct= "";
  96. $this->_maxmatches = 1000;
  97. $this->_cutoff = 0;
  98. $this->_retrycount = 0;
  99. $this->_retrydelay = 0;
  100. $this->_error = "";
  101. $this->_warning = "";
  102. }
  103. /// get last error message (string)
  104. function GetLastError ()
  105. {
  106. return $this->_error;
  107. }
  108. /// get last warning message (string)
  109. function GetLastWarning ()
  110. {
  111. return $this->_warning;
  112. }
  113. /// set searchd server
  114. function SetServer ( $host, $port )
  115. {
  116. assert ( is_string($host) );
  117. assert ( is_int($port) );
  118. $this->_host = $host;
  119. $this->_port = $port;
  120. }
  121. /////////////////////////////////////////////////////////////////////////////
  122. /// connect to searchd server
  123. function _Connect ()
  124. {
  125. if (!( $fp = @fsockopen ( $this->_host, $this->_port ) ) )
  126. {
  127. $this->_error = "connection to {$this->_host}:{$this->_port} failed";
  128. return false;
  129. }
  130. // check version
  131. list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
  132. $v = (int)$v;
  133. if ( $v<1 )
  134. {
  135. fclose ( $fp );
  136. $this->_error = "expected searchd protocol version 1+, got version '$v'";
  137. return false;
  138. }
  139. // all ok, send my version
  140. fwrite ( $fp, pack ( "N", 1 ) );
  141. return $fp;
  142. }
  143. /// get and check response packet from searchd server
  144. function _GetResponse ( $fp, $client_ver )
  145. {
  146. $response = "";
  147. $len = 0;
  148. $header = fread ( $fp, 8 );
  149. if ( strlen($header)==8 )
  150. {
  151. list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
  152. $left = $len;
  153. while ( $left>0 && !feof($fp) )
  154. {
  155. $chunk = fread ( $fp, $left );
  156. if ( $chunk )
  157. {
  158. $response .= $chunk;
  159. $left -= strlen($chunk);
  160. }
  161. }
  162. }
  163. fclose ( $fp );
  164. // check response
  165. $read = strlen ( $response );
  166. if ( !$response || $read!=$len )
  167. {
  168. $this->_error = $len
  169. ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
  170. : "received zero-sized searchd response";
  171. return false;
  172. }
  173. // check status
  174. if ( $status==SEARCHD_WARNING )
  175. {
  176. list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
  177. $this->_warning = substr ( $response, 4, $wlen );
  178. return substr ( $response, 4+$wlen );
  179. }
  180. if ( $status==SEARCHD_ERROR )
  181. {
  182. $this->_error = "searchd error: " . substr ( $response, 4 );
  183. return false;
  184. }
  185. if ( $status==SEARCHD_RETRY )
  186. {
  187. $this->_error = "temporary searchd error: " . substr ( $response, 4 );
  188. return false;
  189. }
  190. if ( $status!=SEARCHD_OK )
  191. {
  192. $this->_error = "unknown status code '$status'";
  193. return false;
  194. }
  195. // check version
  196. if ( $ver<$client_ver )
  197. {
  198. $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
  199. $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
  200. }
  201. return $response;
  202. }
  203. /////////////////////////////////////////////////////////////////////////////
  204. // searching
  205. /////////////////////////////////////////////////////////////////////////////
  206. /// set offset and count into result set,
  207. /// and max-matches and cutoff to use while searching
  208. function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
  209. {
  210. assert ( is_int($offset) );
  211. assert ( is_int($limit) );
  212. assert ( $offset>=0 );
  213. assert ( $limit>0 );
  214. assert ( $max>=0 );
  215. $this->_offset = $offset;
  216. $this->_limit = $limit;
  217. if ( $max>0 )
  218. $this->_maxmatches = $max;
  219. if ( $cutoff>0 )
  220. $this->_cutoff = $cutoff;
  221. }
  222. /// set match mode
  223. function SetMatchMode ( $mode )
  224. {
  225. assert ( $mode==SPH_MATCH_ALL
  226. || $mode==SPH_MATCH_ANY
  227. || $mode==SPH_MATCH_PHRASE
  228. || $mode==SPH_MATCH_BOOLEAN
  229. || $mode==SPH_MATCH_EXTENDED );
  230. $this->_mode = $mode;
  231. }
  232. /// set matches sorting mode
  233. function SetSortMode ( $mode, $sortby="" )
  234. {
  235. assert (
  236. $mode==SPH_SORT_RELEVANCE ||
  237. $mode==SPH_SORT_ATTR_DESC ||
  238. $mode==SPH_SORT_ATTR_ASC ||
  239. $mode==SPH_SORT_TIME_SEGMENTS ||
  240. $mode==SPH_SORT_EXTENDED );
  241. assert ( is_string($sortby) );
  242. assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
  243. $this->_sort = $mode;
  244. $this->_sortby = $sortby;
  245. }
  246. /// set per-field weights
  247. function SetWeights ( $weights )
  248. {
  249. assert ( is_array($weights) );
  250. foreach ( $weights as $weight )
  251. assert ( is_int($weight) );
  252. $this->_weights = $weights;
  253. }
  254. /// set IDs range to match
  255. /// only match those records where document ID
  256. /// is beetwen $min and $max (including $min and $max)
  257. function SetIDRange ( $min, $max )
  258. {
  259. assert ( is_int($min) );
  260. assert ( is_int($max) );
  261. assert ( $min<=$max );
  262. $this->_min_id = $min;
  263. $this->_max_id = $max;
  264. }
  265. /// set values filter
  266. /// only match those records where $attribute column values
  267. /// are in specified set
  268. function SetFilter ( $attribute, $values, $exclude=false )
  269. {
  270. assert ( is_string($attribute) );
  271. assert ( is_array($values) );
  272. assert ( count($values) );
  273. if ( is_array($values) && count($values) )
  274. {
  275. foreach ( $values as $value )
  276. assert ( is_int($value) );
  277. $this->_filters[] = array ( "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
  278. }
  279. }
  280. /// set range filter
  281. /// only match those records where $attribute column value
  282. /// is beetwen $min and $max (including $min and $max)
  283. function SetFilterRange ( $attribute, $min, $max, $exclude=false )
  284. {
  285. assert ( is_string($attribute) );
  286. assert ( is_int($min) );
  287. assert ( is_int($max) );
  288. assert ( $min<=$max );
  289. $this->_filters[] = array ( "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
  290. }
  291. /// set grouping attribute and function
  292. ///
  293. /// in grouping mode, all matches are assigned to different groups
  294. /// based on grouping function value.
  295. ///
  296. /// each group keeps track of the total match count, and the best match
  297. /// (in this group) according to current sorting function.
  298. ///
  299. /// the final result set contains one best match per group, with
  300. /// grouping function value and matches count attached.
  301. ///
  302. /// groups in result set could be sorted by any sorting clause,
  303. /// including both document attributes and the following special
  304. /// internal Sphinx attributes:
  305. ///
  306. /// - @id - match document ID;
  307. /// - @weight, @rank, @relevance - match weight;
  308. /// - @group - groupby function value;
  309. /// - @count - amount of matches in group.
  310. ///
  311. /// the default mode is to sort by groupby value in descending order,
  312. /// ie. by "@group desc".
  313. ///
  314. /// "total_found" would contain total amount of matching groups over
  315. /// the whole index.
  316. ///
  317. /// WARNING: grouping is done in fixed memory and thus its results
  318. /// are only approximate; so there might be more groups reported
  319. /// in total_found than actually present. @count might also
  320. /// be underestimated.
  321. ///
  322. /// for example, if sorting by relevance and grouping by "published"
  323. /// attribute with SPH_GROUPBY_DAY function, then the result set will
  324. /// contain one most relevant match per each day when there were any
  325. /// matches published, with day number and per-day match count attached,
  326. /// and sorted by day number in descending order (ie. recent days first).
  327. function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
  328. {
  329. assert ( is_string($attribute) );
  330. assert ( is_string($groupsort) );
  331. assert ( $func==SPH_GROUPBY_DAY
  332. || $func==SPH_GROUPBY_WEEK
  333. || $func==SPH_GROUPBY_MONTH
  334. || $func==SPH_GROUPBY_YEAR
  335. || $func==SPH_GROUPBY_ATTR
  336. || $func==SPH_GROUPBY_ATTRPAIR );
  337. $this->_groupby = $attribute;
  338. $this->_groupfunc = $func;
  339. $this->_groupsort = $groupsort;
  340. }
  341. /// set count-distinct attribute for group-by queries
  342. function SetGroupDistinct ( $attribute )
  343. {
  344. assert ( is_string($attribute) );
  345. $this->_groupdistinct = $attribute;
  346. }
  347. /// set distributed retries count and delay
  348. function SetRetries ( $count, $delay=0 )
  349. {
  350. assert ( is_int($count) && $count>=0 );
  351. assert ( is_int($delay) && $delay>=0 );
  352. $this->_retrycount = $count;
  353. $this->_retrydelay = $delay;
  354. }
  355. /// connect to searchd server and run given search query
  356. ///
  357. /// $query is query string
  358. /// $index is index name to query, default is "*" which means to query all indexes
  359. ///
  360. /// returns false on failure
  361. /// returns hash which has the following keys on success:
  362. /// "matches"
  363. /// hash which maps found document_id to ( "weight", "group" ) hash
  364. /// "total"
  365. /// total amount of matches retrieved (upto SPH_MAX_MATCHES, see sphinx.h)
  366. /// "total_found"
  367. /// total amount of matching documents in index
  368. /// "time"
  369. /// search time
  370. /// "words"
  371. /// hash which maps query terms (stemmed!) to ( "docs", "hits" ) hash
  372. function Query ( $query, $index="*" )
  373. {
  374. if (!( $fp = $this->_Connect() ))
  375. return false;
  376. /////////////////
  377. // build request
  378. /////////////////
  379. $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_sort ); // mode and limits
  380. $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
  381. $req .= pack ( "N", strlen($query) ) . $query; // query itself
  382. $req .= pack ( "N", count($this->_weights) ); // weights
  383. foreach ( $this->_weights as $weight )
  384. $req .= pack ( "N", (int)$weight );
  385. $req .= pack ( "N", strlen($index) ) . $index; // indexes
  386. $req .= pack ( "NNN", 0, (int)$this->_min_id, (int)$this->_max_id ); // id32 range
  387. // filters
  388. $req .= pack ( "N", count($this->_filters) );
  389. foreach ( $this->_filters as $filter )
  390. {
  391. $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
  392. if ( isset($filter["values"]) )
  393. {
  394. $req .= pack ( "N", count($filter["values"]) );
  395. foreach ( $filter["values"] as $value )
  396. $req .= pack ( "N", $value );
  397. } else
  398. {
  399. $req .= pack ( "NNN", 0, $filter["min"], $filter["max"] );
  400. }
  401. $req .= pack ( "N", $filter["exclude"] );
  402. }
  403. // group-by clause, max-matches count, group-sort clause, cutoff count
  404. $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
  405. $req .= pack ( "N", $this->_maxmatches );
  406. $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
  407. $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
  408. $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
  409. ////////////////////////////
  410. // send query, get response
  411. ////////////////////////////
  412. $len = strlen($req);
  413. $req = pack ( "nnN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len ) . $req; // add header
  414. fwrite ( $fp, $req, $len+8 );
  415. if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ))
  416. return false;
  417. //////////////////
  418. // parse response
  419. //////////////////
  420. $result = array();
  421. $max = strlen($response); // protection from broken response
  422. // read schema
  423. $p = 0;
  424. $fields = array ();
  425. $attrs = array ();
  426. list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  427. while ( $nfields-->0 && $p<$max )
  428. {
  429. list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  430. $fields[] = substr ( $response, $p, $len ); $p += $len;
  431. }
  432. $result["fields"] = $fields;
  433. list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  434. while ( $nattrs-->0 && $p<$max )
  435. {
  436. list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  437. $attr = substr ( $response, $p, $len ); $p += $len;
  438. list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  439. $attrs[$attr] = $type;
  440. }
  441. $result["attrs"] = $attrs;
  442. // read match count
  443. list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  444. list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  445. // read matches
  446. while ( $count-->0 && $p<$max )
  447. {
  448. if ( $id64 )
  449. {
  450. list ( $dochi, $doclo, $weight ) = array_values ( unpack ( "N*N*N*",
  451. substr ( $response, $p, 12 ) ) );
  452. $p += 12;
  453. $doc = (((int)$dochi)<<32) + ((int)$doclo);
  454. } else
  455. {
  456. list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
  457. substr ( $response, $p, 8 ) ) );
  458. $p += 8;
  459. $doc = sprintf ( "%u", $doc ); // workaround for php signed/unsigned braindamage
  460. }
  461. $weight = sprintf ( "%u", $weight );
  462. $result["matches"][$doc]["weight"] = $weight;
  463. foreach ( $attrs as $attr=>$type )
  464. {
  465. list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  466. $result["matches"][$doc]["attrs"][$attr] = sprintf ( "%u", $val );
  467. }
  468. }
  469. list ( $total, $total_found, $msecs, $words ) =
  470. array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
  471. $result["total"] = sprintf ( "%u", $total );
  472. $result["total_found"] = sprintf ( "%u", $total_found );
  473. $result["time"] = sprintf ( "%.3f", $msecs/1000 );
  474. $p += 16;
  475. while ( $words-->0 && $p<$max )
  476. {
  477. list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
  478. $word = substr ( $response, $p, $len ); $p += $len;
  479. list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
  480. $result["words"][$word] = array (
  481. "docs"=>sprintf ( "%u", $docs ),
  482. "hits"=>sprintf ( "%u", $hits ) );
  483. }
  484. return $result;
  485. }
  486. /////////////////////////////////////////////////////////////////////////////
  487. // excerpts generation
  488. /////////////////////////////////////////////////////////////////////////////
  489. /// connect to searchd server and generate exceprts from given documents
  490. ///
  491. /// $docs is an array of strings which represent the documents' contents
  492. /// $index is a string specifiying the index which settings will be used
  493. /// for stemming, lexing and case folding
  494. /// $words is a string which contains the words to highlight
  495. /// $opts is a hash which contains additional optional highlighting parameters:
  496. /// "before_match"
  497. /// a string to insert before a set of matching words, default is "<b>"
  498. /// "after_match"
  499. /// a string to insert after a set of matching words, default is "<b>"
  500. /// "chunk_separator"
  501. /// a string to insert between excerpts chunks, default is " ... "
  502. /// "limit"
  503. /// max excerpt size in symbols (codepoints), default is 256
  504. /// "around"
  505. /// how much words to highlight around each match, default is 5
  506. ///
  507. /// returns false on failure
  508. /// returns an array of string excerpts on success
  509. function BuildExcerpts ( $docs, $index, $words, $opts=array() )
  510. {
  511. assert ( is_array($docs) );
  512. assert ( is_string($index) );
  513. assert ( is_string($words) );
  514. assert ( is_array($opts) );
  515. if (!( $fp = $this->_Connect() ))
  516. return false;
  517. /////////////////
  518. // fixup options
  519. /////////////////
  520. if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>";
  521. if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
  522. if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
  523. if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
  524. if ( !isset($opts["around"]) ) $opts["around"] = 5;
  525. /////////////////
  526. // build request
  527. /////////////////
  528. // v.1.0 req
  529. $req = pack ( "NN", 0, 1 ); // mode=0, flags=1 (remove spaces)
  530. $req .= pack ( "N", strlen($index) ) . $index; // req index
  531. $req .= pack ( "N", strlen($words) ) . $words; // req words
  532. // options
  533. $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
  534. $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
  535. $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
  536. $req .= pack ( "N", (int)$opts["limit"] );
  537. $req .= pack ( "N", (int)$opts["around"] );
  538. // documents
  539. $req .= pack ( "N", count($docs) );
  540. foreach ( $docs as $doc )
  541. {
  542. assert ( is_string($doc) );
  543. $req .= pack ( "N", strlen($doc) ) . $doc;
  544. }
  545. ////////////////////////////
  546. // send query, get response
  547. ////////////////////////////
  548. $len = strlen($req);
  549. $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
  550. $wrote = fwrite ( $fp, $req, $len+8 );
  551. if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ))
  552. return false;
  553. //////////////////
  554. // parse response
  555. //////////////////
  556. $pos = 0;
  557. $res = array ();
  558. $rlen = strlen($response);
  559. for ( $i=0; $i<count($docs); $i++ )
  560. {
  561. list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
  562. $pos += 4;
  563. if ( $pos+$len > $rlen )
  564. {
  565. $this->_error = "incomplete reply";
  566. return false;
  567. }
  568. $res[] = substr ( $response, $pos, $len );
  569. $pos += $len;
  570. }
  571. return $res;
  572. }
  573. /////////////////////////////////////////////////////////////////////////////
  574. // attribute updates
  575. /////////////////////////////////////////////////////////////////////////////
  576. /// update specified attributes on specified documents
  577. ///
  578. /// $index is a name of the index to be updated
  579. /// $attrs is an array of attribute name strings
  580. /// $values is a hash where key is document id, and value is an array of
  581. /// new attribute values
  582. ///
  583. /// returns number of actually updated documents (0 or more) on success
  584. /// returns -1 on failure
  585. ///
  586. /// usage example:
  587. /// $cl->UpdateAttributes ( "test1", array("group_id"), array(1=>array(456)) );
  588. function UpdateAttributes ( $index, $attrs, $values )
  589. {
  590. // verify everything
  591. assert ( is_string($index) );
  592. assert ( is_array($attrs) );
  593. foreach ( $attrs as $attr )
  594. assert ( is_string($attr) );
  595. assert ( is_array($values) );
  596. foreach ( $values as $id=>$entry )
  597. {
  598. assert ( is_int($id) );
  599. assert ( is_array($entry) );
  600. assert ( count($entry)==count($attrs) );
  601. foreach ( $entry as $v )
  602. assert ( is_int($v) );
  603. }
  604. // build request
  605. $req = pack ( "N", strlen($index) ) . $index;
  606. $req .= pack ( "N", count($attrs) );
  607. foreach ( $attrs as $attr )
  608. $req .= pack ( "N", strlen($attr) ) . $attr;
  609. $req .= pack ( "N", count($values) );
  610. foreach ( $values as $id=>$entry )
  611. {
  612. $req .= pack ( "N", $id );
  613. foreach ( $entry as $v )
  614. $req .= pack ( "N", $v );
  615. }
  616. // connect, send query, get response
  617. if (!( $fp = $this->_Connect() ))
  618. return -1;
  619. $len = strlen($req);
  620. $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
  621. fwrite ( $fp, $req, $len+8 );
  622. if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
  623. return -1;
  624. // parse response
  625. list(,$updated) = unpack ( "N*", substr ( $response, $p, 4 ) );
  626. return $updated;
  627. }
  628. }
  629. //
  630. // $Id$
  631. //
  632. ?>