SphinxClient.java 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343
  1. /*
  2. * $Id$
  3. *
  4. * Java version of Sphinx searchd client (Java API)
  5. *
  6. * Copyright (c) 2007-2008, Andrew Aksyonoff
  7. * Copyright (c) 2007, Vladimir Fedorkov
  8. * All rights reserved
  9. *
  10. * This program is free software; you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License. You should have
  12. * received a copy of the GPL license along with this program; if you
  13. * did not, you can find it at http://www.gnu.org/
  14. */
  15. package org.sphx.api;
  16. import java.io.*;
  17. import java.net.*;
  18. import java.util.*;
  19. /** Sphinx client class */
  20. public class SphinxClient
  21. {
  22. /* matching modes */
  23. public final static int SPH_MATCH_ALL = 0;
  24. public final static int SPH_MATCH_ANY = 1;
  25. public final static int SPH_MATCH_PHRASE = 2;
  26. public final static int SPH_MATCH_BOOLEAN = 3;
  27. public final static int SPH_MATCH_EXTENDED = 4;
  28. public final static int SPH_MATCH_FULLSCAN = 5;
  29. public final static int SPH_MATCH_EXTENDED2 = 6;
  30. /* ranking modes (extended2 only) */
  31. public final static int SPH_RANK_PROXIMITY_BM25 = 0;
  32. public final static int SPH_RANK_BM25 = 1;
  33. public final static int SPH_RANK_NONE = 2;
  34. public final static int SPH_RANK_WORDCOUNT = 3;
  35. public final static int SPH_RANK_PROXIMITY = 4;
  36. public final static int SPH_RANK_MATCHANY = 5;
  37. public final static int SPH_RANK_FIELDMASK = 6;
  38. public final static int SPH_RANK_SPH04 = 7;
  39. public final static int SPH_RANK_TOTAL = 8;
  40. /* sorting modes */
  41. public final static int SPH_SORT_RELEVANCE = 0;
  42. public final static int SPH_SORT_ATTR_DESC = 1;
  43. public final static int SPH_SORT_ATTR_ASC = 2;
  44. public final static int SPH_SORT_TIME_SEGMENTS = 3;
  45. public final static int SPH_SORT_EXTENDED = 4;
  46. public final static int SPH_SORT_EXPR = 5;
  47. /* grouping functions */
  48. public final static int SPH_GROUPBY_DAY = 0;
  49. public final static int SPH_GROUPBY_WEEK = 1;
  50. public final static int SPH_GROUPBY_MONTH = 2;
  51. public final static int SPH_GROUPBY_YEAR = 3;
  52. public final static int SPH_GROUPBY_ATTR = 4;
  53. public final static int SPH_GROUPBY_ATTRPAIR = 5;
  54. /* searchd reply status codes */
  55. public final static int SEARCHD_OK = 0;
  56. public final static int SEARCHD_ERROR = 1;
  57. public final static int SEARCHD_RETRY = 2;
  58. public final static int SEARCHD_WARNING = 3;
  59. /* attribute types */
  60. public final static int SPH_ATTR_INTEGER = 1;
  61. public final static int SPH_ATTR_TIMESTAMP = 2;
  62. public final static int SPH_ATTR_ORDINAL = 3;
  63. public final static int SPH_ATTR_BOOL = 4;
  64. public final static int SPH_ATTR_FLOAT = 5;
  65. public final static int SPH_ATTR_BIGINT = 6;
  66. public final static int SPH_ATTR_STRING = 7;
  67. public final static int SPH_ATTR_MULTI = 0x40000000;
  68. /* searchd commands */
  69. private final static int SEARCHD_COMMAND_SEARCH = 0;
  70. private final static int SEARCHD_COMMAND_EXCERPT = 1;
  71. private final static int SEARCHD_COMMAND_UPDATE = 2;
  72. private final static int SEARCHD_COMMAND_KEYWORDS = 3;
  73. private final static int SEARCHD_COMMAND_PERSIST = 4;
  74. /* searchd command versions */
  75. private final static int VER_MAJOR_PROTO = 0x1;
  76. private final static int VER_COMMAND_SEARCH = 0x117;
  77. private final static int VER_COMMAND_EXCERPT = 0x100;
  78. private final static int VER_COMMAND_UPDATE = 0x101;
  79. private final static int VER_COMMAND_KEYWORDS = 0x100;
  80. /* filter types */
  81. private final static int SPH_FILTER_VALUES = 0;
  82. private final static int SPH_FILTER_RANGE = 1;
  83. private final static int SPH_FILTER_FLOATRANGE = 2;
  84. private String _host;
  85. private int _port;
  86. private String _path;
  87. private Socket _socket;
  88. private int _offset;
  89. private int _limit;
  90. private int _mode;
  91. private int[] _weights;
  92. private int _sort;
  93. private String _sortby;
  94. private int _minId;
  95. private int _maxId;
  96. private ByteArrayOutputStream _rawFilters;
  97. private DataOutputStream _filters;
  98. private int _filterCount;
  99. private String _groupBy;
  100. private int _groupFunc;
  101. private String _groupSort;
  102. private String _groupDistinct;
  103. private int _maxMatches;
  104. private int _cutoff;
  105. private int _retrycount;
  106. private int _retrydelay;
  107. private String _latitudeAttr;
  108. private String _longitudeAttr;
  109. private float _latitude;
  110. private float _longitude;
  111. private String _error;
  112. private String _warning;
  113. private boolean _connerror;
  114. private ArrayList _reqs;
  115. private Map _indexWeights;
  116. private int _ranker;
  117. private int _maxQueryTime;
  118. private Map _fieldWeights;
  119. private Map _overrideTypes;
  120. private Map _overrideValues;
  121. private String _select;
  122. private static final int SPH_CLIENT_TIMEOUT_MILLISEC = 30000;
  123. /** Creates a new SphinxClient instance. */
  124. public SphinxClient()
  125. {
  126. this("localhost", 3312);
  127. }
  128. /** Creates a new SphinxClient instance, with host:port specification. */
  129. public SphinxClient(String host, int port)
  130. {
  131. _host = host;
  132. _port = port;
  133. _path = null;
  134. _socket = null;
  135. _offset = 0;
  136. _limit = 20;
  137. _mode = SPH_MATCH_ALL;
  138. _sort = SPH_SORT_RELEVANCE;
  139. _sortby = "";
  140. _minId = 0;
  141. _maxId = 0;
  142. _filterCount = 0;
  143. _rawFilters = new ByteArrayOutputStream();
  144. _filters = new DataOutputStream(_rawFilters);
  145. _groupBy = "";
  146. _groupFunc = SPH_GROUPBY_DAY;
  147. _groupSort = "@group desc";
  148. _groupDistinct = "";
  149. _maxMatches = 1000;
  150. _cutoff = 0;
  151. _retrycount = 0;
  152. _retrydelay = 0;
  153. _latitudeAttr = null;
  154. _longitudeAttr = null;
  155. _latitude = 0;
  156. _longitude = 0;
  157. _error = "";
  158. _warning = "";
  159. _connerror = false;
  160. _reqs = new ArrayList();
  161. _weights = null;
  162. _indexWeights = new LinkedHashMap();
  163. _fieldWeights = new LinkedHashMap();
  164. _ranker = SPH_RANK_PROXIMITY_BM25;
  165. _overrideTypes = new LinkedHashMap();
  166. _overrideValues = new LinkedHashMap();
  167. _select = "*";
  168. }
  169. /** Get last error message, if any. */
  170. public String GetLastError()
  171. {
  172. return _error;
  173. }
  174. /** Get last warning message, if any. */
  175. public String GetLastWarning()
  176. {
  177. return _warning;
  178. }
  179. /** Get last error flag (to tell network connection errors from searchd errors or broken responses). */
  180. public boolean IsConnectError()
  181. {
  182. return _connerror;
  183. }
  184. /** Set searchd host and port to connect to. */
  185. public void SetServer(String host, int port) throws SphinxException
  186. {
  187. myAssert ( host!=null && host.length()>0, "host name must not be empty" );
  188. myAssert ( port>0 && port<65536, "port must be in 1..65535 range" );
  189. _host = host;
  190. _port = port;
  191. }
  192. /** Internal method. Sanity check. */
  193. private void myAssert ( boolean condition, String err ) throws SphinxException
  194. {
  195. if ( !condition )
  196. {
  197. _error = err;
  198. throw new SphinxException ( err );
  199. }
  200. }
  201. /** Internal method. String IO helper. */
  202. private static void writeNetUTF8 ( DataOutputStream ostream, String str ) throws IOException
  203. {
  204. if ( str==null )
  205. {
  206. ostream.writeInt ( 0 );
  207. return;
  208. }
  209. byte[] sBytes = str.getBytes ( "UTF-8" );
  210. int iLen = sBytes.length;
  211. ostream.writeInt ( iLen );
  212. ostream.write ( sBytes );
  213. }
  214. /** Internal method. String IO helper. */
  215. private static String readNetUTF8(DataInputStream istream) throws IOException
  216. {
  217. int iLen = istream.readInt();
  218. byte[] sBytes = new byte [ iLen ];
  219. istream.readFully ( sBytes );
  220. return new String ( sBytes, "UTF-8");
  221. }
  222. /** Internal method. Unsigned int IO helper. */
  223. private static long readDword ( DataInputStream istream ) throws IOException
  224. {
  225. long v = (long) istream.readInt ();
  226. if ( v<0 )
  227. v += 4294967296L;
  228. return v;
  229. }
  230. /** Internal method. Connect to searchd and exchange versions. */
  231. private Socket _Connect()
  232. {
  233. if ( _socket!=null )
  234. return _socket;
  235. _connerror = false;
  236. Socket sock = null;
  237. try
  238. {
  239. sock = new Socket ( _host, _port );
  240. sock.setSoTimeout ( SPH_CLIENT_TIMEOUT_MILLISEC );
  241. DataInputStream sIn = new DataInputStream ( sock.getInputStream() );
  242. int version = sIn.readInt();
  243. if ( version<1 )
  244. {
  245. sock.close ();
  246. _error = "expected searchd protocol version 1+, got version " + version;
  247. return null;
  248. }
  249. DataOutputStream sOut = new DataOutputStream ( sock.getOutputStream() );
  250. sOut.writeInt ( VER_MAJOR_PROTO );
  251. } catch ( IOException e )
  252. {
  253. _error = "connection to " + _host + ":" + _port + " failed: " + e;
  254. _connerror = true;
  255. try
  256. {
  257. if ( sock!=null )
  258. sock.close ();
  259. } catch ( IOException e1 ) {}
  260. return null;
  261. }
  262. return sock;
  263. }
  264. /** Internal method. Get and check response packet from searchd. */
  265. private byte[] _GetResponse ( Socket sock )
  266. {
  267. /* connect */
  268. DataInputStream sIn = null;
  269. InputStream SockInput = null;
  270. try
  271. {
  272. SockInput = sock.getInputStream();
  273. sIn = new DataInputStream ( SockInput );
  274. } catch ( IOException e )
  275. {
  276. _error = "getInputStream() failed: " + e;
  277. return null;
  278. }
  279. /* read response */
  280. byte[] response = null;
  281. short status = 0, ver = 0;
  282. int len = 0;
  283. try
  284. {
  285. /* read status fields */
  286. status = sIn.readShort();
  287. ver = sIn.readShort();
  288. len = sIn.readInt();
  289. /* read response if non-empty */
  290. if ( len<=0 )
  291. {
  292. _error = "invalid response packet size (len=" + len + ")";
  293. return null;
  294. }
  295. response = new byte[len];
  296. sIn.readFully ( response, 0, len );
  297. /* check status */
  298. if ( status==SEARCHD_WARNING )
  299. {
  300. DataInputStream in = new DataInputStream ( new ByteArrayInputStream ( response ) );
  301. int iWarnLen = in.readInt ();
  302. _warning = new String ( response, 4, iWarnLen );
  303. System.arraycopy ( response, 4+iWarnLen, response, 0, response.length-4-iWarnLen );
  304. } else if ( status==SEARCHD_ERROR )
  305. {
  306. _error = "searchd error: " + new String ( response, 4, response.length-4 );
  307. return null;
  308. } else if ( status==SEARCHD_RETRY )
  309. {
  310. _error = "temporary searchd error: " + new String ( response, 4, response.length-4 );
  311. return null;
  312. } else if ( status!=SEARCHD_OK )
  313. {
  314. _error = "searched returned unknown status, code=" + status;
  315. return null;
  316. }
  317. } catch ( IOException e )
  318. {
  319. if ( len!=0 )
  320. {
  321. /* get trace, to provide even more failure details */
  322. PrintWriter ew = new PrintWriter ( new StringWriter() );
  323. e.printStackTrace ( ew );
  324. ew.flush ();
  325. ew.close ();
  326. String sTrace = ew.toString ();
  327. /* build error message */
  328. _error = "failed to read searchd response (status=" + status + ", ver=" + ver + ", len=" + len + ", trace=" + sTrace +")";
  329. } else
  330. {
  331. _error = "received zero-sized searchd response (searchd crashed?): " + e.getMessage();
  332. }
  333. return null;
  334. } finally
  335. {
  336. if ( _socket==null )
  337. {
  338. try
  339. {
  340. if ( sIn!=null )
  341. sIn.close();
  342. if ( sock!=null && !sock.isConnected() )
  343. sock.close();
  344. } catch ( IOException e )
  345. {
  346. /* silently ignore close failures; nothing could be done anyway */
  347. }
  348. }
  349. }
  350. return response;
  351. }
  352. /** Internal method. Connect to searchd, send request, get response as DataInputStream. */
  353. private DataInputStream _DoRequest ( int command, int version, ByteArrayOutputStream req )
  354. {
  355. /* connect */
  356. Socket sock = _Connect();
  357. if ( sock==null )
  358. return null;
  359. /* send request */
  360. byte[] reqBytes = req.toByteArray();
  361. try
  362. {
  363. DataOutputStream sockDS = new DataOutputStream ( sock.getOutputStream() );
  364. sockDS.writeShort ( command );
  365. sockDS.writeShort ( version );
  366. sockDS.writeInt ( reqBytes.length );
  367. sockDS.write ( reqBytes );
  368. } catch ( Exception e )
  369. {
  370. _error = "network error: " + e;
  371. _connerror = true;
  372. return null;
  373. }
  374. /* get response */
  375. byte[] response = _GetResponse ( sock );
  376. if ( response==null )
  377. return null;
  378. /* spawn that tampon */
  379. return new DataInputStream ( new ByteArrayInputStream ( response ) );
  380. }
  381. /** Set matches offset and limit to return to client, max matches to retrieve on server, and cutoff. */
  382. public void SetLimits ( int offset, int limit, int max, int cutoff ) throws SphinxException
  383. {
  384. myAssert ( offset>=0, "offset must not be negative" );
  385. myAssert ( limit>0, "limit must be positive" );
  386. myAssert ( max>0, "max must be positive" );
  387. myAssert ( cutoff>=0, "cutoff must not be negative" );
  388. _offset = offset;
  389. _limit = limit;
  390. _maxMatches = max;
  391. _cutoff = cutoff;
  392. }
  393. /** Set matches offset and limit to return to client, and max matches to retrieve on server. */
  394. public void SetLimits ( int offset, int limit, int max ) throws SphinxException
  395. {
  396. SetLimits ( offset, limit, max, _cutoff );
  397. }
  398. /** Set matches offset and limit to return to client. */
  399. public void SetLimits ( int offset, int limit) throws SphinxException
  400. {
  401. SetLimits ( offset, limit, _maxMatches, _cutoff );
  402. }
  403. /** Set maximum query time, in milliseconds, per-index, 0 means "do not limit". */
  404. public void SetMaxQueryTime ( int maxTime ) throws SphinxException
  405. {
  406. myAssert ( maxTime>=0, "max_query_time must not be negative" );
  407. _maxQueryTime = maxTime;
  408. }
  409. /** Set matching mode. */
  410. public void SetMatchMode(int mode) throws SphinxException
  411. {
  412. myAssert (
  413. mode==SPH_MATCH_ALL ||
  414. mode==SPH_MATCH_ANY ||
  415. mode==SPH_MATCH_PHRASE ||
  416. mode==SPH_MATCH_BOOLEAN ||
  417. mode==SPH_MATCH_EXTENDED ||
  418. mode==SPH_MATCH_EXTENDED2, "unknown mode value; use one of the SPH_MATCH_xxx constants" );
  419. _mode = mode;
  420. }
  421. /** Set ranking mode. */
  422. public void SetRankingMode ( int ranker ) throws SphinxException
  423. {
  424. myAssert ( ranker>=0 && ranker<SPH_RANK_TOTAL, "unknown ranker value; use one of the SPH_RANK_xxx constants" );
  425. _ranker = ranker;
  426. }
  427. /** Set sorting mode. */
  428. public void SetSortMode ( int mode, String sortby ) throws SphinxException
  429. {
  430. myAssert (
  431. mode==SPH_SORT_RELEVANCE ||
  432. mode==SPH_SORT_ATTR_DESC ||
  433. mode==SPH_SORT_ATTR_ASC ||
  434. mode==SPH_SORT_TIME_SEGMENTS ||
  435. mode==SPH_SORT_EXTENDED ||
  436. mode==SPH_SORT_EXPR, "unknown mode value; use one of the available SPH_SORT_xxx constants" );
  437. myAssert ( mode==SPH_SORT_RELEVANCE || ( sortby!=null && sortby.length()>0 ), "sortby string must not be empty in selected mode" );
  438. _sort = mode;
  439. _sortby = ( sortby==null ) ? "" : sortby;
  440. }
  441. /** Set per-field weights (all values must be positive). WARNING: DEPRECATED, use SetFieldWeights() instead. */
  442. public void SetWeights(int[] weights) throws SphinxException
  443. {
  444. myAssert ( weights!=null, "weights must not be null" );
  445. for (int i = 0; i < weights.length; i++) {
  446. int weight = weights[i];
  447. myAssert ( weight>0, "all weights must be greater than 0" );
  448. }
  449. _weights = weights;
  450. }
  451. /**
  452. * Bind per-field weights by field name.
  453. * @param fieldWeights hash which maps String index names to Integer weights
  454. */
  455. public void SetFieldeights ( Map fieldWeights ) throws SphinxException
  456. {
  457. /* FIXME! implement checks here */
  458. _fieldWeights = ( fieldWeights==null ) ? new LinkedHashMap () : fieldWeights;
  459. }
  460. /**
  461. * Bind per-index weights by index name (and enable summing the weights on duplicate matches, instead of replacing them).
  462. * @param indexWeights hash which maps String index names to Integer weights
  463. */
  464. public void SetIndexWeights ( Map indexWeights ) throws SphinxException
  465. {
  466. /* FIXME! implement checks here */
  467. _indexWeights = ( indexWeights==null ) ? new LinkedHashMap () : indexWeights;
  468. }
  469. /** Set document IDs range to match. */
  470. public void SetIDRange ( int min, int max ) throws SphinxException
  471. {
  472. myAssert ( min<=max, "min must be less or equal to max" );
  473. _minId = min;
  474. _maxId = max;
  475. }
  476. /** Set values filter. Only match records where attribute value is in given set. */
  477. public void SetFilter ( String attribute, int[] values, boolean exclude ) throws SphinxException
  478. {
  479. myAssert ( values!=null && values.length>0, "values array must not be null or empty" );
  480. myAssert ( attribute!=null && attribute.length()>0, "attribute name must not be null or empty" );
  481. try
  482. {
  483. writeNetUTF8 ( _filters, attribute );
  484. _filters.writeInt ( SPH_FILTER_VALUES );
  485. _filters.writeInt ( values.length );
  486. for ( int i=0; i<values.length; i++ )
  487. _filters.writeLong ( values[i] );
  488. _filters.writeInt ( exclude ? 1 : 0 );
  489. } catch ( Exception e )
  490. {
  491. myAssert ( false, "IOException: " + e.getMessage() );
  492. }
  493. _filterCount++;
  494. }
  495. /** Set values filter. Only match records where attribute value is in given set. */
  496. public void SetFilter ( String attribute, long[] values, boolean exclude ) throws SphinxException
  497. {
  498. myAssert ( values!=null && values.length>0, "values array must not be null or empty" );
  499. myAssert ( attribute!=null && attribute.length()>0, "attribute name must not be null or empty" );
  500. try
  501. {
  502. writeNetUTF8 ( _filters, attribute );
  503. _filters.writeInt ( SPH_FILTER_VALUES );
  504. _filters.writeInt ( values.length );
  505. for ( int i=0; i<values.length; i++ )
  506. _filters.writeLong ( values[i] );
  507. _filters.writeInt ( exclude ? 1 : 0 );
  508. } catch ( Exception e )
  509. {
  510. myAssert ( false, "IOException: " + e.getMessage() );
  511. }
  512. _filterCount++;
  513. }
  514. /** Set values filter with a single value (syntax sugar; see {@link #SetFilter(String,int[],boolean)}). */
  515. public void SetFilter ( String attribute, int value, boolean exclude ) throws SphinxException
  516. {
  517. long[] values = new long[] { value };
  518. SetFilter ( attribute, values, exclude );
  519. }
  520. /** Set values filter with a single value (syntax sugar; see {@link #SetFilter(String,int[],boolean)}). */
  521. public void SetFilter ( String attribute, long value, boolean exclude ) throws SphinxException
  522. {
  523. long[] values = new long[] { value };
  524. SetFilter ( attribute, values, exclude );
  525. }
  526. /** Set integer range filter. Only match records if attribute value is beetwen min and max (inclusive). */
  527. public void SetFilterRange ( String attribute, long min, long max, boolean exclude ) throws SphinxException
  528. {
  529. myAssert ( min<=max, "min must be less or equal to max" );
  530. try
  531. {
  532. writeNetUTF8 ( _filters, attribute );
  533. _filters.writeInt ( SPH_FILTER_RANGE );
  534. _filters.writeLong ( min );
  535. _filters.writeLong ( max );
  536. _filters.writeInt ( exclude ? 1 : 0 );
  537. } catch ( Exception e )
  538. {
  539. myAssert ( false, "IOException: " + e.getMessage() );
  540. }
  541. _filterCount++;
  542. }
  543. /** Set integer range filter. Only match records if attribute value is beetwen min and max (inclusive). */
  544. public void SetFilterRange ( String attribute, int min, int max, boolean exclude ) throws SphinxException
  545. {
  546. SetFilterRange ( attribute, (long)min, (long)max, exclude );
  547. }
  548. /** Set float range filter. Only match records if attribute value is beetwen min and max (inclusive). */
  549. public void SetFilterFloatRange ( String attribute, float min, float max, boolean exclude ) throws SphinxException
  550. {
  551. myAssert ( min<=max, "min must be less or equal to max" );
  552. try
  553. {
  554. writeNetUTF8 ( _filters, attribute );
  555. _filters.writeInt ( SPH_FILTER_FLOATRANGE );
  556. _filters.writeFloat ( min );
  557. _filters.writeFloat ( max );
  558. _filters.writeInt ( exclude ? 1 : 0 );
  559. } catch ( Exception e )
  560. {
  561. myAssert ( false, "IOException: " + e.getMessage() );
  562. }
  563. _filterCount++;
  564. }
  565. /** Setup geographical anchor point. Required to use @geodist in filters and sorting; distance will be computed to this point. */
  566. public void SetGeoAnchor ( String latitudeAttr, String longitudeAttr, float latitude, float longitude ) throws SphinxException
  567. {
  568. myAssert ( latitudeAttr!=null && latitudeAttr.length()>0, "longitudeAttr string must not be null or empty" );
  569. myAssert ( longitudeAttr!=null && longitudeAttr.length()>0, "longitudeAttr string must not be null or empty" );
  570. _latitudeAttr = latitudeAttr;
  571. _longitudeAttr = longitudeAttr;
  572. _latitude = latitude;
  573. _longitude = longitude;
  574. }
  575. /** Set grouping attribute and function. */
  576. public void SetGroupBy ( String attribute, int func, String groupsort ) throws SphinxException
  577. {
  578. myAssert (
  579. func==SPH_GROUPBY_DAY ||
  580. func==SPH_GROUPBY_WEEK ||
  581. func==SPH_GROUPBY_MONTH ||
  582. func==SPH_GROUPBY_YEAR ||
  583. func==SPH_GROUPBY_ATTR ||
  584. func==SPH_GROUPBY_ATTRPAIR, "unknown func value; use one of the available SPH_GROUPBY_xxx constants" );
  585. _groupBy = attribute;
  586. _groupFunc = func;
  587. _groupSort = groupsort;
  588. }
  589. /** Set grouping attribute and function with default ("@group desc") groupsort (syntax sugar). */
  590. public void SetGroupBy(String attribute, int func) throws SphinxException
  591. {
  592. SetGroupBy(attribute, func, "@group desc");
  593. }
  594. /** Set count-distinct attribute for group-by queries. */
  595. public void SetGroupDistinct(String attribute)
  596. {
  597. _groupDistinct = attribute;
  598. }
  599. /** Set distributed retries count and delay. */
  600. public void SetRetries ( int count, int delay ) throws SphinxException
  601. {
  602. myAssert ( count>=0, "count must not be negative" );
  603. myAssert ( delay>=0, "delay must not be negative" );
  604. _retrycount = count;
  605. _retrydelay = delay;
  606. }
  607. /** Set distributed retries count with default (zero) delay (syntax sugar). */
  608. public void SetRetries ( int count ) throws SphinxException
  609. {
  610. SetRetries ( count, 0 );
  611. }
  612. /**
  613. * Set attribute values override (one override list per attribute).
  614. * @param values maps Long document IDs to Int/Long/Float values (as specified in attrtype).
  615. */
  616. public void SetOverride ( String attrname, int attrtype, Map values ) throws SphinxException
  617. {
  618. myAssert ( attrname!=null && attrname.length()>0, "attrname must not be empty" );
  619. myAssert ( attrtype==SPH_ATTR_INTEGER || attrtype==SPH_ATTR_TIMESTAMP || attrtype==SPH_ATTR_BOOL || attrtype==SPH_ATTR_FLOAT || attrtype==SPH_ATTR_BIGINT,
  620. "unsupported attrtype (must be one of INTEGER, TIMESTAMP, BOOL, FLOAT, or BIGINT)" );
  621. _overrideTypes.put ( attrname, new Integer ( attrtype ) );
  622. _overrideValues.put ( attrname, values );
  623. }
  624. /** Set select-list (attributes or expressions), SQL-like syntax. */
  625. public void SetSelect ( String select ) throws SphinxException
  626. {
  627. myAssert ( select!=null, "select clause string must not be null" );
  628. _select = select;
  629. }
  630. /** Reset all currently set filters (for multi-queries). */
  631. public void ResetFilters()
  632. {
  633. /* should we close them first? */
  634. _rawFilters = new ByteArrayOutputStream();
  635. _filters = new DataOutputStream(_rawFilters);
  636. _filterCount = 0;
  637. /* reset GEO anchor */
  638. _latitudeAttr = null;
  639. _longitudeAttr = null;
  640. _latitude = 0;
  641. _longitude = 0;
  642. }
  643. /** Clear groupby settings (for multi-queries). */
  644. public void ResetGroupBy ()
  645. {
  646. _groupBy = "";
  647. _groupFunc = SPH_GROUPBY_DAY;
  648. _groupSort = "@group desc";
  649. _groupDistinct = "";
  650. }
  651. /** Clear all attribute value overrides (for multi-queries). */
  652. public void ResetOverrides ()
  653. {
  654. _overrideTypes.clear ();
  655. _overrideValues.clear ();
  656. }
  657. /** Connect to searchd server and run current search query against all indexes (syntax sugar). */
  658. public SphinxResult Query ( String query ) throws SphinxException
  659. {
  660. return Query ( query, "*", "" );
  661. }
  662. /** Connect to searchd server and run current search query against all indexes (syntax sugar). */
  663. public SphinxResult Query ( String query, String index ) throws SphinxException
  664. {
  665. return Query ( query, index, "" );
  666. }
  667. /** Connect to searchd server and run current search query. */
  668. public SphinxResult Query ( String query, String index, String comment ) throws SphinxException
  669. {
  670. myAssert ( _reqs==null || _reqs.size()==0, "AddQuery() and Query() can not be combined; use RunQueries() instead" );
  671. AddQuery ( query, index, comment );
  672. SphinxResult[] results = RunQueries();
  673. _reqs = new ArrayList(); /* just in case it failed too early */
  674. if ( results==null || results.length<1 )
  675. return null; /* probably network error; error message should be already filled */
  676. SphinxResult res = results[0];
  677. _warning = res.warning;
  678. _error = res.error;
  679. if ( res==null || res.getStatus()==SEARCHD_ERROR )
  680. return null;
  681. return res;
  682. }
  683. /** Add new query with current settings to current search request. */
  684. public int AddQuery ( String query, String index, String comment ) throws SphinxException
  685. {
  686. ByteArrayOutputStream req = new ByteArrayOutputStream();
  687. /* build request */
  688. try {
  689. DataOutputStream out = new DataOutputStream(req);
  690. out.writeInt(_offset);
  691. out.writeInt(_limit);
  692. out.writeInt(_mode);
  693. out.writeInt(_ranker);
  694. out.writeInt(_sort);
  695. writeNetUTF8(out, _sortby);
  696. writeNetUTF8(out, query);
  697. int weightLen = _weights != null ? _weights.length : 0;
  698. out.writeInt(weightLen);
  699. if (_weights != null) {
  700. for (int i = 0; i < _weights.length; i++)
  701. out.writeInt(_weights[i]);
  702. }
  703. writeNetUTF8(out, index);
  704. out.writeInt(0);
  705. out.writeInt(_minId);
  706. out.writeInt(_maxId);
  707. /* filters */
  708. out.writeInt(_filterCount);
  709. out.write(_rawFilters.toByteArray());
  710. /* group-by, max matches, sort-by-group flag */
  711. out.writeInt(_groupFunc);
  712. writeNetUTF8(out, _groupBy);
  713. out.writeInt(_maxMatches);
  714. writeNetUTF8(out, _groupSort);
  715. out.writeInt(_cutoff);
  716. out.writeInt(_retrycount);
  717. out.writeInt(_retrydelay);
  718. writeNetUTF8(out, _groupDistinct);
  719. /* anchor point */
  720. if (_latitudeAttr == null || _latitudeAttr.length() == 0 || _longitudeAttr == null || _longitudeAttr.length() == 0) {
  721. out.writeInt(0);
  722. } else {
  723. out.writeInt(1);
  724. writeNetUTF8(out, _latitudeAttr);
  725. writeNetUTF8(out, _longitudeAttr);
  726. out.writeFloat(_latitude);
  727. out.writeFloat(_longitude);
  728. }
  729. /* per-index weights */
  730. out.writeInt(_indexWeights.size());
  731. for (Iterator e = _indexWeights.keySet().iterator(); e.hasNext();) {
  732. String indexName = (String) e.next();
  733. Integer weight = (Integer) _indexWeights.get(indexName);
  734. writeNetUTF8(out, indexName);
  735. out.writeInt(weight.intValue());
  736. }
  737. /* max query time */
  738. out.writeInt ( _maxQueryTime );
  739. /* per-field weights */
  740. out.writeInt ( _fieldWeights.size() );
  741. for ( Iterator e=_fieldWeights.keySet().iterator(); e.hasNext(); )
  742. {
  743. String field = (String) e.next();
  744. Integer weight = (Integer) _fieldWeights.get ( field );
  745. writeNetUTF8 ( out, field );
  746. out.writeInt ( weight.intValue() );
  747. }
  748. /* comment */
  749. writeNetUTF8 ( out, comment );
  750. /* overrides */
  751. out.writeInt ( _overrideTypes.size() );
  752. for ( Iterator e=_overrideTypes.keySet().iterator(); e.hasNext(); )
  753. {
  754. String attr = (String) e.next();
  755. Integer type = (Integer) _overrideTypes.get ( attr );
  756. Map values = (Map) _overrideValues.get ( attr );
  757. writeNetUTF8 ( out, attr );
  758. out.writeInt ( type.intValue() );
  759. out.writeInt ( values.size() );
  760. for ( Iterator e2=values.keySet().iterator(); e2.hasNext(); )
  761. {
  762. Long id = (Long) e2.next ();
  763. out.writeLong ( id.longValue() );
  764. switch ( type.intValue() )
  765. {
  766. case SPH_ATTR_FLOAT: out.writeFloat ( ( (Float) values.get ( id ) ).floatValue() ); break;
  767. case SPH_ATTR_BIGINT: out.writeLong ( ( (Long)values.get ( id ) ).longValue() ); break;
  768. default: out.writeInt ( ( (Integer)values.get ( id ) ).intValue() ); break;
  769. }
  770. }
  771. }
  772. /* select-list */
  773. writeNetUTF8 ( out, _select );
  774. /* done! */
  775. out.flush ();
  776. int qIndex = _reqs.size();
  777. _reqs.add ( qIndex, req.toByteArray() );
  778. return qIndex;
  779. } catch ( Exception e )
  780. {
  781. myAssert ( false, "error in AddQuery(): " + e + ": " + e.getMessage() );
  782. } finally
  783. {
  784. try
  785. {
  786. _filters.close ();
  787. _rawFilters.close ();
  788. } catch ( IOException e )
  789. {
  790. myAssert ( false, "error in AddQuery(): " + e + ": " + e.getMessage() );
  791. }
  792. }
  793. return -1;
  794. }
  795. /** Run all previously added search queries. */
  796. public SphinxResult[] RunQueries() throws SphinxException
  797. {
  798. if ( _reqs==null || _reqs.size()<1 )
  799. {
  800. _error = "no queries defined, issue AddQuery() first";
  801. return null;
  802. }
  803. /* build the mega-request */
  804. int nreqs = _reqs.size();
  805. ByteArrayOutputStream reqBuf = new ByteArrayOutputStream();
  806. try
  807. {
  808. DataOutputStream req = new DataOutputStream ( reqBuf );
  809. req.writeInt ( nreqs );
  810. for ( int i=0; i<nreqs; i++ )
  811. req.write ( (byte[]) _reqs.get(i) );
  812. req.flush ();
  813. } catch ( Exception e )
  814. {
  815. _error = "internal error: failed to build request: " + e;
  816. return null;
  817. }
  818. DataInputStream in =_DoRequest ( SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, reqBuf );
  819. if ( in==null )
  820. return null;
  821. SphinxResult[] results = new SphinxResult [ nreqs ];
  822. _reqs = new ArrayList();
  823. try
  824. {
  825. for ( int ires=0; ires<nreqs; ires++ )
  826. {
  827. SphinxResult res = new SphinxResult();
  828. results[ires] = res;
  829. int status = in.readInt();
  830. res.setStatus ( status );
  831. if (status != SEARCHD_OK) {
  832. String message = readNetUTF8(in);
  833. if (status == SEARCHD_WARNING) {
  834. res.warning = message;
  835. } else {
  836. res.error = message;
  837. continue;
  838. }
  839. }
  840. /* read fields */
  841. int nfields = in.readInt();
  842. res.fields = new String[nfields];
  843. int pos = 0;
  844. for (int i = 0; i < nfields; i++)
  845. res.fields[i] = readNetUTF8(in);
  846. /* read arrts */
  847. int nattrs = in.readInt();
  848. res.attrTypes = new int[nattrs];
  849. res.attrNames = new String[nattrs];
  850. for (int i = 0; i < nattrs; i++) {
  851. String AttrName = readNetUTF8(in);
  852. int AttrType = in.readInt();
  853. res.attrNames[i] = AttrName;
  854. res.attrTypes[i] = AttrType;
  855. }
  856. /* read match count */
  857. int count = in.readInt();
  858. int id64 = in.readInt();
  859. res.matches = new SphinxMatch[count];
  860. for ( int matchesNo=0; matchesNo<count; matchesNo++ )
  861. {
  862. SphinxMatch docInfo;
  863. docInfo = new SphinxMatch (
  864. ( id64==0 ) ? readDword(in) : in.readLong(),
  865. in.readInt() );
  866. /* read matches */
  867. for (int attrNumber = 0; attrNumber < res.attrTypes.length; attrNumber++)
  868. {
  869. String attrName = res.attrNames[attrNumber];
  870. int type = res.attrTypes[attrNumber];
  871. /* handle bigints */
  872. if ( type==SPH_ATTR_BIGINT )
  873. {
  874. docInfo.attrValues.add ( attrNumber, new Long ( in.readLong() ) );
  875. continue;
  876. }
  877. /* handle floats */
  878. if ( type==SPH_ATTR_FLOAT )
  879. {
  880. docInfo.attrValues.add ( attrNumber, new Float ( in.readFloat() ) );
  881. continue;
  882. }
  883. /* handle strings */
  884. if ( type==SPH_ATTR_STRING )
  885. {
  886. String s = readNetUTF8(in);
  887. docInfo.attrValues.add ( attrNumber, s );
  888. continue;
  889. }
  890. /* handle everything else as unsigned ints */
  891. long val = readDword ( in );
  892. if ( ( type & SPH_ATTR_MULTI )!=0 )
  893. {
  894. long[] vals = new long [ (int)val ];
  895. for ( int k=0; k<val; k++ )
  896. vals[k] = readDword ( in );
  897. docInfo.attrValues.add ( attrNumber, vals );
  898. } else
  899. {
  900. docInfo.attrValues.add ( attrNumber, new Long ( val ) );
  901. }
  902. }
  903. res.matches[matchesNo] = docInfo;
  904. }
  905. res.total = in.readInt();
  906. res.totalFound = in.readInt();
  907. res.time = in.readInt() / 1000.0f;
  908. res.words = new SphinxWordInfo [ in.readInt() ];
  909. for ( int i=0; i<res.words.length; i++ )
  910. res.words[i] = new SphinxWordInfo ( readNetUTF8(in), readDword(in), readDword(in) );
  911. }
  912. return results;
  913. } catch ( IOException e )
  914. {
  915. _error = "incomplete reply";
  916. return null;
  917. }
  918. }
  919. /**
  920. * Connect to searchd server and generate excerpts (snippets) from given documents.
  921. * @param opts maps String keys to String or Integer values (see the documentation for complete keys list).
  922. * @return null on failure, array of snippets on success.
  923. */
  924. public String[] BuildExcerpts ( String[] docs, String index, String words, Map opts ) throws SphinxException
  925. {
  926. myAssert(docs != null && docs.length > 0, "BuildExcerpts: Have no documents to process");
  927. myAssert(index != null && index.length() > 0, "BuildExcerpts: Have no index to process documents");
  928. myAssert(words != null && words.length() > 0, "BuildExcerpts: Have no words to highlight");
  929. if (opts == null) opts = new LinkedHashMap();
  930. /* fixup options */
  931. if (!opts.containsKey("before_match")) opts.put("before_match", "<b>");
  932. if (!opts.containsKey("after_match")) opts.put("after_match", "</b>");
  933. if (!opts.containsKey("chunk_separator")) opts.put("chunk_separator", "...");
  934. if (!opts.containsKey("limit")) opts.put("limit", new Integer(256));
  935. if (!opts.containsKey("around")) opts.put("around", new Integer(5));
  936. if (!opts.containsKey("exact_phrase")) opts.put("exact_phrase", new Integer(0));
  937. if (!opts.containsKey("single_passage")) opts.put("single_passage", new Integer(0));
  938. if (!opts.containsKey("use_boundaries")) opts.put("use_boundaries", new Integer(0));
  939. if (!opts.containsKey("weight_order")) opts.put("weight_order", new Integer(0));
  940. /* build request */
  941. ByteArrayOutputStream reqBuf = new ByteArrayOutputStream();
  942. DataOutputStream req = new DataOutputStream ( reqBuf );
  943. try
  944. {
  945. req.writeInt(0);
  946. int iFlags = 1; /* remove_spaces */
  947. if ( ((Integer)opts.get("exact_phrase")).intValue()!=0 ) iFlags |= 2;
  948. if ( ((Integer)opts.get("single_passage")).intValue()!=0 ) iFlags |= 4;
  949. if ( ((Integer)opts.get("use_boundaries")).intValue()!=0 ) iFlags |= 8;
  950. if ( ((Integer)opts.get("weight_order")).intValue()!=0 ) iFlags |= 16;
  951. req.writeInt ( iFlags );
  952. writeNetUTF8 ( req, index );
  953. writeNetUTF8 ( req, words );
  954. /* send options */
  955. writeNetUTF8 ( req, (String) opts.get("before_match") );
  956. writeNetUTF8 ( req, (String) opts.get("after_match") );
  957. writeNetUTF8 ( req, (String) opts.get("chunk_separator") );
  958. req.writeInt ( ((Integer) opts.get("limit")).intValue() );
  959. req.writeInt ( ((Integer) opts.get("around")).intValue() );
  960. /* send documents */
  961. req.writeInt ( docs.length );
  962. for ( int i=0; i<docs.length; i++ )
  963. writeNetUTF8 ( req, docs[i] );
  964. req.flush();
  965. } catch ( Exception e )
  966. {
  967. _error = "internal error: failed to build request: " + e;
  968. return null;
  969. }
  970. DataInputStream in = _DoRequest ( SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, reqBuf );
  971. if ( in==null )
  972. return null;
  973. try
  974. {
  975. String[] res = new String [ docs.length ];
  976. for ( int i=0; i<docs.length; i++ )
  977. res[i] = readNetUTF8 ( in );
  978. return res;
  979. } catch ( Exception e )
  980. {
  981. _error = "incomplete reply";
  982. return null;
  983. }
  984. }
  985. /**
  986. * Connect to searchd server and update given attributes on given documents in given indexes.
  987. * Sample code that will set group_id=123 where id=1 and group_id=456 where id=3:
  988. *
  989. * <pre>
  990. * String[] attrs = new String[1];
  991. *
  992. * attrs[0] = "group_id";
  993. * long[][] values = new long[2][2];
  994. *
  995. * values[0] = new long[2]; values[0][0] = 1; values[0][1] = 123;
  996. * values[1] = new long[2]; values[1][0] = 3; values[1][1] = 456;
  997. *
  998. * int res = cl.UpdateAttributes ( "test1", attrs, values );
  999. * </pre>
  1000. *
  1001. * @param index index name(s) to update; might be distributed
  1002. * @param attrs array with the names of the attributes to update
  1003. * @param values array of updates; each long[] entry must contains document ID
  1004. * in the first element, and all new attribute values in the following ones
  1005. * @return -1 on failure, amount of actually found and updated documents (might be 0) on success
  1006. *
  1007. * @throws SphinxException on invalid parameters
  1008. */
  1009. public int UpdateAttributes ( String index, String[] attrs, long[][] values ) throws SphinxException
  1010. {
  1011. /* check args */
  1012. myAssert ( index!=null && index.length()>0, "no index name provided" );
  1013. myAssert ( attrs!=null && attrs.length>0, "no attribute names provided" );
  1014. myAssert ( values!=null && values.length>0, "no update entries provided" );
  1015. for ( int i=0; i<values.length; i++ )
  1016. {
  1017. myAssert ( values[i]!=null, "update entry #" + i + " is null" );
  1018. myAssert ( values[i].length==1+attrs.length, "update entry #" + i + " has wrong length" );
  1019. }
  1020. /* build and send request */
  1021. ByteArrayOutputStream reqBuf = new ByteArrayOutputStream();
  1022. DataOutputStream req = new DataOutputStream ( reqBuf );
  1023. try
  1024. {
  1025. writeNetUTF8 ( req, index );
  1026. req.writeInt ( attrs.length );
  1027. for ( int i=0; i<attrs.length; i++ )
  1028. writeNetUTF8 ( req, attrs[i] );
  1029. req.writeInt ( values.length );
  1030. for ( int i=0; i<values.length; i++ )
  1031. {
  1032. req.writeLong ( values[i][0] ); /* send docid as 64bit value */
  1033. for ( int j=1; j<values[i].length; j++ )
  1034. req.writeInt ( (int)values[i][j] ); /* send values as 32bit values; FIXME! what happens when they are over 2^31? */
  1035. }
  1036. req.flush();
  1037. } catch ( Exception e )
  1038. {
  1039. _error = "internal error: failed to build request: " + e;
  1040. return -1;
  1041. }
  1042. /* get and parse response */
  1043. DataInputStream in = _DoRequest ( SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, reqBuf );
  1044. if ( in==null )
  1045. return -1;
  1046. try
  1047. {
  1048. return in.readInt ();
  1049. } catch ( Exception e )
  1050. {
  1051. _error = "incomplete reply";
  1052. return -1;
  1053. }
  1054. }
  1055. /**
  1056. * Connect to searchd server, and generate keyword list for a given query.
  1057. * Returns null on failure, an array of Maps with misc per-keyword info on success.
  1058. */
  1059. public Map[] BuildKeywords ( String query, String index, boolean hits ) throws SphinxException
  1060. {
  1061. /* build request */
  1062. ByteArrayOutputStream reqBuf = new ByteArrayOutputStream();
  1063. DataOutputStream req = new DataOutputStream ( reqBuf );
  1064. try
  1065. {
  1066. writeNetUTF8 ( req, query );
  1067. writeNetUTF8 ( req, index );
  1068. req.writeInt ( hits ? 1 : 0 );
  1069. } catch ( Exception e )
  1070. {
  1071. _error = "internal error: failed to build request: " + e;
  1072. return null;
  1073. }
  1074. /* run request */
  1075. DataInputStream in = _DoRequest ( SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, reqBuf );
  1076. if ( in==null )
  1077. return null;
  1078. /* parse reply */
  1079. try
  1080. {
  1081. int iNumWords = in.readInt ();
  1082. Map[] res = new Map[iNumWords];
  1083. for ( int i=0; i<iNumWords; i++ )
  1084. {
  1085. res[i] = new LinkedHashMap ();
  1086. res[i].put ( "tokenized", readNetUTF8 ( in ) );
  1087. res[i].put ( "normalized", readNetUTF8 ( in ) );
  1088. if ( hits )
  1089. {
  1090. res[i].put ( "docs", new Long ( readDword ( in ) ) );
  1091. res[i].put ( "hits", new Long ( readDword ( in ) ) );
  1092. }
  1093. }
  1094. return res;
  1095. } catch ( Exception e )
  1096. {
  1097. _error = "incomplete reply";
  1098. return null;
  1099. }
  1100. }
  1101. /** Escape the characters with special meaning in query syntax. */
  1102. static public String EscapeString ( String s )
  1103. {
  1104. return s.replaceAll ( "([\\(\\)\\|\\-\\!\\@\\~\\\"\\&\\/\\^\\$\\=])", "\\\\$1" );
  1105. }
  1106. /** Open persistent connection to searchd. */
  1107. public boolean Open()
  1108. {
  1109. if ( _socket!=null )
  1110. {
  1111. _error = "already connected";
  1112. return false;
  1113. }
  1114. Socket sock = _Connect();
  1115. if ( sock==null )
  1116. return false;
  1117. // command, command version = 0, body length = 4, body = 1
  1118. try
  1119. {
  1120. DataOutputStream sOut = new DataOutputStream ( sock.getOutputStream() );
  1121. sOut.writeShort ( SEARCHD_COMMAND_PERSIST );
  1122. sOut.writeShort ( 0 );
  1123. sOut.writeInt ( 4 );
  1124. sOut.writeInt ( 1 );
  1125. } catch ( IOException e )
  1126. {
  1127. _error = "network error: " + e;
  1128. _connerror = true;
  1129. }
  1130. _socket = sock;
  1131. return true;
  1132. }
  1133. /** Close existing persistent connection. */
  1134. public boolean Close()
  1135. {
  1136. if ( _socket==null )
  1137. {
  1138. _error = "not connected";
  1139. return false;
  1140. }
  1141. try
  1142. {
  1143. _socket.close();
  1144. } catch ( IOException e )
  1145. {}
  1146. _socket = null;
  1147. return true;
  1148. }
  1149. }
  1150. /*
  1151. * $Id$
  1152. */