SphinxClient.java 44 KB

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