SphinxClient.java 39 KB

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