SphinxClient.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  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. /* sorting modes */
  31. public final static int SPH_SORT_RELEVANCE = 0;
  32. public final static int SPH_SORT_ATTR_DESC = 1;
  33. public final static int SPH_SORT_ATTR_ASC = 2;
  34. public final static int SPH_SORT_TIME_SEGMENTS = 3;
  35. public final static int SPH_SORT_EXTENDED = 4;
  36. public final static int SPH_SORT_EXPR = 5;
  37. /* grouping functions */
  38. public final static int SPH_GROUPBY_DAY = 0;
  39. public final static int SPH_GROUPBY_WEEK = 1;
  40. public final static int SPH_GROUPBY_MONTH = 2;
  41. public final static int SPH_GROUPBY_YEAR = 3;
  42. public final static int SPH_GROUPBY_ATTR = 4;
  43. public final static int SPH_GROUPBY_ATTRPAIR = 5;
  44. /* searchd reply status codes */
  45. public final static int SEARCHD_OK = 0;
  46. public final static int SEARCHD_ERROR = 1;
  47. public final static int SEARCHD_RETRY = 2;
  48. public final static int SEARCHD_WARNING = 3;
  49. /* attribute types */
  50. public final static int SPH_ATTR_INTEGER = 1;
  51. public final static int SPH_ATTR_TIMESTAMP = 2;
  52. public final static int SPH_ATTR_ORDINAL = 3;
  53. public final static int SPH_ATTR_BOOL = 4;
  54. public final static int SPH_ATTR_FLOAT = 5;
  55. public final static int SPH_ATTR_MULTI = 0x40000000;
  56. /* searchd commands */
  57. private final static int SEARCHD_COMMAND_SEARCH = 0;
  58. private final static int SEARCHD_COMMAND_EXCERPT= 1;
  59. private final static int SEARCHD_COMMAND_UPDATE = 2;
  60. /* searchd command versions */
  61. private final static int VER_MAJOR_PROTO = 0x1;
  62. private final static int VER_COMMAND_SEARCH = 0x10F;
  63. private final static int VER_COMMAND_EXCERPT = 0x100;
  64. private final static int VER_COMMAND_UPDATE = 0x101;
  65. /* filter types */
  66. private final static int SPH_FILTER_VALUES = 0;
  67. private final static int SPH_FILTER_RANGE = 1;
  68. private final static int SPH_FILTER_FLOATRANGE = 2;
  69. private String _host;
  70. private int _port;
  71. private int _offset;
  72. private int _limit;
  73. private int _mode;
  74. private int[] _weights;
  75. private int _sort;
  76. private String _sortby;
  77. private int _minId;
  78. private int _maxId;
  79. private ByteArrayOutputStream _rawFilters;
  80. private DataOutputStream _filters;
  81. private int _filterCount;
  82. private String _groupBy;
  83. private int _groupFunc;
  84. private String _groupSort;
  85. private String _groupDistinct;
  86. private int _maxMatches;
  87. private int _cutoff;
  88. private int _retrycount;
  89. private int _retrydelay;
  90. private String _latitudeAttr;
  91. private String _longitudeAttr;
  92. private float _latitude;
  93. private float _longitude;
  94. private String _error;
  95. private String _warning;
  96. private ArrayList _reqs;
  97. private Map _indexWeights;
  98. private static final int SPH_CLIENT_TIMEOUT_MILLISEC = 30000;
  99. /**
  100. * Creates new SphinxClient instance.
  101. *
  102. * Default host and port that the instance will connect to are
  103. * localhost:3312. That can be overriden using {@link #SetServer SetServer()}.
  104. */
  105. public SphinxClient()
  106. {
  107. this("localhost", 3312);
  108. }
  109. /**
  110. * Creates new SphinxClient instance, with host:port specification.
  111. *
  112. * Host and port can be later overriden using {@link #SetServer SetServer()}.
  113. *
  114. * @param host searchd host name (default: localhost)
  115. * @param port searchd port number (default: 3312)
  116. */
  117. public SphinxClient(String host, int port)
  118. {
  119. _host = host;
  120. _port = port;
  121. _offset = 0;
  122. _limit = 20;
  123. _mode = SPH_MATCH_ALL;
  124. _sort = SPH_SORT_RELEVANCE;
  125. _sortby = "";
  126. _minId = 0;
  127. _maxId = 0xFFFFFFFF;
  128. _filterCount = 0;
  129. _rawFilters = new ByteArrayOutputStream();
  130. _filters = new DataOutputStream(_rawFilters);
  131. _groupBy = "";
  132. _groupFunc = SPH_GROUPBY_DAY;
  133. _groupSort = "@group desc";
  134. _groupDistinct = "";
  135. _maxMatches = 1000;
  136. _cutoff = 0;
  137. _retrycount = 0;
  138. _retrydelay = 0;
  139. _latitudeAttr = null;
  140. _longitudeAttr = null;
  141. _latitude = 0;
  142. _longitude = 0;
  143. _error = "";
  144. _warning = "";
  145. _reqs = new ArrayList();
  146. _weights = null;
  147. _indexWeights = new LinkedHashMap();
  148. }
  149. /**
  150. * Get last error message, if any.
  151. *
  152. * @return string with last error message (empty string if no errors occured)
  153. */
  154. public String GetLastError()
  155. {
  156. return _error;
  157. }
  158. /**
  159. * Get last warning message, if any.
  160. *
  161. * @return string with last warning message (empty string if no errors occured)
  162. */
  163. public String GetLastWarning()
  164. {
  165. return _warning;
  166. }
  167. /**
  168. * Set searchd host and port to connect to.
  169. *
  170. * @param host searchd host name (default: localhost)
  171. * @param port searchd port number (default: 3312)
  172. *
  173. * @throws SphinxException on invalid parameters
  174. */
  175. public void SetServer(String host, int port) throws SphinxException
  176. {
  177. myAssert ( host!=null && host.length()>0, "host name must not be empty" );
  178. myAssert ( port>0 && port<65536, "port must be in 1..65535 range" );
  179. _host = host;
  180. _port = port;
  181. }
  182. /** Connect to searchd and exchange versions (internal method). */
  183. private Socket _Connect()
  184. {
  185. Socket sock;
  186. try {
  187. sock = new Socket(_host, _port);
  188. sock.setSoTimeout(SPH_CLIENT_TIMEOUT_MILLISEC);
  189. } catch (IOException e) {
  190. _error = "connection to " + _host + ":" + _port + " failed: " + e.getMessage();
  191. return null;
  192. }
  193. DataInputStream sIn;
  194. DataOutputStream sOut;
  195. try {
  196. InputStream sockInput = sock.getInputStream();
  197. OutputStream sockOutput = sock.getOutputStream();
  198. sIn = new DataInputStream(sockInput);
  199. int version = sIn.readInt();
  200. if (version < 1) {
  201. sock.close();
  202. _error = "expected searchd protocol version 1+, got version " + version;
  203. return null;
  204. }
  205. sOut = new DataOutputStream(sockOutput);
  206. sOut.writeInt(VER_MAJOR_PROTO);
  207. } catch (IOException e) {
  208. _error = "Connect: Read from socket failed: " + e.getMessage();
  209. try {
  210. sock.close();
  211. } catch (IOException e1) {
  212. _error = _error + " Cannot close socket: " + e1.getMessage();
  213. }
  214. return null;
  215. }
  216. return sock;
  217. }
  218. /** Get and check response packet from searchd (internal method). */
  219. private byte[] _GetResponse ( Socket sock ) throws SphinxException
  220. {
  221. /* connect */
  222. DataInputStream sIn = null;
  223. InputStream SockInput = null;
  224. try
  225. {
  226. SockInput = sock.getInputStream();
  227. sIn = new DataInputStream ( SockInput );
  228. } catch ( IOException e )
  229. {
  230. myAssert ( false, "getInputStream() failed: " + e.getMessage() );
  231. return null;
  232. }
  233. /* read response */
  234. byte[] response = null;
  235. short status = 0, ver = 0;
  236. int len = 0;
  237. try
  238. {
  239. /* read status fields */
  240. status = sIn.readShort();
  241. ver = sIn.readShort();
  242. len = sIn.readInt();
  243. /* read response if non-empty */
  244. myAssert ( len>0, "zero-sized searchd response body" );
  245. if ( len>0 )
  246. {
  247. response = new byte[len];
  248. sIn.readFully ( response, 0, len );
  249. } else
  250. {
  251. /* FIXME! no response, return null? */
  252. }
  253. /* check status */
  254. if ( status==SEARCHD_WARNING )
  255. {
  256. DataInputStream in = new DataInputStream ( new ByteArrayInputStream ( response ) );
  257. int iWarnLen = in.readInt ();
  258. _warning = new String ( response, 4, iWarnLen );
  259. System.arraycopy ( response, 4+iWarnLen, response, 0, response.length-4-iWarnLen );
  260. } else if ( status==SEARCHD_ERROR )
  261. {
  262. _error = "searchd error: " + new String ( response, 4, response.length-4 );
  263. return null;
  264. } else if ( status==SEARCHD_RETRY )
  265. {
  266. _error = "temporary searchd error: " + new String ( response, 4, response.length-4 );
  267. return null;
  268. } else if ( status!=SEARCHD_OK )
  269. {
  270. _error = "searched returned unknown status, code=" + status;
  271. return null;
  272. }
  273. } catch ( IOException e )
  274. {
  275. if ( len!=0 )
  276. {
  277. /* get trace, to provide even more failure details */
  278. PrintWriter ew = new PrintWriter ( new StringWriter() );
  279. e.printStackTrace ( ew );
  280. ew.flush ();
  281. ew.close ();
  282. String sTrace = ew.toString ();
  283. /* build error message */
  284. _error = "failed to read searchd response (status=" + status + ", ver=" + ver + ", len=" + len + ", trace=" + sTrace +")";
  285. } else
  286. {
  287. _error = "received zero-sized searchd response (searchd crashed?): " + e.getMessage();
  288. }
  289. return null;
  290. } finally
  291. {
  292. try
  293. {
  294. if ( sIn!=null ) sIn.close();
  295. if ( sock!=null && !sock.isConnected() ) sock.close();
  296. } catch ( IOException e )
  297. {
  298. /* silently ignore close failures; nothing could be done anyway */
  299. }
  300. }
  301. return response;
  302. }
  303. /** Set matches offset and limit to return to client, max matches to retrieve on server, and cutoff. */
  304. public void SetLimits ( int offset, int limit, int max, int cutoff ) throws SphinxException
  305. {
  306. myAssert ( offset>=0, "offset must be greater than or equal to 0" );
  307. myAssert ( limit>0, "limit must be greater than 0" );
  308. myAssert ( max>0, "max must be greater than 0" );
  309. myAssert ( cutoff>=0, "max must be greater than or equal to 0" );
  310. _offset = offset;
  311. _limit = limit;
  312. _maxMatches = max;
  313. _cutoff = cutoff;
  314. }
  315. /** Set matches offset and limit to return to client, and max matches to retrieve on server. */
  316. public void SetLimits ( int offset, int limit, int max ) throws SphinxException
  317. {
  318. SetLimits ( offset, limit, max, _cutoff );
  319. }
  320. /** Set matches offset and limit to return to client. */
  321. public void SetLimits ( int offset, int limit) throws SphinxException
  322. {
  323. SetLimits ( offset, limit, _maxMatches, _cutoff );
  324. }
  325. /** Set matching mode. */
  326. public void SetMatchMode(int mode) throws SphinxException
  327. {
  328. myAssert (
  329. mode==SPH_MATCH_ALL ||
  330. mode==SPH_MATCH_ANY ||
  331. mode==SPH_MATCH_PHRASE ||
  332. mode==SPH_MATCH_BOOLEAN ||
  333. mode==SPH_MATCH_EXTENDED, "unknown mode value; use one of the available SPH_MATCH_xxx constants" );
  334. _mode = mode;
  335. }
  336. /** Set sorting mode. */
  337. public void SetSortMode ( int mode, String sortby ) throws SphinxException
  338. {
  339. myAssert (
  340. mode==SPH_SORT_RELEVANCE ||
  341. mode==SPH_SORT_ATTR_DESC ||
  342. mode==SPH_SORT_ATTR_ASC ||
  343. mode==SPH_SORT_TIME_SEGMENTS ||
  344. mode==SPH_SORT_EXTENDED, "unknown mode value; use one of the available SPH_SORT_xxx constants" );
  345. myAssert ( mode==SPH_SORT_RELEVANCE || ( sortby!=null && sortby.length()>0 ), "sortby string must not be empty in selected mode" );
  346. _sort = mode;
  347. _sortby = ( sortby==null ) ? "" : sortby;
  348. }
  349. /** Set per-field weights (all values must be positive). */
  350. public void SetWeights(int[] weights) throws SphinxException
  351. {
  352. myAssert ( weights!=null, "weights must not be null" );
  353. for (int i = 0; i < weights.length; i++) {
  354. int weight = weights[i];
  355. myAssert ( weight>0, "all weights must be greater than 0" );
  356. }
  357. _weights = weights;
  358. }
  359. /**
  360. * Set per-index weights
  361. *
  362. * @param indexWeights hash which maps String index names to Integer weights
  363. */
  364. public void SetIndexWeights ( Map indexWeights ) throws SphinxException
  365. {
  366. /* FIXME! implement checks here */
  367. _indexWeights = indexWeights;
  368. }
  369. /**
  370. * Set document IDs range to match.
  371. *
  372. * Only match those documents where document ID is beetwen given
  373. * min and max values (including themselves).
  374. *
  375. * @param min minimum document ID to match
  376. * @param max maximum document ID to match
  377. *
  378. * @throws SphinxException on invalid parameters
  379. */
  380. public void SetIDRange ( int min, int max ) throws SphinxException
  381. {
  382. myAssert ( min<=max, "min must be less or equal to max" );
  383. _minId = min;
  384. _maxId = max;
  385. }
  386. /**
  387. * Set values filter.
  388. *
  389. * Only match those documents where <code>attribute</code> column value
  390. * is in given values set.
  391. *
  392. * @param attribute attribute name to filter by
  393. * @param values values set to match the attribute value by
  394. * @param exclude whether to exclude matching documents instead
  395. *
  396. * @throws SphinxException on invalid parameters
  397. */
  398. public void SetFilter ( String attribute, int[] values, boolean exclude ) throws SphinxException
  399. {
  400. myAssert ( values!=null && values.length>0, "values array must not be null or empty" );
  401. myAssert ( attribute!=null && attribute.length()>0, "attribute name must not be null or empty" );
  402. try
  403. {
  404. writeNetUTF8 ( _filters, attribute );
  405. _filters.writeInt ( SPH_FILTER_VALUES );
  406. _filters.writeInt ( values.length );
  407. for ( int i=0; i<values.length; i++ )
  408. _filters.writeInt ( values[i] );
  409. _filters.writeInt ( exclude ? 1 : 0 );
  410. } catch ( Exception e )
  411. {
  412. myAssert ( false, "IOException: " + e.getMessage() );
  413. }
  414. _filterCount++;
  415. }
  416. /** Set values filter with a single value (syntax sugar; see {@link #SetFilter(String,int[],boolean)}). */
  417. public void SetFilter ( String attribute, int value, boolean exclude ) throws SphinxException
  418. {
  419. int[] values = new int[] { value };
  420. SetFilter ( attribute, values, exclude );
  421. }
  422. /**
  423. * Set integer range filter.
  424. *
  425. * Only match those documents where <code>attribute</code> column value
  426. * is beetwen given min and max values (including themselves).
  427. *
  428. * @param attribute attribute name to filter by
  429. * @param min min attribute value
  430. * @param max max attribute value
  431. * @param exclude whether to exclude matching documents instead
  432. *
  433. * @throws SphinxException on invalid parameters
  434. */
  435. public void SetFilterRange ( String attribute, int min, int max, boolean exclude ) throws SphinxException
  436. {
  437. myAssert ( min<=max, "min must be less or equal to max" );
  438. try
  439. {
  440. writeNetUTF8 ( _filters, attribute );
  441. _filters.writeInt ( SPH_FILTER_RANGE );
  442. _filters.writeInt ( min );
  443. _filters.writeInt ( max );
  444. _filters.writeInt ( exclude ? 1 : 0 );
  445. } catch ( Exception e )
  446. {
  447. myAssert ( false, "IOException: " + e.getMessage() );
  448. }
  449. _filterCount++;
  450. }
  451. /**
  452. * Set float range filter.
  453. *
  454. * Only match those documents where <code>attribute</code> column value
  455. * is beetwen given min and max values (including themselves).
  456. *
  457. * @param attribute attribute name to filter by
  458. * @param min min attribute value
  459. * @param max max attribute value
  460. * @param exclude whether to exclude matching documents instead
  461. *
  462. * @throws SphinxException on invalid parameters
  463. * Set float range filter.
  464. */
  465. public void SetFilterFloatRange ( String attribute, float min, float max, boolean exclude ) throws SphinxException
  466. {
  467. myAssert ( min<=max, "min must be less or equal to max" );
  468. try
  469. {
  470. writeNetUTF8 ( _filters, attribute );
  471. _filters.writeInt ( SPH_FILTER_RANGE );
  472. _filters.writeFloat ( min );
  473. _filters.writeFloat ( max );
  474. _filters.writeInt ( exclude ? 1 : 0 );
  475. } catch ( Exception e )
  476. {
  477. myAssert ( false, "IOException: " + e.getMessage() );
  478. }
  479. _filterCount++;
  480. }
  481. /**
  482. * Setup geographical anchor point.
  483. *
  484. * Required to use @geodist in filters and sorting.
  485. * Distance will be computed to this point.
  486. *
  487. * @param latitudeAttr the name of latitude attribute
  488. * @param longitudeAttr the name of longitude attribute
  489. * @param latitude anchor point latitude, in radians
  490. * @param longitude anchor point longitude, in radians
  491. *
  492. * @throws SphinxException on invalid parameters
  493. */
  494. public void SetGeoAnchor ( String latitudeAttr, String longitudeAttr, float latitude, float longitude ) throws SphinxException
  495. {
  496. myAssert ( latitudeAttr!=null && latitudeAttr.length()>0, "longitudeAttr string must not be null or empty" );
  497. myAssert ( longitudeAttr!=null && longitudeAttr.length()>0, "longitudeAttr string must not be null or empty" );
  498. _latitudeAttr = latitudeAttr;
  499. _longitudeAttr = longitudeAttr;
  500. _latitude = latitude;
  501. _longitude = longitude;
  502. }
  503. /** Set grouping attribute and function. */
  504. public void SetGroupBy ( String attribute, int func, String groupsort ) throws SphinxException
  505. {
  506. myAssert (
  507. func==SPH_GROUPBY_DAY ||
  508. func==SPH_GROUPBY_WEEK ||
  509. func==SPH_GROUPBY_MONTH ||
  510. func==SPH_GROUPBY_YEAR ||
  511. func==SPH_GROUPBY_ATTR ||
  512. func==SPH_GROUPBY_ATTRPAIR, "unknown func value; use one of the available SPH_GROUPBY_xxx constants" );
  513. _groupBy = attribute;
  514. _groupFunc = func;
  515. _groupSort = groupsort;
  516. }
  517. /** Set grouping attribute and function with default ("@group desc") groupsort (syntax sugar). */
  518. public void SetGroupBy(String attribute, int func) throws SphinxException
  519. {
  520. SetGroupBy(attribute, func, "@group desc");
  521. }
  522. /** Set count-distinct attribute for group-by queries. */
  523. public void SetGroupDistinct(String attribute)
  524. {
  525. _groupDistinct = attribute;
  526. }
  527. /** Set distributed retries count and delay. */
  528. public void SetRetries ( int count, int delay ) throws SphinxException
  529. {
  530. myAssert ( count>=0, "count must not be negative" );
  531. myAssert ( delay>=0, "delay must not be negative" );
  532. _retrycount = count;
  533. _retrydelay = delay;
  534. }
  535. /** Set distributed retries count with default (zero) delay (syntax sugar). */
  536. public void SetRetries ( int count ) throws SphinxException
  537. {
  538. SetRetries ( count, 0 );
  539. }
  540. /** Reset all currently set filters (for multi-queries). */
  541. public void ResetFilters()
  542. {
  543. /* should we close them first? */
  544. _rawFilters = new ByteArrayOutputStream();
  545. _filters = new DataOutputStream(_rawFilters);
  546. _filterCount = 0;
  547. /* reset GEO anchor */
  548. _latitudeAttr = null;
  549. _longitudeAttr = null;
  550. _latitude = 0;
  551. _longitude = 0;
  552. }
  553. /** Connect to searchd server and run current search query against all indexes (syntax sugar). */
  554. public SphinxResult Query(String query) throws SphinxException
  555. {
  556. return Query(query, "*");
  557. }
  558. /**
  559. * Connect to searchd server and run current search query.
  560. *
  561. * @param query query string
  562. * @param index index name(s) to query. May contain anything-separated
  563. * list of index names, or "*" which means to query all indexes.
  564. * @return {@link SphinxResult} object
  565. *
  566. * @throws SphinxException on invalid parameters
  567. */
  568. public SphinxResult Query ( String query, String index ) throws SphinxException
  569. {
  570. myAssert ( _reqs==null || _reqs.size()==0, "AddQuery() and Query() can not be combined; use RunQueries() instead" );
  571. AddQuery(query, index);
  572. SphinxResult[] results = RunQueries();
  573. if (results == null || results.length < 1) {
  574. return null; /* probably network error; error message should be already filled */
  575. }
  576. SphinxResult res = results[0];
  577. _warning = res.warning;
  578. _error = res.error;
  579. if (res == null || res.getStatus() == SEARCHD_ERROR) {
  580. return null;
  581. } else {
  582. return res;
  583. }
  584. }
  585. /** Add new query with current settings to current search request. */
  586. public int AddQuery ( String query, String index ) throws SphinxException
  587. {
  588. ByteArrayOutputStream req = new ByteArrayOutputStream();
  589. /* build request */
  590. try {
  591. DataOutputStream out = new DataOutputStream(req);
  592. out.writeInt(_offset);
  593. out.writeInt(_limit);
  594. out.writeInt(_mode);
  595. out.writeInt(_sort);
  596. writeNetUTF8(out, _sortby);
  597. writeNetUTF8(out, query);
  598. int weightLen = _weights != null ? _weights.length : 0;
  599. out.writeInt(weightLen);
  600. if (_weights != null) {
  601. for (int i = 0; i < _weights.length; i++)
  602. out.writeInt(_weights[i]);
  603. }
  604. writeNetUTF8(out, index);
  605. out.writeInt(0);
  606. out.writeInt(_minId);
  607. out.writeInt(_maxId);
  608. /* filters */
  609. out.writeInt(_filterCount);
  610. out.write(_rawFilters.toByteArray());
  611. /* group-by, max matches, sort-by-group flag */
  612. out.writeInt(_groupFunc);
  613. writeNetUTF8(out, _groupBy);
  614. out.writeInt(_maxMatches);
  615. writeNetUTF8(out, _groupSort);
  616. out.writeInt(_cutoff);
  617. out.writeInt(_retrycount);
  618. out.writeInt(_retrydelay);
  619. writeNetUTF8(out, _groupDistinct);
  620. /* anchor point */
  621. if (_latitudeAttr == null || _latitudeAttr.length() == 0 || _longitudeAttr == null || _longitudeAttr.length() == 0) {
  622. out.writeInt(0);
  623. } else {
  624. out.writeInt(1);
  625. writeNetUTF8(out, _latitudeAttr);
  626. writeNetUTF8(out, _longitudeAttr);
  627. out.writeFloat(_latitude);
  628. out.writeFloat(_longitude);
  629. }
  630. /* per-index weights */
  631. out.writeInt(_indexWeights.size());
  632. for (Iterator e = _indexWeights.keySet().iterator(); e.hasNext();) {
  633. String indexName = (String) e.next();
  634. Integer weight = (Integer) _indexWeights.get(indexName);
  635. writeNetUTF8(out, indexName);
  636. out.writeInt(weight.intValue());
  637. }
  638. out.flush();
  639. int qIndex = _reqs.size();
  640. _reqs.add(qIndex, req.toByteArray());
  641. return qIndex;
  642. } catch (Exception ex) {
  643. myAssert(false, "error on AddQuery: " + ex.getMessage());
  644. } finally {
  645. try {
  646. _filters.close();
  647. _rawFilters.close();
  648. } catch (IOException e) {
  649. myAssert(false, "error on AddQuery: " + e.getMessage());
  650. }
  651. }
  652. return -1;
  653. }
  654. /** Run all previously added search queries. */
  655. public SphinxResult[] RunQueries() throws SphinxException
  656. {
  657. if (_reqs == null || _reqs.size() < 1) {
  658. _error = "no queries defined, issue AddQuery() first";
  659. return null;
  660. }
  661. Socket sock = _Connect();
  662. if (sock == null) return null;
  663. /* send query, get response */
  664. ByteArrayOutputStream req = new ByteArrayOutputStream();
  665. DataOutputStream prepareRQ = null;
  666. int nreqs = _reqs.size();
  667. try {
  668. prepareRQ = new DataOutputStream(req);
  669. prepareRQ.writeShort(SEARCHD_COMMAND_SEARCH);
  670. prepareRQ.writeShort(VER_COMMAND_SEARCH);
  671. int rqLen = 4;
  672. for (int i = 0; i < nreqs; i++) {
  673. byte[] subRq = (byte[]) _reqs.get(i);
  674. rqLen += subRq.length;
  675. }
  676. prepareRQ.writeInt(rqLen);
  677. prepareRQ.writeInt(nreqs);
  678. for (int i = 0; i < nreqs; i++) {
  679. byte[] subRq = (byte[]) _reqs.get(i);
  680. prepareRQ.write(subRq);
  681. }
  682. OutputStream SockOut = sock.getOutputStream();
  683. byte[] reqBytes = req.toByteArray();
  684. SockOut.write(reqBytes);
  685. } catch (Exception e) {
  686. myAssert(false, "Query: Unable to create read/write streams: " + e.getMessage());
  687. return null;
  688. }
  689. /* reset requests */
  690. _reqs = new ArrayList();
  691. /* get response */
  692. byte[] response = null;
  693. response = _GetResponse ( sock );
  694. if (response == null) return null;
  695. /* parse response */
  696. SphinxResult[] results = new SphinxResult[nreqs];
  697. DataInputStream in;
  698. in = new DataInputStream(new ByteArrayInputStream(response));
  699. /* read schema */
  700. int ires;
  701. try {
  702. for (ires = 0; ires < nreqs; ires++) {
  703. SphinxResult res = new SphinxResult();
  704. results[ires] = res;
  705. int status = in.readInt();
  706. res.setStatus(status);
  707. if (status != SEARCHD_OK) {
  708. String message = readNetUTF8(in);
  709. if (status == SEARCHD_WARNING) {
  710. res.warning = message;
  711. } else {
  712. res.error = message;
  713. continue;
  714. }
  715. }
  716. /* read fields */
  717. int nfields = in.readInt();
  718. res.fields = new String[nfields];
  719. int pos = 0;
  720. for (int i = 0; i < nfields; i++)
  721. res.fields[i] = readNetUTF8(in);
  722. /* read arrts */
  723. int nattrs = in.readInt();
  724. res.attrTypes = new int[nattrs];
  725. res.attrNames = new String[nattrs];
  726. for (int i = 0; i < nattrs; i++) {
  727. String AttrName = readNetUTF8(in);
  728. int AttrType = in.readInt();
  729. res.attrNames[i] = AttrName;
  730. res.attrTypes[i] = AttrType;
  731. }
  732. /* read match count */
  733. int count = in.readInt();
  734. int id64 = in.readInt();
  735. res.matches = new SphinxMatch[count];
  736. for ( int matchesNo=0; matchesNo<count; matchesNo++ )
  737. {
  738. SphinxMatch docInfo;
  739. docInfo = new SphinxMatch (
  740. ( id64==0 ) ? readDword(in) : in.readLong(),
  741. in.readInt() );
  742. /* read matches */
  743. for (int attrNumber = 0; attrNumber < res.attrTypes.length; attrNumber++)
  744. {
  745. String attrName = res.attrNames[attrNumber];
  746. int type = res.attrTypes[attrNumber];
  747. /* handle floats */
  748. if ( type==SPH_ATTR_FLOAT )
  749. {
  750. docInfo.attrValues.add ( attrNumber, new Float ( in.readFloat() ) );
  751. continue;
  752. }
  753. /* handle everything else as unsigned ints */
  754. long val = readDword ( in );
  755. if ( ( type & SPH_ATTR_MULTI )!=0 )
  756. {
  757. long[] vals = new long [ (int)val ];
  758. for ( int k=0; k<val; k++ )
  759. vals[k] = readDword ( in );
  760. docInfo.attrValues.add ( attrNumber, vals );
  761. } else
  762. {
  763. docInfo.attrValues.add ( attrNumber, new Long ( val ) );
  764. }
  765. }
  766. res.matches[matchesNo] = docInfo;
  767. }
  768. res.total = in.readInt();
  769. res.totalFound = in.readInt();
  770. res.time = in.readInt() / 1000.0f;
  771. res.words = new SphinxWordInfo [ in.readInt() ];
  772. for ( int i=0; i<res.words.length; i++ )
  773. res.words[i] = new SphinxWordInfo ( readNetUTF8(in), readDword(in), readDword(in) );
  774. }
  775. in.close();
  776. return results;
  777. } catch ( IOException e )
  778. {
  779. myAssert ( false, "unable to parse response: " + e.getMessage() );
  780. } finally
  781. {
  782. try
  783. {
  784. in.close ();
  785. } catch ( IOException e )
  786. {
  787. myAssert ( false, "unable to close DataInputStream: " + e.getMessage() );
  788. }
  789. }
  790. return null;
  791. }
  792. /**
  793. * Connect to searchd server and generate exceprts from given documents.
  794. *
  795. * @param docs an array of strings which represent the documents' contents
  796. * @param index a string with the name of the index which settings will be used for stemming, lexing and case folding
  797. * @param words a string which contains the query words to highlight
  798. * @param opts a hash (String keys, String/Int values) with optional highlighting parameters:
  799. * <ul>
  800. * <li>"before_match" - a string to insert before a set of matching words (default is "&lt;b&gt;");
  801. * <li>"after_match" - a string to insert after a set of matching words (default is "&lt;/b&gt;");
  802. * <li>"chunk_separator" - a string to insert between excerpts chunks (default is "...");
  803. * <li>"limit" - max excerpt size in codepoints (default is 256);
  804. * <li>"around" - how much words to highlight around each match (default is 5).
  805. * </ul>
  806. * @return null on failure, an array of string excerpts on success
  807. *
  808. * @throws SphinxException on invalid parameters
  809. */
  810. public String[] BuildExcerpts ( String[] docs, String index, String words, Map opts ) throws SphinxException
  811. {
  812. myAssert(docs != null && docs.length > 0, "BuildExcerpts: Have no documents to process");
  813. myAssert(index != null && index.length() > 0, "BuildExcerpts: Have no index to process documents");
  814. myAssert(words != null && words.length() > 0, "BuildExcerpts: Have no words to highlight");
  815. if (opts == null) opts = new LinkedHashMap();
  816. Socket sock = _Connect();
  817. if (sock == null) return null;
  818. /* fixup options */
  819. if (!opts.containsKey("before_match")) opts.put("before_match", "<b>");
  820. if (!opts.containsKey("after_match")) opts.put("after_match", "</b>");
  821. if (!opts.containsKey("chunk_separator")) opts.put("chunk_separator", "...");
  822. if (!opts.containsKey("limit")) opts.put("limit", new Integer(256));
  823. if (!opts.containsKey("around")) opts.put("around", new Integer(5));
  824. if (!opts.containsKey("exact_phrase")) opts.put("exact_phrase", new Integer(0));
  825. if (!opts.containsKey("single_passage")) opts.put("single_passage", new Integer(0));
  826. if (!opts.containsKey("use_boundaries")) opts.put("use_boundaries", new Integer(0));
  827. if (!opts.containsKey("weight_order")) opts.put("weight_order", new Integer(0));
  828. /* build request */
  829. ByteArrayOutputStream req = new ByteArrayOutputStream();
  830. DataOutputStream rqData = null;
  831. DataOutputStream socketDS = null;
  832. try {
  833. rqData = new DataOutputStream(req);
  834. /* v.1.0 req */
  835. rqData.writeInt(0);
  836. int iFlags = 1; /* remove_spaces */
  837. if ( ((Integer)opts.get("exact_phrase"))!=0 ) iFlags |= 2;
  838. if ( ((Integer)opts.get("single_passage"))!=0 ) iFlags |= 4;
  839. if ( ((Integer)opts.get("use_boundaries"))!=0 ) iFlags |= 8;
  840. if ( ((Integer)opts.get("weight_order"))!=0 ) iFlags |= 16;
  841. rqData.writeInt ( iFlags );
  842. writeNetUTF8(rqData, index);
  843. writeNetUTF8(rqData, words);
  844. /* send options */
  845. writeNetUTF8(rqData, (String) opts.get("before_match"));
  846. writeNetUTF8(rqData, (String) opts.get("after_match"));
  847. writeNetUTF8(rqData, (String) opts.get("chunk_separator"));
  848. rqData.writeInt(((Integer) opts.get("limit")).intValue());
  849. rqData.writeInt(((Integer) opts.get("around")).intValue());
  850. /* send documents */
  851. for (int i = 0; i < docs.length; i++) {
  852. myAssert(docs[i] != null, "BuildExcerpts: empty document #" + i);
  853. writeNetUTF8(rqData, docs[i]);
  854. }
  855. rqData.flush();
  856. byte[] byteRq = req.toByteArray();
  857. /* send query, get response */
  858. OutputStream SockOut = sock.getOutputStream();
  859. socketDS = new DataOutputStream(SockOut);
  860. socketDS.writeShort(SEARCHD_COMMAND_EXCERPT);
  861. socketDS.writeShort(VER_COMMAND_EXCERPT);
  862. socketDS.writeInt(byteRq.length + 8);
  863. socketDS.write(byteRq);
  864. } catch (Exception ex) {
  865. myAssert(false, "BuildExcerpts: Unable to create read/write streams: " + ex.getMessage());
  866. return null;
  867. }
  868. try {
  869. /* get response */
  870. byte[] response = null;
  871. String[] docsXrpt = new String[docs.length];
  872. response = _GetResponse ( sock );
  873. if (response == null) return null;
  874. /* parse response */
  875. DataInputStream in;
  876. in = new DataInputStream(new ByteArrayInputStream(response));
  877. for (int i = 0; i < docs.length; i++) {
  878. docsXrpt[i] = readNetUTF8(in);
  879. }
  880. return docsXrpt;
  881. } catch (Exception e) {
  882. myAssert(false, "BuildExcerpts: incomplete response " + e.getMessage());
  883. return null;
  884. }
  885. }
  886. /**
  887. * Connect to searchd server and update given attributes on given documents in given indexes.
  888. * Sample code that will set group_id=123 where id=1 and group_id=456 where id=3:
  889. *
  890. * <pre>
  891. * String[] attrs = new String[1];
  892. *
  893. * attrs[0] = "group_id";
  894. * long[][] values = new long[2][2];
  895. *
  896. * values[0] = new long[2]; values[0][0] = 1; values[0][1] = 123;
  897. * values[1] = new long[2]; values[1][0] = 3; values[1][1] = 456;
  898. *
  899. * int res = cl.UpdateAttributes ( "test1", attrs, values );
  900. * </pre>
  901. *
  902. * @param index index name(s) to update; might be distributed
  903. * @param attrs array with the names of the attributes to update
  904. * @param values array of updates; each long[] entry must contains document ID
  905. * in the first element, and all new attribute values in the following ones
  906. * @return -1 on failure, amount of actually found and updated documents (might be 0) on success
  907. *
  908. * @throws SphinxException on invalid parameters
  909. */
  910. public int UpdateAttributes ( String index, String[] attrs, long[][] values ) throws SphinxException
  911. {
  912. /* check args */
  913. myAssert ( index!=null && index.length()>0, "no index name provided" );
  914. myAssert ( attrs!=null && attrs.length>0, "no attribute names provided" );
  915. myAssert ( values!=null && values.length>0, "no update entries provided" );
  916. for ( int i=0; i<values.length; i++ )
  917. {
  918. myAssert ( values[i]!=null, "update entry #" + i + " is null" );
  919. myAssert ( values[i].length==1+attrs.length, "update entry #" + i + " has wrong length" );
  920. }
  921. /* connect */
  922. Socket sock = _Connect();
  923. if ( sock==null )
  924. return -1;
  925. /* build and send request */
  926. ByteArrayOutputStream req = new ByteArrayOutputStream();
  927. DataOutputStream reqData = null;
  928. DataOutputStream socketDS = null;
  929. try
  930. {
  931. reqData = new DataOutputStream ( req );
  932. /* index name */
  933. writeNetUTF8 ( reqData, index );
  934. /* attribute names array */
  935. reqData.writeInt ( attrs.length );
  936. for ( int i=0; i<attrs.length; i++ )
  937. writeNetUTF8 ( reqData, attrs[i] );
  938. /* docid and new values array */
  939. reqData.writeInt ( values.length );
  940. for ( int i=0; i<values.length; i++ )
  941. {
  942. reqData.writeLong ( values[i][0] ); /* send docid as 64bit value */
  943. for ( int j=1; j<values[i].length; j++ )
  944. reqData.writeInt ( (int)values[i][j] ); /* send values as 32bit values; FIXME! what happens when they are over 2^31? */
  945. }
  946. /* send query, get response */
  947. reqData.flush();
  948. byte[] byteReq = req.toByteArray();
  949. OutputStream SockOut = sock.getOutputStream ();
  950. socketDS = new DataOutputStream ( SockOut );
  951. socketDS.writeShort ( SEARCHD_COMMAND_UPDATE );
  952. socketDS.writeShort ( VER_COMMAND_UPDATE );
  953. socketDS.writeInt ( byteReq.length );
  954. socketDS.write ( byteReq );
  955. socketDS.flush ();
  956. } catch ( Exception ex )
  957. {
  958. myAssert ( false, "UpdateAttributes(): unable to create read/write streams: " + ex.getMessage());
  959. return -1;
  960. }
  961. /* get and parse response */
  962. try
  963. {
  964. /* get */
  965. byte[] response = _GetResponse ( sock );
  966. if ( response==null )
  967. return -1;
  968. /* parse */
  969. DataInputStream in;
  970. in = new DataInputStream ( new ByteArrayInputStream ( response ) );
  971. return in.readInt ();
  972. } catch ( Exception e )
  973. {
  974. myAssert(false, "UpdateAttributes(): incomplete response: " + e.getMessage() );
  975. return -1;
  976. }
  977. }
  978. /** Internal sanity check. */
  979. private void myAssert ( boolean condition, String err ) throws SphinxException
  980. {
  981. if ( !condition )
  982. {
  983. _error = err;
  984. throw new SphinxException ( err );
  985. }
  986. }
  987. /** String IO helper (internal method). */
  988. private static void writeNetUTF8(DataOutputStream ostream, String str) throws IOException
  989. {
  990. ostream.writeShort(0);
  991. ostream.writeUTF(str);
  992. }
  993. /** String IO helper (internal method). */
  994. private static String readNetUTF8(DataInputStream istream) throws IOException
  995. {
  996. istream.readUnsignedShort (); /* searchd emits dword lengths, but Java expects words; lets just skip first 2 bytes */
  997. return istream.readUTF ();
  998. }
  999. /** Unsigned int IO helper (internal method). */
  1000. private static long readDword ( DataInputStream istream ) throws IOException
  1001. {
  1002. long v = (long) istream.readInt ();
  1003. if ( v<0 )
  1004. v += 4294967296L;
  1005. return v;
  1006. }
  1007. }
  1008. /*
  1009. * $Id$
  1010. */