1 //
2 // $Id$
3 //
4 
5 //
6 // Copyright (c) 2001-2016, Andrew Aksyonoff
7 // Copyright (c) 2008-2016, Sphinx Technologies Inc
8 // All rights reserved
9 //
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License. You should have
12 // received a copy of the GPL license along with this program; if you
13 // did not, you can find it at http://www.gnu.org/
14 //
15 
16 #include "sphinx.h"
17 #include "sphinxutils.h"
18 #include "sphinxexcerpt.h"
19 #include "sphinxrt.h"
20 #include "sphinxint.h"
21 #include "sphinxquery.h"
22 #include "sphinxjson.h"
23 #include "sphinxplugin.h"
24 
25 extern "C"
26 {
27 #include "sphinxudf.h"
28 }
29 
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <time.h>
38 #include <stdarg.h>
39 #include <limits.h>
40 #include <locale.h>
41 
42 #define SEARCHD_BACKLOG			5
43 #define SPHINXAPI_PORT			9312
44 #define SPHINXQL_PORT			9306
45 #define SPH_ADDRESS_SIZE		sizeof("000.000.000.000")
46 #define SPH_ADDRPORT_SIZE		sizeof("000.000.000.000:00000")
47 #define MVA_UPDATES_POOL		1048576
48 #define NETOUTBUF				8192
49 #define PING_INTERVAL			1000
50 #define QLSTATE_FLUSH_MSEC		50
51 
52 // don't shutdown on SIGKILL (debug purposes)
53 // 1 - SIGKILL will shut down the whole daemon; 0 - watchdog will reincarnate the daemon
54 #define WATCHDOG_SIGKILL		1
55 
56 #define SPH_MYSQL_FLAG_MORE_RESULTS 8 // mysql.h: SERVER_MORE_RESULTS_EXISTS
57 
58 /////////////////////////////////////////////////////////////////////////////
59 
60 #if USE_WINDOWS
61 	// Win-specific headers and calls
62 	#include <io.h>
63 	#include <winsock2.h>
64 	#include <tlhelp32.h>
65 
66 	#pragma comment(linker, "/defaultlib:wsock32.lib")
67 	#pragma message("Automatically linking with wsock32.lib")
68 
69 	#define sphSockRecv(_sock,_buf,_len)	::recv(_sock,_buf,_len,0)
70 	#define sphSockSend(_sock,_buf,_len)	::send(_sock,_buf,_len,0)
71 	#define sphSockClose(_sock)				::closesocket(_sock)
72 
73 	#define sphSeek		_lseeki64
74 	#define stat		_stat
75 
76 #else
77 	// UNIX-specific headers and calls
78 	#include <unistd.h>
79 	#include <netinet/in.h>
80 	#include <netinet/tcp.h>
81 	#include <sys/file.h>
82 	#include <sys/socket.h>
83 	#include <sys/time.h>
84 	#include <sys/wait.h>
85 	#include <sys/un.h>
86 	#include <netdb.h>
87 	#include <sys/syscall.h>
88 
89 	#if HAVE_POLL
90 	#include <poll.h>
91 	#endif
92 
93 	#if HAVE_EPOLL
94 	#include <sys/epoll.h>
95 	#endif
96 
97 	// for thr_self()
98 	#ifdef __FreeBSD__
99 	#include <sys/thr.h>
100 	#endif
101 
102 	// there's no MSG_NOSIGNAL on OS X
103 	#ifndef MSG_NOSIGNAL
104 	#define MSG_NOSIGNAL 0
105 	#endif
106 
107 	#define sphSockRecv(_sock,_buf,_len)	::recv(_sock,_buf,_len,MSG_NOSIGNAL)
108 	#define sphSockSend(_sock,_buf,_len)	::send(_sock,_buf,_len,MSG_NOSIGNAL)
109 	#define sphSockClose(_sock)				::close(_sock)
110 	#define sphSeek		lseek
111 #endif
112 
113 #if USE_SYSLOG
114 	#include <syslog.h>
115 #endif
116 
117 /////////////////////////////////////////////////////////////////////////////
118 // MISC GLOBALS
119 /////////////////////////////////////////////////////////////////////////////
120 
121 struct ServedDesc_t
122 {
123 	CSphIndex *			m_pIndex;
124 	CSphString			m_sIndexPath;
125 	bool				m_bEnabled;		///< to disable index in cases when rotation fails
126 	bool				m_bMlock;
127 	bool				m_bPreopen;
128 	bool				m_bExpand;
129 	bool				m_bToDelete;
130 	bool				m_bOnlyNew;
131 	bool				m_bRT;
132 	bool				m_bAlterEnabled;
133 	CSphString			m_sGlobalIDFPath;
134 	bool				m_bOnDiskAttrs;
135 	bool				m_bOnDiskPools;
136 	int64_t				m_iMass; // relative weight (by access speed) of the index
137 
138 						ServedDesc_t ();
139 						~ServedDesc_t ();
140 };
141 
142 struct ServedIndex_t : public ISphNoncopyable, public ServedDesc_t
143 {
144 public:
ServedIndex_tServedIndex_t145 						ServedIndex_t () {}
146 						~ServedIndex_t ();
147 
148 	void				ReadLock () const;
149 	void				WriteLock () const;
150 	void				Unlock () const;
151 
152 	bool				InitLock ( bool bProcessShared, CSphString & sError ) const;
153 
154 private:
155 	mutable CSphRwlock	m_tLock;
156 };
157 
158 /////////////////////////////////////////////////////////////////////////////
159 
160 enum ESphAddIndex
161 {
162 	ADD_ERROR	= 0,
163 	ADD_LOCAL	= 1,
164 	ADD_DISTR	= 2,
165 	ADD_RT		= 3,
166 	ADD_TMPL	= 4
167 };
168 
169 
170 enum ProtocolType_e
171 {
172 	PROTO_SPHINX = 0,
173 	PROTO_MYSQL41,
174 
175 	PROTO_TOTAL
176 };
177 
178 
179 static const char * g_dProtoNames[PROTO_TOTAL] =
180 {
181 	"sphinxapi", "sphinxql"
182 };
183 
184 
185 static bool				g_bService		= false;
186 #if USE_WINDOWS
187 static bool				g_bServiceStop	= false;
188 static const char *		g_sServiceName	= "searchd";
189 HANDLE					g_hPipe			= INVALID_HANDLE_VALUE;
190 #endif
191 
192 static CSphVector<CSphString>	g_dArgs;
193 
194 static bool				g_bHeadDaemon	= false;
195 static bool				g_bLogStdout	= true;
196 
197 struct CrashQuery_t
198 {
199 	const BYTE *			m_pQuery;	// last query
200 	int						m_iSize;	// last query size
201 	WORD					m_uCMD;		// last command (header)
202 	WORD					m_uVer;		// last command's version (header)
203 	bool					m_bMySQL;	// is query from MySQL or API
204 
CrashQuery_tCrashQuery_t205 	CrashQuery_t ()
206 		: m_pQuery ( NULL )
207 		, m_iSize ( 0 )
208 		, m_uCMD ( 0 )
209 		, m_uVer ( 0 )
210 		, m_bMySQL ( false )
211 	{
212 	}
213 };
214 
215 // This class is basically a pointer to query string and some more additional info.
216 // Each thread which executes query must have exactly one instance of this class on
217 // its stack and m_tLastQueryTLS will contain a pointer to that instance.
218 // Main thread has explicitly created SphCrashLogger_c on its stack, other query
219 // threads should be created with SphCrashLogger_c::ThreadCreate()
220 class SphCrashLogger_c
221 {
222 public:
SphCrashLogger_c()223 	SphCrashLogger_c () {}
224 	// lets invalidate pointer when this instance goes out of scope to get immediate crash
225 	// instead of a reference to incorrect stack frame in case of some programming error
~SphCrashLogger_c()226 	~SphCrashLogger_c () { sphThreadSet ( m_tTLS, this ); }
227 
228 	static void Init ();
229 	static void Done ();
230 
231 #if !USE_WINDOWS
232 	static void HandleCrash ( int );
233 #else
234 	static LONG WINAPI HandleCrash ( EXCEPTION_POINTERS * pExc );
235 #endif
236 	static void SetLastQuery ( const CrashQuery_t & tQuery );
237 	static void SetupTimePID ();
238 	static CrashQuery_t GetQuery ();
239 	void SetupTLS ();
240 
241 	// create thread with crash logging
242 	static bool ThreadCreate ( SphThread_t * pThread, void ( *pCall )(void*), void * pArg, bool bDetached=false );
243 
244 private:
245 	struct CallArgPair_t
246 	{
CallArgPair_tSphCrashLogger_c::CallArgPair_t247 		CallArgPair_t ( void ( *pCall )(void *), void * pArg )
248 			: m_pCall ( pCall )
249 			, m_pArg ( pArg )
250 		{}
251 		void ( *m_pCall )( void * );
252 		void * m_pArg;
253 	};
254 
255 	// sets up a TLS for a given thread
256 	static void ThreadWrapper ( void * pArg );
257 
258 	CrashQuery_t			m_tQuery;		// per thread copy of last query for thread mode
259 	static SphThreadKey_t	m_tTLS;	// pointer to on-stack instance of this class
260 };
261 
262 enum LogFormat_e
263 {
264 	LOG_FORMAT_PLAIN,
265 	LOG_FORMAT_SPHINXQL
266 };
267 
268 static ESphLogLevel		g_eLogLevel		= SPH_LOG_INFO;
269 static int				g_iLogFile		= STDOUT_FILENO;	// log file descriptor
270 static bool				g_bLogSyslog	= false;
271 static bool				g_bQuerySyslog	= false;
272 static CSphString		g_sLogFile;							// log file name
273 static bool				g_bLogTty		= false;			// cached isatty(g_iLogFile)
274 static LogFormat_e		g_eLogFormat	= LOG_FORMAT_PLAIN;
275 static bool				g_bShortenIn	= false;			// whether to cut list in IN() clauses.
276 static const int		SHORTEN_IN_LIMIT = 128;				// how many values will be pushed into log uncutted
277 static int				g_iQueryLogMinMs	= 0;				// log 'slow' threshold for query
278 static char				g_sLogFilter[SPH_MAX_FILENAME_LEN] = "\0";
279 static int				g_iLogFilterLen = 0;
280 
281 static int				g_iReadTimeout		= 5;	// sec
282 static int				g_iWriteTimeout		= 5;
283 static int				g_iClientTimeout	= 300;
284 static int				g_iPersistentPoolSize	= 0;
285 static CSphVector<int>	g_dPersistentConnections; // protect by CSphScopedLock<ThreadsOnlyMutex_t> tLock ( g_tPersLock );
286 static int				g_iMaxChildren		= 0;
287 #if !USE_WINDOWS
288 static bool				g_bPreopenIndexes	= true;
289 #else
290 static bool				g_bPreopenIndexes	= false;
291 #endif
292 static bool				g_bWatchdog			= true;
293 static int				g_iExpansionLimit	= 0;
294 static bool				g_bOnDiskAttrs		= false;
295 static bool				g_bOnDiskPools		= false;
296 static int				g_iShutdownTimeout	= 3000000; // default timeout on daemon shutdown and stopwait is 3 seconds
297 
298 struct Listener_t
299 {
300 	int					m_iSock;
301 	bool				m_bTcp;
302 	ProtocolType_e		m_eProto;
303 };
304 static CSphVector<Listener_t>	g_dListeners;
305 
306 static int				g_iQueryLogFile	= -1;
307 static CSphString		g_sQueryLogFile;
308 static CSphString		g_sPidFile;
309 static int				g_iPidFD		= -1;
310 
311 static int				g_iMaxCachedDocs	= 0;	// in bytes
312 static int				g_iMaxCachedHits	= 0;	// in bytes
313 
314 static int				g_iAttrFlushPeriod	= 0;			// in seconds; 0 means "do not flush"
315 static int				g_iMaxPacketSize	= 8*1024*1024;	// in bytes; for both query packets from clients and response packets from agents
316 static int				g_iMaxFilters		= 256;
317 static int				g_iMaxFilterValues	= 4096;
318 static int				g_iMaxBatchQueries	= 32;
319 static ESphCollation	g_eCollation = SPH_COLLATION_DEFAULT;
320 static CSphString		g_sSnippetsFilePrefix;
321 #if !USE_WINDOWS
322 static CSphProcessSharedVariable<bool> g_tHaveTTY ( true );
323 #endif
324 
325 enum Mpm_e
326 {
327 	MPM_NONE,		///< process queries in a loop one by one (eg. in --console)
328 	MPM_FORK,		///< fork a worker process for each query
329 	MPM_PREFORK,	///< keep a number of pre-forked processes
330 	MPM_THREADS		///< create a worker thread for each query
331 };
332 
333 static Mpm_e			g_eWorkers			= MPM_THREADS;
334 
335 static int				g_iPreforkChildren	= 10;		// how much workers to keep
336 static CSphVector<int>	g_dChildren;
337 static int				g_iClientFD			= -1;
338 static int				g_iDistThreads		= 0;
339 static int				g_iPingInterval		= 0;		// by default ping HA agents every 1 second
340 static DWORD			g_uHAPeriodKarma	= 60;		// by default use the last 1 minute statistic to determine the best HA agent
341 
342 static int				g_iAgentConnectTimeout = 1000;
343 static int				g_iAgentQueryTimeout = 3000;	// global (default). May be override by index-scope values, if one specified
344 
345 const int	MAX_RETRY_COUNT		= 8;
346 const int	MAX_RETRY_DELAY		= 1000;
347 
348 static int				g_iAgentRetryCount = 0;
349 static int				g_iAgentRetryDelay = MAX_RETRY_DELAY/2;	// global (default) values. May be override by the query options 'retry_count' and 'retry_timeout'
350 
351 
352 struct ListNode_t
353 {
354 	ListNode_t * m_pPrev;
355 	ListNode_t * m_pNext;
ListNode_tListNode_t356 	ListNode_t ()
357 		: m_pPrev ( NULL )
358 		, m_pNext ( NULL )
359 	{ }
360 };
361 
362 
363 class List_t
364 {
365 public:
List_t()366 	List_t ()
367 	{
368 		m_tStub.m_pPrev = &m_tStub;
369 		m_tStub.m_pNext = &m_tStub;
370 		m_iCount = 0;
371 	}
372 
Add(ListNode_t * pNode)373 	void Add ( ListNode_t * pNode )
374 	{
375 		assert ( !pNode->m_pNext && !pNode->m_pPrev );
376 		pNode->m_pNext = m_tStub.m_pNext;
377 		pNode->m_pPrev = &m_tStub;
378 		m_tStub.m_pNext->m_pPrev = pNode;
379 		m_tStub.m_pNext = pNode;
380 
381 		m_iCount++;
382 	}
383 
Remove(ListNode_t * pNode)384 	void Remove ( ListNode_t * pNode )
385 	{
386 		assert ( pNode->m_pNext && pNode->m_pPrev );
387 		pNode->m_pNext->m_pPrev = pNode->m_pPrev;
388 		pNode->m_pPrev->m_pNext = pNode->m_pNext;
389 		pNode->m_pNext = NULL;
390 		pNode->m_pPrev = NULL;
391 
392 		m_iCount--;
393 	}
394 
GetLength() const395 	int GetLength () const
396 	{
397 		return m_iCount;
398 	}
399 
Begin() const400 	const ListNode_t * Begin () const
401 	{
402 		return m_tStub.m_pNext;
403 	}
404 
End() const405 	const ListNode_t * End () const
406 	{
407 		return &m_tStub;
408 	}
409 
410 private:
411 	ListNode_t	m_tStub;	// stub node
412 	int			m_iCount;	// elements counter
413 };
414 
415 
416 enum ThdState_e
417 {
418 	THD_HANDSHAKE = 0,
419 	THD_NET_READ,
420 	THD_NET_WRITE,
421 	THD_QUERY,
422 	THD_NET_IDLE,
423 
424 	THD_STATE_TOTAL
425 };
426 
427 static const char * g_dThdStates[THD_STATE_TOTAL] = {
428 	"handshake", "net_read", "net_write", "query", "net_idle"
429 };
430 
431 struct ThdDesc_t : public ListNode_t
432 {
433 	SphThread_t		m_tThd;
434 	ProtocolType_e	m_eProto;
435 	int				m_iClientSock;
436 	CSphString		m_sClientName;
437 
438 	ThdState_e		m_eThdState;
439 	const char *	m_sCommand;
440 	int				m_iConnID;		///< current conn-id for this thread
441 
442 	// stuff for SHOW THREADS
443 	int				m_iTid;			///< OS thread id, or 0 if unknown
444 	int64_t			m_tmConnect;	///< when did the client connect?
445 	int64_t			m_tmStart;		///< when did the current request start?
446 	CSphFixedVector<char> m_dBuf;	///< current request description
447 
ThdDesc_tThdDesc_t448 	ThdDesc_t ()
449 		: m_eProto ( PROTO_MYSQL41 )
450 		, m_iClientSock ( 0 )
451 		, m_eThdState ( THD_HANDSHAKE )
452 		, m_sCommand ( NULL )
453 		, m_iConnID ( -1 )
454 		, m_iTid ( 0 )
455 		, m_tmConnect ( 0 )
456 		, m_tmStart ( 0 )
457 		, m_dBuf ( 512 )
458 	{
459 		m_dBuf[0] = '\0';
460 		m_dBuf.Last() = '\0';
461 	}
462 
SetSnippetThreadInfoThdDesc_t463 	void SetSnippetThreadInfo ( const CSphVector<ExcerptQuery_t> & dSnippets )
464 	{
465 		int64_t iSize = 0;
466 		ARRAY_FOREACH ( i, dSnippets )
467 		{
468 			if ( dSnippets[i].m_iSize )
469 				iSize -= dSnippets[i].m_iSize; // because iSize negative for sorting purpose
470 			else
471 				iSize += dSnippets[i].m_sSource.Length();
472 		}
473 
474 		iSize /= 100;
475 		SetThreadInfo ( "api-snippet datasize=%d.%d""k query=\"%s\"", int ( iSize/10 ), int ( iSize%10 ), dSnippets[0].m_sWords.scstr() );
476 	}
477 
SetThreadInfoThdDesc_t478 	void SetThreadInfo ( const char * sTemplate, ... )
479 	{
480 		va_list ap;
481 
482 		va_start ( ap, sTemplate );
483 		int iPrinted = vsnprintf ( m_dBuf.Begin(), m_dBuf.GetLength()-1, sTemplate, ap );
484 		va_end ( ap );
485 
486 		if ( iPrinted>0 )
487 			m_dBuf[Min ( iPrinted, m_dBuf.GetLength()-1 )] = '\0';
488 	}
489 };
490 
491 struct StaticThreadsOnlyMutex_t
492 {
493 	StaticThreadsOnlyMutex_t ();
494 	~StaticThreadsOnlyMutex_t ();
495 	void Lock ();
496 	void Unlock ();
497 
498 private:
499 	CSphMutex m_tLock;
500 };
501 
502 
503 static StaticThreadsOnlyMutex_t	g_tThdMutex;
504 static List_t					g_dThd;				///< existing threads table
505 
506 static int						g_iConnID = 0;		///< global conn-id in none/fork/threads; current conn-id in prefork
507 static SphThreadKey_t			g_tConnKey;			///< current conn-id TLS in threads
508 static int *					g_pConnID = NULL;	///< global conn-id ptr in prefork
509 
510 // handshake
511 static char						g_sMysqlHandshake[128];
512 static int						g_iMysqlHandshake = 0;
513 
514 //////////////////////////////////////////////////////////////////////////
515 
516 static CSphString		g_sConfigFile;
517 static DWORD			g_uCfgCRC32		= 0;
518 static struct stat		g_tCfgStat;
519 
520 static CSphConfigParser g_pCfg;
521 
522 #if USE_WINDOWS
523 static bool				g_bSeamlessRotate	= false;
524 #else
525 static bool				g_bSeamlessRotate	= true;
526 #endif
527 
528 static bool				g_bIOStats		= false;
529 static bool				g_bCpuStats		= false;
530 static bool				g_bOptNoDetach	= false;
531 static bool				g_bOptNoLock	= false;
532 static bool				g_bSafeTrace	= false;
533 static bool				g_bStripPath	= false;
534 
535 static volatile bool	g_bDoDelete			= false;	// do we need to delete any indexes?
536 static CSphAtomic<long> g_iRotateCount			( 0 );	// how many times do we need to rotate
537 static volatile sig_atomic_t g_bGotSighup		= 0;	// we just received SIGHUP; need to log
538 static volatile sig_atomic_t g_bGotSigterm		= 0;	// we just received SIGTERM; need to shutdown
539 static volatile sig_atomic_t g_bGotSigchld		= 0;	// we just received SIGCHLD; need to count dead children
540 static volatile sig_atomic_t g_bGotSigusr1		= 0;	// we just received SIGUSR1; need to reopen logs
541 
542 // pipe to watchdog to inform that daemon is going to close, so no need to restart it in case of crash
543 static CSphSharedBuffer<DWORD>	g_bDaemonAtShutdown;
544 static volatile bool			g_bShutdown = false;
545 
546 static CSphVector<int>	g_dHupChildren;					// children to send hup signal on rotation is done
547 static int64_t			g_tmRotateChildren		= 0;	// pause to next children term signal after rotation is done
548 static int				g_iRotationThrottle		= 0;	// pause between children term signals after rotation is done
549 
550 /// global index hash
551 /// used in both non-threaded and multi-threaded modes
552 ///
553 /// hash entry is a CSphIndex pointer, rwlock, and a few flags (see ServedIndex_t)
554 /// rlock on entry guarantees it won't change, eg. that index pointer will stay alive
555 /// wlock on entry allows to change (delete/replace) the index pointer
556 ///
557 /// note that entry locks are held outside the hash
558 /// and Delete() honours that by acquiring wlock on an entry first
559 class IndexHash_c : protected SmallStringHash_T<ServedIndex_t>
560 {
561 	friend class IndexHashIterator_c;
562 	typedef SmallStringHash_T<ServedIndex_t> BASE;
563 
564 public:
565 	explicit				IndexHash_c ();
566 	virtual					~IndexHash_c ();
567 
GetLength() const568 	int						GetLength () const { return BASE::GetLength(); }
Reset()569 	void					Reset () { BASE::Reset(); }
570 
571 	bool					Add ( const ServedDesc_t & tDesc, const CSphString & tKey );
572 	bool					Delete ( const CSphString & tKey );
573 
574 	const ServedIndex_t *	GetRlockedEntry ( const CSphString & tKey ) const;
575 	ServedIndex_t *			GetWlockedEntry ( const CSphString & tKey ) const;
576 	ServedIndex_t &			GetUnlockedEntry ( const CSphString & tKey ) const;
577 	ServedIndex_t *			GetUnlockedEntryP ( const CSphString & tKey ) const;
578 	bool					Exists ( const CSphString & tKey ) const;
579 
580 protected:
581 	void					Rlock () const;
582 	void					Wlock () const;
583 	void					Unlock () const;
584 
585 private:
586 	mutable CSphRwlock		m_tLock;
587 };
588 
589 
590 /// multi-threaded hash iterator
591 class IndexHashIterator_c : public ISphNoncopyable
592 {
593 public:
594 	explicit			IndexHashIterator_c ( const IndexHash_c * pHash, bool bWrite=false );
595 						~IndexHashIterator_c ();
596 
597 	bool				Next ();
598 	ServedIndex_t &		Get ();
599 	const CSphString &	GetKey ();
600 
601 private:
602 	const IndexHash_c *			m_pHash;
603 	IndexHash_c::HashEntry_t *	m_pIterator;
604 };
605 
606 
607 static IndexHash_c *						g_pLocalIndexes = NULL;	// served (local) indexes hash
608 static IndexHash_c *						g_pTemplateIndexes = NULL; // template (tokenizer) indexes hash
609 static CSphVector<const char *>				g_dRotating;			// names of indexes to be rotated this time
610 static const char *							g_sPrereading	= NULL;	// name of index currently being preread
611 static CSphIndex *							g_pPrereading	= NULL;	// rotation "buffer"
612 
613 static StaticThreadsOnlyMutex_t				g_tRotateQueueMutex;
614 static CSphVector<CSphString>				g_dRotateQueue;		// FIXME? maybe replace it with lockless ring buffer
615 static StaticThreadsOnlyMutex_t				g_tRotateConfigMutex;
616 static SphThread_t							g_tRotateThread;
617 static SphThread_t							g_tRotationServiceThread;
618 static volatile bool						g_bInvokeRotationService = false;
619 static SphThread_t							g_tPingThread;
620 
621 /// flush parameters of rt indexes
622 static SphThread_t							g_tRtFlushThread;
623 
624 // optimize thread
625 static SphThread_t							g_tOptimizeThread;
626 static StaticThreadsOnlyMutex_t				g_tOptimizeQueueMutex;
627 static CSphVector<CSphString>				g_dOptimizeQueue;
628 static ThrottleState_t						g_tRtThrottle;
629 
630 static StaticThreadsOnlyMutex_t				g_tDistLock;
631 static StaticThreadsOnlyMutex_t				g_tPersLock;
632 
633 class InterWorkerStorage;
634 static InterWorkerStorage*					g_pPersistentInUse;
635 static const DWORD							g_uAtLeastUnpersistent = 1; // how many workers will never became persistent
636 
637 enum
638 {
639 	SPH_PIPE_PREREAD
640 };
641 
642 struct PipeInfo_t
643 {
644 	int		m_iFD;			///< read-pipe to child
645 	int		m_iHandler;		///< who's my handler (SPH_PIPE_xxx)
646 
PipeInfo_tPipeInfo_t647 	PipeInfo_t () : m_iFD ( -1 ), m_iHandler ( -1 ) {}
648 };
649 
650 static CSphVector<PipeInfo_t>	g_dPipes;		///< currently open read-pipes to children processes
651 
652 
653 /////////////////////////////////////////////////////////////////////////////
654 
655 /// known commands
656 enum SearchdCommand_e
657 {
658 	SEARCHD_COMMAND_SEARCH		= 0,
659 	SEARCHD_COMMAND_EXCERPT		= 1,
660 	SEARCHD_COMMAND_UPDATE		= 2,
661 	SEARCHD_COMMAND_KEYWORDS	= 3,
662 	SEARCHD_COMMAND_PERSIST		= 4,
663 	SEARCHD_COMMAND_STATUS		= 5,
664 	SEARCHD_COMMAND_FLUSHATTRS	= 7,
665 	SEARCHD_COMMAND_SPHINXQL	= 8,
666 	SEARCHD_COMMAND_PING		= 9,
667 	SEARCHD_COMMAND_DELETE		= 10,
668 	SEARCHD_COMMAND_UVAR		= 11,
669 
670 	SEARCHD_COMMAND_TOTAL
671 };
672 
673 
674 /// known command versions
675 enum
676 {
677 	VER_COMMAND_SEARCH		= 0x11F, // 1.31
678 	VER_COMMAND_EXCERPT		= 0x104,
679 	VER_COMMAND_UPDATE		= 0x103,
680 	VER_COMMAND_KEYWORDS	= 0x100,
681 	VER_COMMAND_STATUS		= 0x101,
682 	VER_COMMAND_FLUSHATTRS	= 0x100,
683 	VER_COMMAND_SPHINXQL	= 0x100,
684 	VER_COMMAND_PING		= 0x100,
685 	VER_COMMAND_UVAR		= 0x100,
686 };
687 
688 
689 /// known status return codes
690 enum SearchdStatus_e
691 {
692 	SEARCHD_OK		= 0,	///< general success, command-specific reply follows
693 	SEARCHD_ERROR	= 1,	///< general failure, error message follows
694 	SEARCHD_RETRY	= 2,	///< temporary failure, error message follows, client should retry later
695 	SEARCHD_WARNING	= 3		///< general success, warning message and command-specific reply follow
696 };
697 
698 
699 /// master-agent API protocol extensions version
700 enum
701 {
702 	VER_MASTER = 14
703 };
704 
705 
706 /// command names
707 static const char * g_dApiCommands[SEARCHD_COMMAND_TOTAL] =
708 {
709 	"search", "excerpt", "update", "keywords", "persist", "status", "query", "flushattrs", "query", "ping", "delete", "uvar"
710 };
711 
712 
713 
714 //////////////////////////////////////////////////////////////////////////
715 
716 const int	STATS_MAX_AGENTS	= 4096;				///< we'll track stats for this much remote agents
717 const int	STATS_MAX_DASH	= STATS_MAX_AGENTS / 4;	///< we'll track stats for RR of this much remote agents
718 const int	STATS_DASH_TIME = 15;					///< store the history for last periods
719 
720 template <class DATA, int SIZE> class StaticStorage_t : public ISphNoncopyable
721 {
722 	DWORD			m_bmItemStats[SIZE/32];	///< per-item storage usage bitmap
723 public:
724 	DATA			m_dItemStats[SIZE];		///< per-item storage
725 public:
StaticStorage_t()726 	explicit StaticStorage_t()
727 	{
728 		for ( int i=0; i<SIZE/32; ++i )
729 			m_bmItemStats[i] = 0;
730 	}
AllocItem()731 	int AllocItem()
732 	{
733 		int iRes = -1;
734 		for ( int i=0; i<SIZE/32; i++ )
735 			if ( m_bmItemStats[i]!=0xffffffffUL )
736 			{
737 				int j = FindBit ( m_bmItemStats[i] );
738 				m_bmItemStats[i] |= ( 1<<j );
739 				iRes = i*32 + j;
740 				memset ( &m_dItemStats[iRes], 0, sizeof(DATA) );
741 				break;
742 			}
743 		return iRes;
744 	}
FreeItem(int iItem)745 	void FreeItem ( int iItem )
746 	{
747 		if ( iItem<0 || iItem>=SIZE )
748 			return;
749 
750 		assert ( m_bmItemStats[iItem>>5] & ( 1UL<<( iItem & 31 ) ) );
751 		m_bmItemStats[iItem>>5] &= ~( 1UL<<( iItem & 31 ) );
752 	}
753 };
754 
755 /// per-agent query stats
756 enum eAgentStats
757 {
758 	eTimeoutsQuery = 0,	///< number of time-outed queries
759 	eTimeoutsConnect,	///< number of time-outed connections
760 	eConnectFailures,	///< failed to connect
761 	eNetworkErrors,		///< network error
762 	eWrongReplies,		///< incomplete reply
763 	eUnexpectedClose,	///< agent closed the connection
764 	eWarnings,			///< agent answered, but with warnings
765 	eNetworkCritical = eWarnings,
766 	eNoErrors,			///< successfull queries, no errors
767 	eNetworkNonCritical = eNoErrors,
768 	eTotalMsecs,		///< number of microseconds in queries, total
769 	eMaxCounters = eTotalMsecs,
770 	eConnTries,		///< total number of connec tries
771 	eAverageMsecs,		///< average connect time
772 	eMaxMsecs,			///< maximal connect time
773 	eMaxStat
774 };
775 struct AgentStats_t
776 {
777 	uint64_t		m_iStats[eMaxStat];
778 	static const char * m_sNames[eMaxStat];
779 
ResetAgentStats_t780 	void Reset()
781 	{
782 		for ( int i=0; i<eMaxStat; ++i )
783 			m_iStats[i] = 0;
784 	}
AddAgentStats_t785 	void Add ( const AgentStats_t& rhs )
786 	{
787 		for ( int i=0; i<=eMaxCounters; ++i )
788 			m_iStats[i] += rhs.m_iStats[i];
789 
790 		if ( m_iStats[eConnTries] )
791 			m_iStats[eAverageMsecs] = ( m_iStats[eAverageMsecs] + rhs.m_iStats[eAverageMsecs] ) / 2;
792 		else
793 			m_iStats[eAverageMsecs] = rhs.m_iStats[eAverageMsecs];
794 		m_iStats[eMaxMsecs] = Max ( m_iStats[eMaxMsecs], rhs.m_iStats[eMaxMsecs] );
795 		m_iStats[eConnTries] += rhs.m_iStats[eConnTries];
796 	}
797 };
798 
799 const char * AgentStats_t::m_sNames[eMaxStat]=
800 	{ "query_timeouts", "connect_timeouts", "connect_failures",
801 		"network_errors", "wrong_replies", "unexpected_closings",
802 		"warnings", "succeeded_queries", "total_query_time", "connect_count",
803 		"connect_avg", "connect_max" };
804 
805 struct AgentDash_t : AgentStats_t
806 {
807 	DWORD			m_uTimestamp;	///< adds the minutes timestamp to AgentStats_t
808 };
809 
810 class RentPersistent
811 {
812 	int				m_iPersPool;
813 
814 public:
RentPersistent(int iPersPool)815 	explicit RentPersistent ( int iPersPool )
816 		: m_iPersPool ( iPersPool )
817 	{}
818 
Init(int iPersPool)819 	void Init ( int iPersPool )
820 	{
821 		m_iPersPool = iPersPool;
822 	}
823 
RentConnection()824 	int RentConnection ()
825 	{
826 		// for the moment we try to return already connected socket.
827 		CSphScopedLock<StaticThreadsOnlyMutex_t> tLock ( g_tPersLock );
828 		int* pFree = &g_dPersistentConnections[m_iPersPool];
829 		if ( *pFree==-1 ) // fresh pool, just cleared. All slots are free
830 			*pFree = g_iPersistentPoolSize;
831 		if ( !*pFree )
832 			return -2; // means 'no free slots'
833 		int iSocket = g_dPersistentConnections[m_iPersPool+*pFree];
834 		g_dPersistentConnections[m_iPersPool+*pFree]=-1;
835 		--(*pFree);
836 		return iSocket;
837 	}
838 
ReturnConnection(int iSocket)839 	void ReturnConnection ( int iSocket )
840 	{
841 		// for the moment we try to return already connected socket.
842 		CSphScopedLock<StaticThreadsOnlyMutex_t> tLock ( g_tPersLock );
843 		int* pFree = &g_dPersistentConnections[m_iPersPool];
844 		assert ( *pFree<g_iPersistentPoolSize );
845 		if ( *pFree==g_iPersistentPoolSize )
846 		{
847 			sphSockClose ( iSocket );
848 			return;
849 		}
850 		++(*pFree);
851 		g_dPersistentConnections[m_iPersPool+*pFree]=iSocket;
852 	}
853 };
854 
855 struct AgentDesc_t
856 {
857 	CSphString		m_sHost;		///< remote searchd host
858 	int				m_iPort;		///< remote searchd port, 0 if local
859 	CSphString		m_sPath;		///< local searchd UNIX socket path
860 	CSphString		m_sIndexes;		///< remote index names to query
861 	bool			m_bBlackhole;	///< blackhole agent flag
862 	int				m_iFamily;		///< TCP or UNIX socket
863 	DWORD			m_uAddr;		///< IP address
864 	int				m_iStatsIndex;	///< index into global searchd stats array
865 	int				m_iDashIndex;	///< index into global searchd host stats array (1 host can hold >1 agents)
866 	bool			m_bPersistent;	///< whether to keep the persistent connection to the agent.
867 	RentPersistent	m_dPersPool;	///< socket number, -1 if not connected (has sense only if the connection is persistent)
868 
869 public:
AgentDesc_tAgentDesc_t870 	AgentDesc_t ()
871 		: m_iPort ( -1 )
872 		, m_bBlackhole ( false )
873 		, m_iFamily ( AF_INET )
874 		, m_uAddr ( 0 )
875 		, m_iStatsIndex ( -1 )
876 		, m_iDashIndex ( 0 )
877 		, m_bPersistent ( false )
878 		, m_dPersPool ( -1 )
879 	{}
880 
GetNameAgentDesc_t881 	CSphString GetName() const
882 	{
883 		CSphString sName;
884 		switch ( m_iFamily )
885 		{
886 		case AF_INET: sName.SetSprintf ( "%s:%u", m_sHost.cstr(), m_iPort ); break;
887 		case AF_UNIX: sName = m_sPath; break;
888 		}
889 		return sName;
890 	}
891 };
892 
893 /// per-host dashboard
894 struct HostDashboard_t
895 {
896 	int				m_iRefCount;			// several indexes can use this one agent
897 	int64_t			m_iLastAnswerTime;		// updated when we get an answer from the host
898 	int64_t			m_iLastQueryTime;		// updated when we send a query to a host
899 	int64_t			m_iErrorsARow;			// num of errors a row, updated when we update the general statistic.
900 	AgentDesc_t		m_dDescriptor;			// only host info, no indices. Used for ping.
901 	bool			m_bNeedPing;			// we'll ping only HA agents, not everyone
902 
903 private:
904 	AgentDash_t	m_dStats[STATS_DASH_TIME];
905 
906 public:
907 
InitHostDashboard_t908 	void Init ( AgentDesc_t * pAgent )
909 	{
910 		m_iRefCount = 1;
911 		m_iLastQueryTime = m_iLastAnswerTime = sphMicroTimer() - g_iPingInterval*1000;
912 		m_iErrorsARow = 0;
913 		m_dDescriptor = *pAgent;
914 		m_bNeedPing = false;
915 	}
916 
IsOlderHostDashboard_t917 	inline bool IsOlder ( int64_t iTime ) const
918 	{
919 		return ( (iTime-m_iLastAnswerTime)>g_iPingInterval*1000 );
920 	}
921 
GetCurSecondsHostDashboard_t922 	inline static DWORD GetCurSeconds()
923 	{
924 		int64_t iNow = sphMicroTimer()/1000000;
925 		return DWORD ( iNow & 0xFFFFFFFF );
926 	}
927 
IsHalfPeriodChangedHostDashboard_t928 	inline static bool IsHalfPeriodChanged ( DWORD * pLast )
929 	{
930 		assert ( pLast );
931 		DWORD uSeconds = GetCurSeconds();
932 		if ( ( uSeconds - *pLast )>( g_uHAPeriodKarma / 2 ) )
933 		{
934 			*pLast = uSeconds;
935 			return true;
936 		}
937 		return false;
938 	}
939 
GetCurrentStatHostDashboard_t940 	AgentDash_t*	GetCurrentStat()
941 	{
942 		DWORD uTime = GetCurSeconds()/g_uHAPeriodKarma;
943 		int iIdx = uTime % STATS_DASH_TIME;
944 		AgentDash_t & dStats = m_dStats[iIdx];
945 		if ( dStats.m_uTimestamp!=uTime ) // we have new or reused stat
946 			dStats.Reset();
947 		dStats.m_uTimestamp = uTime;
948 		return &dStats;
949 	}
950 
GetDashStatHostDashboard_t951 	int GetDashStat ( AgentDash_t* pRes, int iPeriods=1 ) const
952 	{
953 		assert ( pRes );
954 		pRes->Reset();
955 
956 		int iCollected = 0;
957 
958 		DWORD uSeconds = GetCurSeconds();
959 		if ( (uSeconds % g_uHAPeriodKarma) < (g_uHAPeriodKarma/2) )
960 			++iPeriods;
961 
962 		int iLimit = Min ( iPeriods, STATS_DASH_TIME );
963 		int iMaxExpected = iLimit + 1;
964 		DWORD uTime = uSeconds/g_uHAPeriodKarma;
965 		int iIdx = uTime % STATS_DASH_TIME;
966 		// pRes->m_uTimestamp = uTime;
967 
968 		for ( ; iLimit>0 ; --iLimit )
969 		{
970 			const AgentDash_t & dStats = m_dStats[iIdx];
971 			if ( dStats.m_uTimestamp==uTime ) // it might be no queries at all in the fixed time
972 			{
973 				pRes->Add ( dStats );
974 				iCollected = iLimit;
975 			}
976 
977 			--uTime;
978 			--iIdx;
979 			if ( iIdx<0 )
980 				iIdx = STATS_DASH_TIME-1;
981 		}
982 
983 		return iMaxExpected - iCollected;
984 	}
985 };
986 
987 struct SearchdStats_t
988 {
989 	DWORD		m_uStarted;
990 	int64_t		m_iConnections;
991 	int64_t		m_iMaxedOut;
992 	int64_t		m_iCommandCount[SEARCHD_COMMAND_TOTAL];
993 	int64_t		m_iAgentConnect;
994 	int64_t		m_iAgentRetry;
995 
996 	int64_t		m_iQueries;			///< search queries count (differs from search commands count because of multi-queries)
997 	int64_t		m_iQueryTime;		///< wall time spent (including network wait time)
998 	int64_t		m_iQueryCpuTime;	///< CPU time spent
999 
1000 	int64_t		m_iDistQueries;		///< distributed queries count
1001 	int64_t		m_iDistWallTime;	///< wall time spent on distributed queries
1002 	int64_t		m_iDistLocalTime;	///< wall time spent searching local indexes in distributed queries
1003 	int64_t		m_iDistWaitTime;	///< time spent waiting for remote agents in distributed queries
1004 
1005 	int64_t		m_iDiskReads;		///< total read IO calls (fired by search queries)
1006 	int64_t		m_iDiskReadBytes;	///< total read IO traffic
1007 	int64_t		m_iDiskReadTime;	///< total read IO time
1008 
1009 	int64_t		m_iPredictedTime;	///< total agent predicted query time
1010 	int64_t		m_iAgentPredictedTime;	///< total agent predicted query time
1011 
1012 	// FIXME!!! all these are still not thread safe (read at CheckPing vs modify at ValidateAgentDesc)
1013 	// and not concurent friendly due to g_tStatsMutex ( read at BuildOneAgentStatus might block often write at agent_stats_inc )
1014 	StaticStorage_t<AgentStats_t,STATS_MAX_AGENTS> m_dAgentStats;
1015 	StaticStorage_t<HostDashboard_t,STATS_MAX_DASH> m_dDashboard;
1016 	SmallStringHash_T<int>							m_hDashBoard; ///< find hosts for agents and sort them all
1017 };
1018 
1019 
1020 struct StaticThreadProcMutex_t
1021 {
1022 	StaticThreadProcMutex_t ();
1023 	~StaticThreadProcMutex_t ();
1024 	void Lock ();
1025 	void Unlock ();
1026 
1027 private:
1028 	CSphMutex m_tThdLock;
1029 	CSphProcessSharedMutex m_tProcLock;
1030 };
1031 
1032 
1033 static SearchdStats_t *			g_pStats		= NULL;
1034 static CSphSharedBuffer<SearchdStats_t>	g_tStatsBuffer;
1035 static StaticThreadProcMutex_t	g_tStatsMutex;
1036 
1037 static CSphQueryResultMeta		g_tLastMeta;
1038 static StaticThreadsOnlyMutex_t g_tLastMetaMutex;
1039 
1040 //////////////////////////////////////////////////////////////////////////
1041 
1042 struct FlushState_t
1043 {
1044 	int		m_bFlushing;		///< update flushing in progress
1045 	int		m_iFlushTag;		///< last flushed tag
1046 	bool	m_bForceCheck;		///< forced check/flush flag
1047 };
1048 
1049 static volatile FlushState_t *	g_pFlush		= NULL;
1050 static CSphSharedBuffer<FlushState_t>	g_tFlushBuffer;
1051 
1052 //////////////////////////////////////////////////////////////////////////
1053 
1054 /// available uservar types
1055 enum Uservar_e
1056 {
1057 	USERVAR_INT_SET
1058 };
1059 
1060 /// uservar name to value binding
1061 struct Uservar_t
1062 {
1063 	Uservar_e			m_eType;
1064 	UservarIntSet_c *	m_pVal;
1065 
Uservar_tUservar_t1066 	Uservar_t ()
1067 		: m_eType ( USERVAR_INT_SET )
1068 		, m_pVal ( NULL )
1069 	{}
1070 };
1071 
1072 static StaticThreadsOnlyMutex_t		g_tUservarsMutex;
1073 static SmallStringHash_T<Uservar_t>	g_hUservars;
1074 
1075 static volatile int64_t				g_tmSphinxqlState; // last state (uservars+udfs+...) update timestamp
1076 static SphThread_t					g_tSphinxqlStateFlushThread;
1077 static CSphString					g_sSphinxqlState;
1078 
1079 /////////////////////////////////////////////////////////////////////////////
1080 // MACHINE-DEPENDENT STUFF
1081 /////////////////////////////////////////////////////////////////////////////
1082 
1083 #if USE_WINDOWS
1084 
1085 // Windows hacks
1086 #undef EINTR
1087 #undef EWOULDBLOCK
1088 #undef ETIMEDOUT
1089 #undef EINPROGRESS
1090 #undef ECONNRESET
1091 #undef ECONNABORTED
1092 #define LOCK_EX			0
1093 #define LOCK_UN			1
1094 #define STDIN_FILENO	fileno(stdin)
1095 #define STDOUT_FILENO	fileno(stdout)
1096 #define STDERR_FILENO	fileno(stderr)
1097 #define ETIMEDOUT		WSAETIMEDOUT
1098 #define EWOULDBLOCK		WSAEWOULDBLOCK
1099 #define EINPROGRESS		WSAEINPROGRESS
1100 #define EINTR			WSAEINTR
1101 #define ECONNRESET		WSAECONNRESET
1102 #define ECONNABORTED	WSAECONNABORTED
1103 #define socklen_t		int
1104 
1105 #define ftruncate		_chsize
1106 #define getpid			GetCurrentProcessId
1107 
1108 #endif // USE_WINDOWS
1109 
1110 /////////////////////////////////////////////////////////////////////////////
1111 // MISC
1112 /////////////////////////////////////////////////////////////////////////////
1113 
ReleaseTTYFlag()1114 void ReleaseTTYFlag()
1115 {
1116 #if !USE_WINDOWS
1117 	g_tHaveTTY.WriteValue(false);
1118 #endif
1119 }
1120 
ServedDesc_t()1121 ServedDesc_t::ServedDesc_t ()
1122 	: m_pIndex ( NULL )
1123 	, m_bEnabled ( true )
1124 	, m_bMlock ( false )
1125 	, m_bPreopen ( false )
1126 	, m_bExpand ( false )
1127 	, m_bToDelete ( false )
1128 	, m_bOnlyNew ( false )
1129 	, m_bRT ( false )
1130 	, m_bAlterEnabled ( true )
1131 	, m_bOnDiskAttrs ( false )
1132 	, m_bOnDiskPools ( false )
1133 	, m_iMass ( 0 )
1134 {}
1135 
~ServedDesc_t()1136 ServedDesc_t::~ServedDesc_t ()
1137 {
1138 	SafeDelete ( m_pIndex );
1139 }
1140 
~ServedIndex_t()1141 ServedIndex_t::~ServedIndex_t ()
1142 {
1143 	Verify ( m_tLock.Done() );
1144 }
1145 
ReadLock() const1146 void ServedIndex_t::ReadLock () const
1147 {
1148 	if ( m_tLock.ReadLock() )
1149 		sphLogDebugvv ( "ReadLock %p", this );
1150 	else
1151 	{
1152 		sphLogDebug ( "ReadLock %p failed", this );
1153 		assert ( false );
1154 	}
1155 }
1156 
WriteLock() const1157 void ServedIndex_t::WriteLock () const
1158 {
1159 	if ( m_tLock.WriteLock() )
1160 		sphLogDebugvv ( "WriteLock %p", this );
1161 	else
1162 	{
1163 		sphLogDebug ( "WriteLock %p failed", this );
1164 		assert ( false );
1165 	}
1166 }
1167 
InitLock(bool bProcessShared,CSphString & sError) const1168 bool ServedIndex_t::InitLock ( bool bProcessShared, CSphString & sError ) const
1169 {
1170 	bool bRes = m_tLock.Init ( bProcessShared );
1171 	if ( !bRes )
1172 		sError = m_tLock.GetError();
1173 
1174 	return bRes;
1175 }
1176 
Unlock() const1177 void ServedIndex_t::Unlock () const
1178 {
1179 	if ( m_tLock.Unlock() )
1180 		sphLogDebugvv ( "Unlock %p", this );
1181 	else
1182 	{
1183 		sphLogDebug ( "Unlock %p failed", this );
1184 		assert ( false );
1185 	}
1186 }
1187 
1188 //////////////////////////////////////////////////////////////////////////
1189 
IndexHashIterator_c(const IndexHash_c * pHash,bool bWrite)1190 IndexHashIterator_c::IndexHashIterator_c ( const IndexHash_c * pHash, bool bWrite )
1191 	: m_pHash ( pHash )
1192 	, m_pIterator ( NULL )
1193 {
1194 	if ( !bWrite )
1195 		m_pHash->Rlock();
1196 	else
1197 		m_pHash->Wlock();
1198 }
1199 
~IndexHashIterator_c()1200 IndexHashIterator_c::~IndexHashIterator_c ()
1201 {
1202 	m_pHash->Unlock();
1203 }
1204 
Next()1205 bool IndexHashIterator_c::Next ()
1206 {
1207 	m_pIterator = m_pIterator ? m_pIterator->m_pNextByOrder : m_pHash->m_pFirstByOrder;
1208 	return m_pIterator!=NULL;
1209 }
1210 
Get()1211 ServedIndex_t & IndexHashIterator_c::Get ()
1212 {
1213 	assert ( m_pIterator );
1214 	return m_pIterator->m_tValue;
1215 }
1216 
GetKey()1217 const CSphString & IndexHashIterator_c::GetKey ()
1218 {
1219 	assert ( m_pIterator );
1220 	return m_pIterator->m_tKey;
1221 }
1222 
1223 //////////////////////////////////////////////////////////////////////////
1224 
IndexHash_c()1225 IndexHash_c::IndexHash_c ()
1226 {
1227 	if ( g_eWorkers==MPM_THREADS )
1228 		if ( !m_tLock.Init() )
1229 			sphDie ( "failed to init hash indexes rwlock" );
1230 }
1231 
1232 
~IndexHash_c()1233 IndexHash_c::~IndexHash_c()
1234 {
1235 	if ( g_eWorkers==MPM_THREADS )
1236 		Verify ( m_tLock.Done() );
1237 }
1238 
1239 
Rlock() const1240 void IndexHash_c::Rlock () const
1241 {
1242 	if ( g_eWorkers==MPM_THREADS )
1243 		Verify ( m_tLock.ReadLock() );
1244 }
1245 
1246 
Wlock() const1247 void IndexHash_c::Wlock () const
1248 {
1249 	if ( g_eWorkers==MPM_THREADS )
1250 		Verify ( m_tLock.WriteLock() );
1251 }
1252 
1253 
Unlock() const1254 void IndexHash_c::Unlock () const
1255 {
1256 	if ( g_eWorkers==MPM_THREADS )
1257 		Verify ( m_tLock.Unlock() );
1258 }
1259 
1260 
Add(const ServedDesc_t & tDesc,const CSphString & tKey)1261 bool IndexHash_c::Add ( const ServedDesc_t & tDesc, const CSphString & tKey )
1262 {
1263 	Wlock();
1264 	int iPrevSize = GetLength ();
1265 	ServedIndex_t & tVal = BASE::AddUnique ( tKey );
1266 	bool bAdded = ( iPrevSize<GetLength() );
1267 	if ( bAdded )
1268 	{
1269 		*( (ServedDesc_t *)&tVal ) = tDesc;
1270 		CSphString sError;
1271 		if ( !tVal.InitLock ( true, sError ) )
1272 		{
1273 			tVal.m_bAlterEnabled = false;
1274 			sphWarning ( "failed to init process shared rwlock: %s; ALTER disabled", sError.cstr() );
1275 			Verify ( tVal.InitLock ( false, sError ) );
1276 		}
1277 	}
1278 	Unlock();
1279 	return bAdded;
1280 }
1281 
1282 
Delete(const CSphString & tKey)1283 bool IndexHash_c::Delete ( const CSphString & tKey )
1284 {
1285 	// tricky part
1286 	// hash itself might be unlocked, but entry (!) might still be locked
1287 	// hence, we also need to acquire a lock on entry, and an exclusive one
1288 	Wlock();
1289 	bool bRes = false;
1290 	ServedIndex_t * pEntry = BASE::operator() ( tKey );
1291 	if ( pEntry )
1292 	{
1293 		pEntry->WriteLock();
1294 		pEntry->Unlock();
1295 		bRes = BASE::Delete ( tKey );
1296 	}
1297 	Unlock();
1298 	return bRes;
1299 }
1300 
1301 
GetRlockedEntry(const CSphString & tKey) const1302 const ServedIndex_t * IndexHash_c::GetRlockedEntry ( const CSphString & tKey ) const
1303 {
1304 	Rlock();
1305 	ServedIndex_t * pEntry = BASE::operator() ( tKey );
1306 	if ( pEntry )
1307 		pEntry->ReadLock();
1308 	Unlock();
1309 	return pEntry;
1310 }
1311 
1312 
GetWlockedEntry(const CSphString & tKey) const1313 ServedIndex_t * IndexHash_c::GetWlockedEntry ( const CSphString & tKey ) const
1314 {
1315 	Rlock();
1316 	ServedIndex_t * pEntry = BASE::operator() ( tKey );
1317 	if ( pEntry )
1318 		pEntry->WriteLock();
1319 	Unlock();
1320 	return pEntry;
1321 }
1322 
1323 
GetUnlockedEntry(const CSphString & tKey) const1324 ServedIndex_t & IndexHash_c::GetUnlockedEntry ( const CSphString & tKey ) const
1325 {
1326 	Rlock();
1327 	ServedIndex_t & tRes = BASE::operator[] ( tKey );
1328 	Unlock();
1329 	return tRes;
1330 }
1331 
GetUnlockedEntryP(const CSphString & tKey) const1332 ServedIndex_t * IndexHash_c::GetUnlockedEntryP ( const CSphString & tKey ) const
1333 {
1334 	Rlock();
1335 	ServedIndex_t * pRes = BASE::operator() ( tKey );
1336 	Unlock();
1337 	return pRes;
1338 }
1339 
1340 
1341 
Exists(const CSphString & tKey) const1342 bool IndexHash_c::Exists ( const CSphString & tKey ) const
1343 {
1344 	Rlock();
1345 	bool bRes = BASE::Exists ( tKey );
1346 	Unlock();
1347 	return bRes;
1348 }
1349 
1350 //////////////////////////////////////////////////////////////////////////
1351 
StaticThreadsOnlyMutex_t()1352 StaticThreadsOnlyMutex_t::StaticThreadsOnlyMutex_t ()
1353 {
1354 	if ( !m_tLock.Init() )
1355 		sphDie ( "failed to create static mutex" );
1356 }
1357 
~StaticThreadsOnlyMutex_t()1358 StaticThreadsOnlyMutex_t::~StaticThreadsOnlyMutex_t ()
1359 {
1360 	m_tLock.Done();
1361 }
1362 
Lock()1363 void StaticThreadsOnlyMutex_t::Lock ()
1364 {
1365 	if ( g_eWorkers==MPM_THREADS )
1366 		m_tLock.Lock();
1367 }
1368 
Unlock()1369 void StaticThreadsOnlyMutex_t::Unlock()
1370 {
1371 	if ( g_eWorkers==MPM_THREADS )
1372 		m_tLock.Unlock();
1373 }
1374 
StaticThreadProcMutex_t()1375 StaticThreadProcMutex_t::StaticThreadProcMutex_t ()
1376 {
1377 	if ( !m_tThdLock.Init () )
1378 		sphDie ( "failed to create thread mutex" );
1379 }
1380 
~StaticThreadProcMutex_t()1381 StaticThreadProcMutex_t::~StaticThreadProcMutex_t ()
1382 {
1383 	m_tThdLock.Done ();
1384 }
1385 
Lock()1386 void StaticThreadProcMutex_t::Lock ()
1387 {
1388 	if ( g_eWorkers==MPM_THREADS )
1389 		m_tThdLock.Lock ();
1390 	else
1391 		m_tProcLock.Lock();
1392 }
1393 
Unlock()1394 void StaticThreadProcMutex_t::Unlock ()
1395 {
1396 	if ( g_eWorkers==MPM_THREADS )
1397 		m_tThdLock.Unlock ();
1398 	else
1399 		m_tProcLock.Unlock();
1400 }
1401 
1402 
1403 /////////////////////////////////////////////////////////////////////////////
1404 // LOGGING
1405 /////////////////////////////////////////////////////////////////////////////
1406 
1407 void Shutdown (); // forward ref for sphFatal()
1408 
1409 
1410 /// format current timestamp for logging
sphFormatCurrentTime(char * sTimeBuf,int iBufLen)1411 int sphFormatCurrentTime ( char * sTimeBuf, int iBufLen )
1412 {
1413 	int64_t iNow = sphMicroTimer ();
1414 	time_t ts = (time_t) ( iNow/1000000 ); // on some systems (eg. FreeBSD 6.2), tv.tv_sec has another type and we can't just pass it
1415 
1416 #if !USE_WINDOWS
1417 	struct tm tmp;
1418 	localtime_r ( &ts, &tmp );
1419 #else
1420 	struct tm tmp;
1421 	tmp = *localtime ( &ts );
1422 #endif
1423 
1424 	static const char * sWeekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
1425 	static const char * sMonth[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1426 
1427 	return snprintf ( sTimeBuf, iBufLen, "%.3s %.3s%3d %.2d:%.2d:%.2d.%.3d %d",
1428 		sWeekday [ tmp.tm_wday ],
1429 		sMonth [ tmp.tm_mon ],
1430 		tmp.tm_mday, tmp.tm_hour,
1431 		tmp.tm_min, tmp.tm_sec, (int)((iNow%1000000)/1000),
1432 		1900+tmp.tm_year );
1433 }
1434 
1435 
1436 /// physically emit log entry
1437 /// buffer must have 1 extra byte for linefeed
1438 #if USE_WINDOWS
sphLogEntry(ESphLogLevel eLevel,char * sBuf,char * sTtyBuf)1439 void sphLogEntry ( ESphLogLevel eLevel, char * sBuf, char * sTtyBuf )
1440 #else
1441 void sphLogEntry ( ESphLogLevel , char * sBuf, char * sTtyBuf )
1442 #endif
1443 {
1444 #if USE_WINDOWS
1445 	if ( g_bService && g_iLogFile==STDOUT_FILENO )
1446 	{
1447 		HANDLE hEventSource;
1448 		LPCTSTR lpszStrings[2];
1449 
1450 		hEventSource = RegisterEventSource ( NULL, g_sServiceName );
1451 		if ( hEventSource )
1452 		{
1453 			lpszStrings[0] = g_sServiceName;
1454 			lpszStrings[1] = sBuf;
1455 
1456 			WORD eType = EVENTLOG_INFORMATION_TYPE;
1457 			switch ( eLevel )
1458 			{
1459 				case SPH_LOG_FATAL:		eType = EVENTLOG_ERROR_TYPE; break;
1460 				case SPH_LOG_WARNING:	eType = EVENTLOG_WARNING_TYPE; break;
1461 				case SPH_LOG_INFO:		eType = EVENTLOG_INFORMATION_TYPE; break;
1462 			}
1463 
1464 			ReportEvent ( hEventSource,	// event log handle
1465 				eType,					// event type
1466 				0,						// event category
1467 				0,						// event identifier
1468 				NULL,					// no security identifier
1469 				2,						// size of lpszStrings array
1470 				0,						// no binary data
1471 				lpszStrings,			// array of strings
1472 				NULL );					// no binary data
1473 
1474 			DeregisterEventSource ( hEventSource );
1475 		}
1476 
1477 	} else
1478 #endif
1479 	{
1480 		strcat ( sBuf, "\n" ); // NOLINT
1481 
1482 		sphSeek ( g_iLogFile, 0, SEEK_END );
1483 		if ( g_bLogTty )
1484 			sphWrite ( g_iLogFile, sTtyBuf, strlen(sTtyBuf) );
1485 		else
1486 			sphWrite ( g_iLogFile, sBuf, strlen(sBuf) );
1487 
1488 		if ( g_bLogStdout && g_iLogFile!=STDOUT_FILENO )
1489 			sphWrite ( STDOUT_FILENO, sTtyBuf, strlen(sTtyBuf) );
1490 	}
1491 }
1492 
1493 static int GetOsThreadId();
1494 
1495 /// log entry (with log levels, dupe catching, etc)
1496 /// call with NULL format for dupe flushing
sphLog(ESphLogLevel eLevel,const char * sFmt,va_list ap)1497 void sphLog ( ESphLogLevel eLevel, const char * sFmt, va_list ap )
1498 {
1499 	// dupe catcher state
1500 	static const int	FLUSH_THRESH_TIME	= 1000000; // in microseconds
1501 	static const int	FLUSH_THRESH_COUNT	= 100;
1502 
1503 	static ESphLogLevel eLastLevel = SPH_LOG_INFO;
1504 	static DWORD uLastEntry = 0;
1505 	static int64_t tmLastStamp = -1000000-FLUSH_THRESH_TIME;
1506 	static int iLastRepeats = 0;
1507 
1508 	// only if we can
1509 	if ( sFmt && eLevel>g_eLogLevel )
1510 		return;
1511 
1512 #if USE_SYSLOG
1513 	if ( g_bLogSyslog && sFmt )
1514 	{
1515 		const int levels[] = { LOG_EMERG, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG_DEBUG, LOG_DEBUG };
1516 		vsyslog ( levels[eLevel], sFmt, ap );
1517 		return;
1518 	}
1519 #endif
1520 
1521 	if ( g_iLogFile<0 && !g_bService )
1522 		return;
1523 
1524 	// format the banner
1525 	char sTimeBuf[128];
1526 	sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
1527 
1528 	const char * sBanner = "";
1529 	if ( sFmt==NULL ) eLevel = eLastLevel;
1530 	if ( eLevel==SPH_LOG_FATAL ) sBanner = "FATAL: ";
1531 	if ( eLevel==SPH_LOG_WARNING ) sBanner = "WARNING: ";
1532 	if ( eLevel>=SPH_LOG_DEBUG ) sBanner = "DEBUG: ";
1533 
1534 	char sBuf [ 1024 ];
1535 	snprintf ( sBuf, sizeof(sBuf)-1, "[%s] [%d] ", sTimeBuf, GetOsThreadId() );
1536 
1537 	char * sTtyBuf = sBuf + strlen(sBuf);
1538 	strncpy ( sTtyBuf, sBanner, 32 ); // 32 is arbitrary; just something that is enough and keeps lint happy
1539 
1540 	int iLen = strlen(sBuf);
1541 
1542 	// format the message
1543 	if ( sFmt )
1544 	{
1545 		// need more space for tail zero and "\n" that added at sphLogEntry
1546 		int iSafeGap = 4;
1547 		int iBufSize = sizeof(sBuf)-iLen-iSafeGap;
1548 		vsnprintf ( sBuf+iLen, iBufSize, sFmt, ap );
1549 		sBuf[ sizeof(sBuf)-iSafeGap ] = '\0';
1550 	}
1551 
1552 	if ( sFmt && eLevel>SPH_LOG_INFO && g_iLogFilterLen )
1553 	{
1554 		if ( strncmp ( sBuf+iLen, g_sLogFilter, g_iLogFilterLen )!=0 )
1555 			return;
1556 	}
1557 
1558 	// catch dupes
1559 	DWORD uEntry = sFmt ? sphCRC32 ( sBuf+iLen ) : 0;
1560 	int64_t tmNow = sphMicroTimer();
1561 
1562 	// accumulate while possible
1563 	if ( sFmt && eLevel==eLastLevel && uEntry==uLastEntry && iLastRepeats<FLUSH_THRESH_COUNT && tmNow<tmLastStamp+FLUSH_THRESH_TIME )
1564 	{
1565 		tmLastStamp = tmNow;
1566 		iLastRepeats++;
1567 		return;
1568 	}
1569 
1570 	// flush if needed
1571 	if ( iLastRepeats!=0 && ( sFmt || tmNow>=tmLastStamp+FLUSH_THRESH_TIME ) )
1572 	{
1573 		// flush if we actually have something to flush, and
1574 		// case 1: got a message we can't accumulate
1575 		// case 2: got a periodic flush and been otherwise idle for a thresh period
1576 		char sLast[256];
1577 		strncpy ( sLast, sBuf, iLen );
1578 		snprintf ( sLast+iLen, sizeof(sLast)-iLen, "last message repeated %d times", iLastRepeats );
1579 		sphLogEntry ( eLastLevel, sLast, sLast + ( sTtyBuf-sBuf ) );
1580 
1581 		tmLastStamp = tmNow;
1582 		iLastRepeats = 0;
1583 		eLastLevel = SPH_LOG_INFO;
1584 		uLastEntry = 0;
1585 	}
1586 
1587 	// was that a flush-only call?
1588 	if ( !sFmt )
1589 		return;
1590 
1591 	tmLastStamp = tmNow;
1592 	iLastRepeats = 0;
1593 	eLastLevel = eLevel;
1594 	uLastEntry = uEntry;
1595 
1596 	// do the logging
1597 	sphLogEntry ( eLevel, sBuf, sTtyBuf );
1598 }
1599 
1600 void sphFatal ( const char * sFmt, ... ) __attribute__ ( ( format ( printf, 1, 2 ) ) );
sphFatal(const char * sFmt,...)1601 void sphFatal ( const char * sFmt, ... )
1602 {
1603 	va_list ap;
1604 	va_start ( ap, sFmt );
1605 	sphLog ( SPH_LOG_FATAL, sFmt, ap );
1606 	va_end ( ap );
1607 	Shutdown ();
1608 	exit ( 1 );
1609 }
1610 
1611 #if !USE_WINDOWS
GetNamedPipeName(int iPid)1612 static CSphString GetNamedPipeName ( int iPid )
1613 {
1614 	CSphString sRes;
1615 	sRes.SetSprintf ( "/tmp/searchd_%d", iPid );
1616 	return sRes;
1617 }
1618 #endif
1619 
LogWarning(const char * sWarning)1620 void LogWarning ( const char * sWarning )
1621 {
1622 	sphWarning ( "%s", sWarning );
1623 }
1624 
1625 /////////////////////////////////////////////////////////////////////////////
1626 
CmpString(const CSphString & a,const CSphString & b)1627 static int CmpString ( const CSphString & a, const CSphString & b )
1628 {
1629 	if ( !a.cstr() && !b.cstr() )
1630 		return 0;
1631 
1632 	if ( !a.cstr() || !b.cstr() )
1633 		return a.cstr() ? -1 : 1;
1634 
1635 	return strcmp ( a.cstr(), b.cstr() );
1636 }
1637 
1638 struct SearchFailure_t
1639 {
1640 public:
1641 	CSphString	m_sIndex;	///< searched index name
1642 	CSphString	m_sError;	///< search error message
1643 
1644 public:
SearchFailure_tSearchFailure_t1645 	SearchFailure_t () {}
1646 
1647 public:
operator ==SearchFailure_t1648 	bool operator == ( const SearchFailure_t & r ) const
1649 	{
1650 		return m_sIndex==r.m_sIndex && m_sError==r.m_sError;
1651 	}
1652 
operator <SearchFailure_t1653 	bool operator < ( const SearchFailure_t & r ) const
1654 	{
1655 		int iRes = CmpString ( m_sError.cstr(), r.m_sError.cstr() );
1656 		if ( !iRes )
1657 			iRes = CmpString ( m_sIndex.cstr(), r.m_sIndex.cstr() );
1658 		return iRes<0;
1659 	}
1660 
operator =SearchFailure_t1661 	const SearchFailure_t & operator = ( const SearchFailure_t & r )
1662 	{
1663 		if ( this!=&r )
1664 		{
1665 			m_sIndex = r.m_sIndex;
1666 			m_sError = r.m_sError;
1667 		}
1668 		return *this;
1669 	}
1670 };
1671 
1672 
1673 class SearchFailuresLog_c
1674 {
1675 protected:
1676 	CSphVector<SearchFailure_t>		m_dLog;
1677 
1678 public:
Submit(const char * sIndex,const char * sError)1679 	void Submit ( const char * sIndex, const char * sError )
1680 	{
1681 		SearchFailure_t & tEntry = m_dLog.Add ();
1682 		tEntry.m_sIndex = sIndex;
1683 		tEntry.m_sError = sError;
1684 	}
1685 
SubmitEx(const char * sIndex,const char * sTemplate,...)1686 	void SubmitEx ( const char * sIndex, const char * sTemplate, ... ) __attribute__ ( ( format ( printf, 3, 4 ) ) )
1687 	{
1688 		SearchFailure_t & tEntry = m_dLog.Add ();
1689 		va_list ap;
1690 		va_start ( ap, sTemplate );
1691 		tEntry.m_sIndex = sIndex;
1692 		tEntry.m_sError.SetSprintfVa ( sTemplate, ap );
1693 		va_end ( ap );
1694 	}
1695 
1696 public:
IsEmpty()1697 	bool IsEmpty ()
1698 	{
1699 		return m_dLog.GetLength()==0;
1700 	}
1701 
BuildReport(CSphStringBuilder & sReport)1702 	void BuildReport ( CSphStringBuilder & sReport )
1703 	{
1704 		if ( IsEmpty() )
1705 			return;
1706 
1707 		// collapse same messages
1708 		m_dLog.Uniq ();
1709 		int iSpanStart = 0;
1710 
1711 		for ( int i=1; i<=m_dLog.GetLength(); i++ )
1712 		{
1713 			// keep scanning while error text is the same
1714 			if ( i!=m_dLog.GetLength() )
1715 				if ( m_dLog[i].m_sError==m_dLog[i-1].m_sError )
1716 					continue;
1717 
1718 			// build current span
1719 			CSphStringBuilder sSpan;
1720 			if ( iSpanStart )
1721 				sSpan += ";\n";
1722 			sSpan += "index ";
1723 			for ( int j=iSpanStart; j<i; j++ )
1724 			{
1725 				if ( j!=iSpanStart )
1726 					sSpan += ",";
1727 				sSpan += m_dLog[j].m_sIndex.cstr();
1728 			}
1729 			sSpan += ": ";
1730 			sSpan += m_dLog[iSpanStart].m_sError.cstr();
1731 
1732 			// flush current span
1733 			sReport += sSpan.cstr();
1734 
1735 			// done
1736 			iSpanStart = i;
1737 		}
1738 	}
1739 };
1740 
1741 /////////////////////////////////////////////////////////////////////////////
1742 // SIGNAL HANDLERS
1743 /////////////////////////////////////////////////////////////////////////////
1744 
1745 
1746 #if !USE_WINDOWS
UpdateAliveChildrenList(CSphVector<int> & dChildren)1747 static void UpdateAliveChildrenList ( CSphVector<int> & dChildren )
1748 {
1749 	ARRAY_FOREACH ( i, dChildren )
1750 	{
1751 		int iPID = dChildren[i];
1752 		int iStatus = 0;
1753 		if ( iPID>0 && waitpid ( iPID, &iStatus, WNOHANG )==iPID && ( WIFEXITED ( iStatus ) || WIFSIGNALED ( iStatus ) ) )
1754 			iPID = 0;
1755 
1756 		if ( iPID<=0 )
1757 			dChildren.RemoveFast ( i-- );
1758 	}
1759 }
1760 #endif
1761 
1762 
SaveIndexes()1763 static bool SaveIndexes ()
1764 {
1765 	CSphString sError;
1766 	bool bAllSaved = true;
1767 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
1768 	{
1769 		const ServedIndex_t & tServed = it.Get();
1770 		if ( !tServed.m_bEnabled )
1771 			continue;
1772 
1773 		tServed.ReadLock();
1774 		if ( !tServed.m_pIndex->SaveAttributes ( sError ) )
1775 		{
1776 			sphWarning ( "index %s: attrs save failed: %s", it.GetKey().cstr(), sError.cstr() );
1777 			bAllSaved = false;
1778 		}
1779 		tServed.Unlock();
1780 	}
1781 	return bAllSaved;
1782 }
1783 
1784 
Shutdown()1785 void Shutdown ()
1786 {
1787 #if !USE_WINDOWS
1788 	int fdStopwait = -1;
1789 #endif
1790 	bool bAttrsSaveOk = true;
1791 	g_bShutdown = true;
1792 
1793 	// some head-only shutdown procedures
1794 	if ( g_bHeadDaemon )
1795 	{
1796 		if ( !g_bDaemonAtShutdown.IsEmpty() )
1797 		{
1798 			*g_bDaemonAtShutdown.GetWritePtr() = 1;
1799 		}
1800 
1801 #if !USE_WINDOWS
1802 		// stopwait handshake
1803 		CSphString sPipeName = GetNamedPipeName ( getpid() );
1804 		fdStopwait = ::open ( sPipeName.cstr(), O_WRONLY | O_NONBLOCK );
1805 		if ( fdStopwait>=0 )
1806 		{
1807 			DWORD uHandshakeOk = 0;
1808 			int iDummy; // to avoid gcc unused result warning
1809 			iDummy = ::write ( fdStopwait, &uHandshakeOk, sizeof(DWORD) );
1810 			iDummy++; // to avoid gcc set but not used variable warning
1811 		}
1812 #endif
1813 		sphThreadJoin ( &g_tRotationServiceThread );
1814 		sphThreadJoin ( &g_tPingThread );
1815 
1816 		if ( g_eWorkers==MPM_THREADS )
1817 		{
1818 			// tell flush-rt thread to shutdown, and wait until it does
1819 			sphThreadJoin ( &g_tRtFlushThread );
1820 
1821 			// tell rotation thread to shutdown, and wait until it does
1822 			if ( g_bSeamlessRotate )
1823 			{
1824 				sphThreadJoin ( &g_tRotateThread );
1825 			}
1826 
1827 			// tell uservars flush thread to shutdown, and wait until it does
1828 			if ( !g_sSphinxqlState.IsEmpty() )
1829 				sphThreadJoin ( &g_tSphinxqlStateFlushThread );
1830 
1831 			sphThreadJoin ( &g_tOptimizeThread );
1832 
1833 			int64_t tmShutStarted = sphMicroTimer();
1834 			// stop search threads; up to shutdown_timeout seconds
1835 			while ( g_dThd.GetLength() > 0 && ( sphMicroTimer()-tmShutStarted )<g_iShutdownTimeout )
1836 				sphSleepMsec ( 50 );
1837 		}
1838 
1839 #if !USE_WINDOWS
1840 		if ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK )
1841 		{
1842 			// in *forked mode, explicitly kill all children
1843 			ARRAY_FOREACH ( i, g_dChildren )
1844 			{
1845 				sphLogDebug ( "killing child %d", g_dChildren[i] );
1846 				kill ( g_dChildren[i], SIGTERM );
1847 			}
1848 
1849 			int64_t tmShutStarted = sphMicroTimer();
1850 			// stop search threads; up to shutdown_timeout seconds
1851 			while ( g_dChildren.GetLength()>0 && ( sphMicroTimer()-tmShutStarted )<g_iShutdownTimeout )
1852 			{
1853 				UpdateAliveChildrenList ( g_dChildren );
1854 				sphSleepMsec ( 50 );
1855 			}
1856 
1857 			if ( g_dChildren.GetLength() )
1858 			{
1859 				ARRAY_FOREACH ( i, g_dChildren )
1860 					kill ( g_dChildren[i], SIGKILL );
1861 
1862 				sphSleepMsec ( 100 );
1863 				UpdateAliveChildrenList ( g_dChildren );
1864 				if ( g_dChildren.GetLength() )
1865 					sphWarning ( "there are still %d alive children", g_dChildren.GetLength() );
1866 			}
1867 		}
1868 #endif
1869 
1870 		CSphString sError;
1871 		// save attribute updates for all local indexes
1872 		bAttrsSaveOk = SaveIndexes();
1873 
1874 		// unlock indexes and release locks if needed
1875 		for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
1876 			if ( it.Get().m_pIndex )
1877 				it.Get().m_pIndex->Unlock();
1878 		g_pLocalIndexes->Reset();
1879 
1880 		// unlock indexes and release locks if needed
1881 		for ( IndexHashIterator_c it ( g_pTemplateIndexes ); it.Next(); )
1882 			if ( it.Get().m_pIndex )
1883 				it.Get().m_pIndex->Unlock();
1884 		g_pTemplateIndexes->Reset();
1885 
1886 		// clear shut down of rt indexes + binlog
1887 		SafeDelete ( g_pLocalIndexes );
1888 		SafeDelete ( g_pTemplateIndexes );
1889 		sphDoneIOStats();
1890 		sphRTDone();
1891 
1892 		sphShutdownWordforms ();
1893 		sphShutdownGlobalIDFs ();
1894 		sphAotShutdown ();
1895 	}
1896 
1897 	ARRAY_FOREACH ( i, g_dListeners )
1898 		if ( g_dListeners[i].m_iSock>=0 )
1899 			sphSockClose ( g_dListeners[i].m_iSock );
1900 
1901 	{
1902 		CSphScopedLock<StaticThreadsOnlyMutex_t> tLock ( g_tPersLock );
1903 		ARRAY_FOREACH ( i, g_dPersistentConnections )
1904 		{
1905 			if ( ( i % g_iPersistentPoolSize ) && g_dPersistentConnections[i]>=0 )
1906 				sphSockClose ( g_dPersistentConnections[i] );
1907 			g_dPersistentConnections[i] = -1;
1908 		}
1909 	}
1910 
1911 	// remove pid
1912 	if ( g_bHeadDaemon && !g_sPidFile.IsEmpty() )
1913 	{
1914 		::close ( g_iPidFD );
1915 		::unlink ( g_sPidFile.cstr() );
1916 	}
1917 
1918 	if ( g_bHeadDaemon )
1919 		sphInfo ( "shutdown complete" );
1920 
1921 	if ( g_bHeadDaemon )
1922 	{
1923 		SphCrashLogger_c::Done();
1924 		sphThreadDone ( g_iLogFile );
1925 	}
1926 
1927 #if USE_WINDOWS
1928 	CloseHandle ( g_hPipe );
1929 #else
1930 	if ( g_bHeadDaemon && fdStopwait>=0 )
1931 	{
1932 		DWORD uStatus = bAttrsSaveOk;
1933 		int iDummy; // to avoid gcc unused result warning
1934 		iDummy = ::write ( fdStopwait, &uStatus, sizeof(DWORD) );
1935 		iDummy++; // to avoid gcc set but not used variable warning
1936 		::close ( fdStopwait );
1937 	}
1938 #endif
1939 }
1940 
1941 #if !USE_WINDOWS
sighup(int)1942 void sighup ( int )
1943 {
1944 	g_bGotSighup = 1;
1945 }
1946 
1947 
sigterm(int)1948 void sigterm ( int )
1949 {
1950 	// tricky bit
1951 	// we can't call exit() here because malloc()/free() are not re-entrant
1952 	// we could call _exit() but let's try to die gracefully on TERM
1953 	// and let signal sender wait and send KILL as needed
1954 	g_bGotSigterm = 1;
1955 	sphInterruptNow();
1956 }
1957 
1958 
sigchld(int)1959 void sigchld ( int )
1960 {
1961 	g_bGotSigchld = 1;
1962 }
1963 
1964 
sigusr1(int)1965 void sigusr1 ( int )
1966 {
1967 	g_bGotSigusr1 = 1;
1968 }
1969 #endif // !USE_WINDOWS
1970 
1971 
1972 struct QueryCopyState_t
1973 {
1974 	BYTE * m_pDst;
1975 	BYTE * m_pDstEnd;
1976 	const BYTE * m_pSrc;
1977 	const BYTE * m_pSrcEnd;
1978 };
1979 
1980 // crash query handler
1981 static const int g_iQueryLineLen = 80;
1982 static const char g_dEncodeBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
sphCopyEncodedBase64(QueryCopyState_t & tEnc)1983 bool sphCopyEncodedBase64 ( QueryCopyState_t & tEnc )
1984 {
1985 	BYTE * pDst = tEnc.m_pDst;
1986 	const BYTE * pDstBase = tEnc.m_pDst;
1987 	const BYTE * pSrc = tEnc.m_pSrc;
1988 	const BYTE * pDstEnd = tEnc.m_pDstEnd-5;
1989 	const BYTE * pSrcEnd = tEnc.m_pSrcEnd-3;
1990 
1991 	while ( pDst<=pDstEnd && pSrc<=pSrcEnd )
1992 	{
1993 		// put line delimiter at max line length
1994 		if ( ( ( pDst-pDstBase ) % g_iQueryLineLen )>( ( pDst-pDstBase+4 ) % g_iQueryLineLen ) )
1995 			*pDst++ = '\n';
1996 
1997 		// Convert to big endian
1998 		DWORD uSrc = ( pSrc[0] << 16 ) | ( pSrc[1] << 8 ) | ( pSrc[2] );
1999 		pSrc += 3;
2000 
2001 		*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x00FC0000 ) >> 18 ];
2002 		*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x0003F000 ) >> 12 ];
2003 		*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x00000FC0 ) >> 6 ];
2004 		*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x0000003F ) ];
2005 	}
2006 
2007 	// there is a tail in source data and a room for it at destination buffer
2008 	if ( pSrc<tEnc.m_pSrcEnd && ( tEnc.m_pSrcEnd-pSrc<3 ) && ( pDst<=pDstEnd-4 ) )
2009 	{
2010 		int iLeft = ( tEnc.m_pSrcEnd - pSrc ) % 3;
2011 		if ( iLeft==1 )
2012 		{
2013 			DWORD uSrc = pSrc[0]<<16;
2014 			pSrc += 1;
2015 			*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x00FC0000 ) >> 18 ];
2016 			*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x0003F000 ) >> 12 ];
2017 			*pDst++ = '=';
2018 			*pDst++ = '=';
2019 		} else if ( iLeft==2 )
2020 		{
2021 			DWORD uSrc = ( pSrc[0]<<16 ) | ( pSrc[1] << 8 );
2022 			pSrc += 2;
2023 			*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x00FC0000 ) >> 18 ];
2024 			*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x0003F000 ) >> 12 ];
2025 			*pDst++ = g_dEncodeBase64 [ ( uSrc & 0x00000FC0 ) >> 6 ];
2026 			*pDst++ = '=';
2027 		}
2028 	}
2029 
2030 	tEnc.m_pDst = pDst;
2031 	tEnc.m_pSrc = pSrc;
2032 
2033 	return ( tEnc.m_pSrc<tEnc.m_pSrcEnd );
2034 }
2035 
sphCopySphinxQL(QueryCopyState_t & tState)2036 static bool sphCopySphinxQL ( QueryCopyState_t & tState )
2037 {
2038 	BYTE * pDst = tState.m_pDst;
2039 	const BYTE * pSrc = tState.m_pSrc;
2040 	BYTE * pNextLine = pDst+g_iQueryLineLen;
2041 
2042 	while ( pDst<tState.m_pDstEnd && pSrc<tState.m_pSrcEnd )
2043 	{
2044 		if ( pDst>pNextLine && pDst+1<tState.m_pDstEnd && ( sphIsSpace ( *pSrc ) || *pSrc==',' ) )
2045 		{
2046 			*pDst++ = *pSrc++;
2047 			*pDst++ = '\n';
2048 			pNextLine = pDst + g_iQueryLineLen;
2049 		} else
2050 		{
2051 			*pDst++ = *pSrc++;
2052 		}
2053 	}
2054 
2055 	tState.m_pDst = pDst;
2056 	tState.m_pSrc = pSrc;
2057 
2058 	return ( tState.m_pSrc<tState.m_pSrcEnd );
2059 }
2060 
2061 typedef bool CopyQuery_fn ( QueryCopyState_t & tState );
2062 
2063 #define SPH_TIME_PID_MAX_SIZE 256
2064 const char		g_sCrashedBannerAPI[] = "\n--- crashed SphinxAPI request dump ---\n";
2065 const char		g_sCrashedBannerMySQL[] = "\n--- crashed SphinxQL request dump ---\n";
2066 const char		g_sCrashedBannerTail[] = "\n--- request dump end ---\n";
2067 #if USE_WINDOWS
2068 const char		g_sMinidumpBanner[] = "minidump located at: ";
2069 const char		g_sMemoryStatBanner[] = "\n--- memory statistics ---\n";
2070 #endif
2071 static BYTE		g_dCrashQueryBuff [4096];
2072 static char		g_sCrashInfo [SPH_TIME_PID_MAX_SIZE] = "[][]\n";
2073 static int		g_iCrashInfoLen = 0;
2074 
2075 #if USE_WINDOWS
2076 static char		g_sMinidump[SPH_TIME_PID_MAX_SIZE] = "";
2077 #endif
2078 
2079 SphThreadKey_t SphCrashLogger_c::m_tTLS = SphThreadKey_t ();
2080 
2081 static CrashQuery_t g_tUnhandled;
2082 
Init()2083 void SphCrashLogger_c::Init ()
2084 {
2085 	Verify ( sphThreadKeyCreate ( &m_tTLS ) );
2086 }
2087 
Done()2088 void SphCrashLogger_c::Done ()
2089 {
2090 	sphThreadKeyDelete ( m_tTLS );
2091 }
2092 
2093 
2094 #if !USE_WINDOWS
HandleCrash(int sig)2095 void SphCrashLogger_c::HandleCrash ( int sig )
2096 #else
2097 LONG WINAPI SphCrashLogger_c::HandleCrash ( EXCEPTION_POINTERS * pExc )
2098 #endif // !USE_WINDOWS
2099 {
2100 	if ( g_iLogFile<0 )
2101 		CRASH_EXIT;
2102 
2103 	// log [time][pid]
2104 	sphSeek ( g_iLogFile, 0, SEEK_END );
2105 	sphWrite ( g_iLogFile, g_sCrashInfo, g_iCrashInfoLen );
2106 
2107 	// log query
2108 	CrashQuery_t tQuery = SphCrashLogger_c::GetQuery();
2109 
2110 	// request dump banner
2111 	int iBannerLen = ( tQuery.m_bMySQL ? sizeof(g_sCrashedBannerMySQL) : sizeof(g_sCrashedBannerAPI) ) - 1;
2112 	const char * pBanner = tQuery.m_bMySQL ? g_sCrashedBannerMySQL : g_sCrashedBannerAPI;
2113 	sphWrite ( g_iLogFile, pBanner, iBannerLen );
2114 
2115 	// query
2116 	if ( tQuery.m_iSize )
2117 	{
2118 		QueryCopyState_t tCopyState;
2119 		tCopyState.m_pDst = g_dCrashQueryBuff;
2120 		tCopyState.m_pDstEnd = g_dCrashQueryBuff + sizeof(g_dCrashQueryBuff);
2121 		tCopyState.m_pSrc = tQuery.m_pQuery;
2122 		tCopyState.m_pSrcEnd = tQuery.m_pQuery + tQuery.m_iSize;
2123 
2124 		CopyQuery_fn * pfnCopy = NULL;
2125 		if ( !tQuery.m_bMySQL )
2126 		{
2127 			pfnCopy = &sphCopyEncodedBase64;
2128 
2129 			// should be power of 3 to seamlessly convert to BASE64
2130 			BYTE dHeader[] = {
2131 				(BYTE)( ( tQuery.m_uCMD>>8 ) & 0xff ),
2132 				(BYTE)( tQuery.m_uCMD & 0xff ),
2133 				(BYTE)( ( tQuery.m_uVer>>8 ) & 0xff ),
2134 				(BYTE)( tQuery.m_uVer & 0xff ),
2135 				(BYTE)( ( tQuery.m_iSize>>24 ) & 0xff ),
2136 				(BYTE)( ( tQuery.m_iSize>>16 ) & 0xff ),
2137 				(BYTE)( ( tQuery.m_iSize>>8 ) & 0xff ),
2138 				(BYTE)( tQuery.m_iSize & 0xff ),
2139 				*tQuery.m_pQuery
2140 			};
2141 
2142 			QueryCopyState_t tHeaderState;
2143 			tHeaderState.m_pDst = g_dCrashQueryBuff;
2144 			tHeaderState.m_pDstEnd = g_dCrashQueryBuff + sizeof(g_dCrashQueryBuff);
2145 			tHeaderState.m_pSrc = dHeader;
2146 			tHeaderState.m_pSrcEnd = dHeader + sizeof(dHeader);
2147 			pfnCopy ( tHeaderState );
2148 			assert ( tHeaderState.m_pSrc==tHeaderState.m_pSrcEnd );
2149 			tCopyState.m_pDst = tHeaderState.m_pDst;
2150 			tCopyState.m_pSrc++;
2151 		} else
2152 		{
2153 			pfnCopy = &sphCopySphinxQL;
2154 		}
2155 
2156 		while ( pfnCopy ( tCopyState ) )
2157 		{
2158 			sphWrite ( g_iLogFile, g_dCrashQueryBuff, tCopyState.m_pDst-g_dCrashQueryBuff );
2159 			tCopyState.m_pDst = g_dCrashQueryBuff; // reset the destination buffer
2160 		}
2161 		assert ( tCopyState.m_pSrc==tCopyState.m_pSrcEnd );
2162 
2163 		int iLeft = tCopyState.m_pDst-g_dCrashQueryBuff;
2164 		if ( iLeft>0 )
2165 		{
2166 			sphWrite ( g_iLogFile, g_dCrashQueryBuff, iLeft );
2167 		}
2168 	}
2169 
2170 	// tail
2171 	sphWrite ( g_iLogFile, g_sCrashedBannerTail, sizeof(g_sCrashedBannerTail)-1 );
2172 
2173 	sphSafeInfo ( g_iLogFile, "Sphinx " SPHINX_VERSION );
2174 
2175 #if USE_WINDOWS
2176 	// mini-dump reference
2177 	int iMiniDumpLen = snprintf ( (char *)g_dCrashQueryBuff, sizeof(g_dCrashQueryBuff),
2178 		"%s %s.%p.mdmp\n", g_sMinidumpBanner, g_sMinidump, tQuery.m_pQuery );
2179 	sphWrite ( g_iLogFile, g_dCrashQueryBuff, iMiniDumpLen );
2180 	snprintf ( (char *)g_dCrashQueryBuff, sizeof(g_dCrashQueryBuff), "%s.%p.mdmp",
2181 		g_sMinidump, tQuery.m_pQuery );
2182 #endif
2183 
2184 	// log trace
2185 #if !USE_WINDOWS
2186 	sphSafeInfo ( g_iLogFile, "Handling signal %d", sig );
2187 	// print message to stdout during daemon start
2188 	if ( g_bLogStdout && g_iLogFile!=STDOUT_FILENO )
2189 		sphSafeInfo ( STDOUT_FILENO, "Crash!!! Handling signal %d", sig );
2190 	sphBacktrace ( g_iLogFile, g_bSafeTrace );
2191 #else
2192 	sphBacktrace ( pExc, (char *)g_dCrashQueryBuff );
2193 #endif
2194 
2195 	// threads table
2196 	if ( g_eWorkers==MPM_THREADS )
2197 	{
2198 		// FIXME? should we try to lock threads table somehow?
2199 		sphSafeInfo ( g_iLogFile, "--- %d active threads ---", g_dThd.GetLength() );
2200 		const ListNode_t * pIt = g_dThd.Begin();
2201 		int iThd = 0;
2202 		while ( pIt!=g_dThd.End() )
2203 		{
2204 			ThdDesc_t * pThd = (ThdDesc_t *)pIt;
2205 			sphSafeInfo ( g_iLogFile, "thd %d, proto %s, state %s, command %s",
2206 				iThd,
2207 				g_dProtoNames[pThd->m_eProto],
2208 				g_dThdStates[pThd->m_eThdState],
2209 				pThd->m_sCommand ? pThd->m_sCommand : "-" );
2210 			pIt = pIt->m_pNext;
2211 			iThd++;
2212 		}
2213 	}
2214 
2215 	// memory info
2216 #if SPH_ALLOCS_PROFILER
2217 	sphWrite ( g_iLogFile, g_sMemoryStatBanner, sizeof ( g_sMemoryStatBanner )-1 );
2218 	sphMemStatDump ( g_iLogFile );
2219 #endif
2220 
2221 	sphSafeInfo ( g_iLogFile, "------- CRASH DUMP END -------" );
2222 
2223 	CRASH_EXIT;
2224 }
2225 
SetLastQuery(const CrashQuery_t & tQuery)2226 void SphCrashLogger_c::SetLastQuery ( const CrashQuery_t & tQuery )
2227 {
2228 	SphCrashLogger_c * pCrashLogger = (SphCrashLogger_c *)sphThreadGet ( m_tTLS );
2229 	assert ( pCrashLogger );
2230 	pCrashLogger->m_tQuery = tQuery;
2231 }
2232 
SetupTimePID()2233 void SphCrashLogger_c::SetupTimePID ()
2234 {
2235 	char sTimeBuf[SPH_TIME_PID_MAX_SIZE];
2236 	sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
2237 
2238 	g_iCrashInfoLen = snprintf ( g_sCrashInfo, SPH_TIME_PID_MAX_SIZE-1,
2239 		"------- FATAL: CRASH DUMP -------\n[%s] [%5d]\n", sTimeBuf, (int)getpid() );
2240 }
2241 
SetupTLS()2242 void SphCrashLogger_c::SetupTLS ()
2243 {
2244 	Verify ( sphThreadSet ( m_tTLS, this ) );
2245 }
2246 
GetQuery()2247 CrashQuery_t SphCrashLogger_c::GetQuery()
2248 {
2249 	SphCrashLogger_c * pCrashLogger = (SphCrashLogger_c *)sphThreadGet ( m_tTLS );
2250 
2251 	// in case TLS not set \ found handler still should process crash
2252 	// FIXME!!! some service threads use raw threads instead ThreadCreate
2253 	if ( !pCrashLogger )
2254 		return g_tUnhandled;
2255 	else
2256 		return pCrashLogger->m_tQuery;
2257 }
2258 
ThreadCreate(SphThread_t * pThread,void (* pCall)(void *),void * pArg,bool bDetached)2259 bool SphCrashLogger_c::ThreadCreate ( SphThread_t * pThread, void (*pCall)(void*), void * pArg, bool bDetached )
2260 {
2261 	CallArgPair_t * pWrapperArg = new CallArgPair_t ( pCall, pArg );
2262 	bool bSuccess = sphThreadCreate ( pThread, ThreadWrapper, pWrapperArg, bDetached );
2263 	if ( !bSuccess )
2264 		delete pWrapperArg;
2265 	return bSuccess;
2266 }
2267 
ThreadWrapper(void * pArg)2268 void SphCrashLogger_c::ThreadWrapper ( void * pArg )
2269 {
2270 	CallArgPair_t * pPair = static_cast<CallArgPair_t *> ( pArg );
2271 	SphCrashLogger_c tQueryTLS;
2272 	tQueryTLS.SetupTLS();
2273 	pPair->m_pCall ( pPair->m_pArg );
2274 	delete pPair;
2275 }
2276 
2277 
2278 #if USE_WINDOWS
SetSignalHandlers(bool)2279 void SetSignalHandlers ( bool )
2280 {
2281 	SphCrashLogger_c::Init();
2282 	snprintf ( g_sMinidump, SPH_TIME_PID_MAX_SIZE-1, "%s.%d", g_sPidFile.scstr(), (int)getpid() );
2283 	SetUnhandledExceptionFilter ( SphCrashLogger_c::HandleCrash );
2284 }
2285 #else
SetSignalHandlers(bool bAllowCtrlC=false)2286 void SetSignalHandlers ( bool bAllowCtrlC=false )
2287 {
2288 	SphCrashLogger_c::Init();
2289 	struct sigaction sa;
2290 	sigfillset ( &sa.sa_mask );
2291 	sa.sa_flags = SA_NOCLDSTOP;
2292 
2293 	bool bSignalsSet = false;
2294 	for ( ;; )
2295 	{
2296 		sa.sa_handler = sigterm;	if ( sigaction ( SIGTERM, &sa, NULL )!=0 ) break;
2297 		if ( !bAllowCtrlC )
2298 		{
2299 			sa.sa_handler = sigterm;
2300 			if ( sigaction ( SIGINT, &sa, NULL )!=0 )
2301 				break;
2302 		}
2303 		sa.sa_handler = sighup;		if ( sigaction ( SIGHUP, &sa, NULL )!=0 ) break;
2304 		sa.sa_handler = sigusr1;	if ( sigaction ( SIGUSR1, &sa, NULL )!=0 ) break;
2305 		sa.sa_handler = sigchld;	if ( sigaction ( SIGCHLD, &sa, NULL )!=0 ) break;
2306 		sa.sa_handler = SIG_IGN;	if ( sigaction ( SIGPIPE, &sa, NULL )!=0 ) break;
2307 
2308 		sa.sa_flags |= SA_RESETHAND;
2309 		sa.sa_handler = SphCrashLogger_c::HandleCrash;	if ( sigaction ( SIGSEGV, &sa, NULL )!=0 ) break;
2310 		sa.sa_handler = SphCrashLogger_c::HandleCrash;	if ( sigaction ( SIGBUS, &sa, NULL )!=0 ) break;
2311 		sa.sa_handler = SphCrashLogger_c::HandleCrash;	if ( sigaction ( SIGABRT, &sa, NULL )!=0 ) break;
2312 		sa.sa_handler = SphCrashLogger_c::HandleCrash;	if ( sigaction ( SIGILL, &sa, NULL )!=0 ) break;
2313 		sa.sa_handler = SphCrashLogger_c::HandleCrash;	if ( sigaction ( SIGFPE, &sa, NULL )!=0 ) break;
2314 
2315 		bSignalsSet = true;
2316 		break;
2317 	}
2318 	if ( !bSignalsSet )
2319 		sphFatal ( "sigaction(): %s", strerror(errno) );
2320 }
2321 #endif
2322 
2323 
2324 /////////////////////////////////////////////////////////////////////////////
2325 // NETWORK STUFF
2326 /////////////////////////////////////////////////////////////////////////////
2327 
2328 #if USE_WINDOWS
2329 
2330 const int		WIN32_PIPE_BUFSIZE		= 32;
2331 
2332 /// on Windows, the wrapper just prevents the warnings
sphFDSet(int fd,fd_set * fdset)2333 void sphFDSet ( int fd, fd_set * fdset )
2334 {
2335 	#pragma warning(push) // store current warning values
2336 	#pragma warning(disable:4127) // conditional expr is const
2337 	#pragma warning(disable:4389) // signed/unsigned mismatch
2338 
2339 	FD_SET ( fd, fdset );
2340 
2341 	#pragma warning(pop) // restore warnings
2342 }
2343 
2344 #else // !USE_WINDOWS
2345 
2346 #define SPH_FDSET_OVERFLOW(_fd) ( (_fd)<0 || (_fd)>=(int)FD_SETSIZE )
2347 
2348 /// on UNIX, we also check that the descript won't corrupt the stack
sphFDSet(int fd,fd_set * set)2349 void sphFDSet ( int fd, fd_set * set )
2350 {
2351 	if ( SPH_FDSET_OVERFLOW(fd) )
2352 		sphFatal ( "sphFDSet() failed fd=%d, FD_SETSIZE=%d", fd, FD_SETSIZE );
2353 	else
2354 		FD_SET ( fd, set );
2355 }
2356 
2357 #endif // USE_WINDOWS
2358 
2359 
2360 #if USE_WINDOWS
sphSockError(int iErr=0)2361 const char * sphSockError ( int iErr=0 )
2362 {
2363 	if ( iErr==0 )
2364 		iErr = WSAGetLastError ();
2365 
2366 	static char sBuf [ 256 ];
2367 	_snprintf ( sBuf, sizeof(sBuf), "WSA error %d", iErr );
2368 	return sBuf;
2369 }
2370 #else
sphSockError(int=0)2371 const char * sphSockError ( int =0 )
2372 {
2373 	return strerror ( errno );
2374 }
2375 #endif
2376 
2377 
sphSockGetErrno()2378 int sphSockGetErrno ()
2379 {
2380 	#if USE_WINDOWS
2381 		return WSAGetLastError();
2382 	#else
2383 		return errno;
2384 	#endif
2385 }
2386 
2387 
sphSockSetErrno(int iErr)2388 void sphSockSetErrno ( int iErr )
2389 {
2390 	#if USE_WINDOWS
2391 		WSASetLastError ( iErr );
2392 	#else
2393 		errno = iErr;
2394 	#endif
2395 }
2396 
2397 
sphSockPeekErrno()2398 int sphSockPeekErrno ()
2399 {
2400 	int iRes = sphSockGetErrno();
2401 	sphSockSetErrno ( iRes );
2402 	return iRes;
2403 }
2404 
2405 
2406 /// formats IP address given in network byte order into sBuffer
2407 /// returns the buffer
sphFormatIP(char * sBuffer,int iBufferSize,DWORD uAddress)2408 char * sphFormatIP ( char * sBuffer, int iBufferSize, DWORD uAddress )
2409 {
2410 	const BYTE *a = (const BYTE *)&uAddress;
2411 	snprintf ( sBuffer, iBufferSize, "%u.%u.%u.%u", a[0], a[1], a[2], a[3] );
2412 	return sBuffer;
2413 }
2414 
2415 
2416 static const bool GETADDR_STRICT = true; ///< strict check, will die with sphFatal() on failure
2417 
sphGetAddress(const char * sHost,bool bFatal=false)2418 DWORD sphGetAddress ( const char * sHost, bool bFatal=false )
2419 {
2420 	struct hostent * pHost = gethostbyname ( sHost );
2421 
2422 	if ( pHost==NULL || pHost->h_addrtype!=AF_INET )
2423 	{
2424 		if ( bFatal )
2425 			sphFatal ( "no AF_INET address found for: %s", sHost );
2426 		return 0;
2427 	}
2428 
2429 	struct in_addr ** ppAddrs = (struct in_addr **)pHost->h_addr_list;
2430 	assert ( ppAddrs[0] );
2431 
2432 	assert ( sizeof(DWORD)==pHost->h_length );
2433 	DWORD uAddr;
2434 	memcpy ( &uAddr, ppAddrs[0], sizeof(DWORD) );
2435 
2436 	if ( ppAddrs[1] )
2437 	{
2438 		char sBuf [ SPH_ADDRESS_SIZE ];
2439 		sphWarning ( "multiple addresses found for '%s', using the first one (ip=%s)",
2440 			sHost, sphFormatIP ( sBuf, sizeof(sBuf), uAddr ) );
2441 	}
2442 
2443 	return uAddr;
2444 }
2445 
2446 
2447 #if !USE_WINDOWS
sphCreateUnixSocket(const char * sPath)2448 int sphCreateUnixSocket ( const char * sPath )
2449 {
2450 	static struct sockaddr_un uaddr;
2451 	size_t len = strlen ( sPath );
2452 
2453 	if ( len + 1 > sizeof( uaddr.sun_path ) )
2454 		sphFatal ( "UNIX socket path is too long (len=%d)", (int)len );
2455 
2456 	sphInfo ( "listening on UNIX socket %s", sPath );
2457 
2458 	memset ( &uaddr, 0, sizeof(uaddr) );
2459 	uaddr.sun_family = AF_UNIX;
2460 	memcpy ( uaddr.sun_path, sPath, len + 1 );
2461 
2462 	int iSock = socket ( AF_UNIX, SOCK_STREAM, 0 );
2463 	if ( iSock==-1 )
2464 		sphFatal ( "failed to create UNIX socket: %s", sphSockError() );
2465 
2466 	if ( unlink ( sPath )==-1 )
2467 	{
2468 		if ( errno!=ENOENT )
2469 			sphFatal ( "unlink() on UNIX socket file failed: %s", sphSockError() );
2470 	}
2471 
2472 	int iMask = umask ( 0 );
2473 	if ( bind ( iSock, (struct sockaddr *)&uaddr, sizeof(uaddr) )!=0 )
2474 		sphFatal ( "bind() on UNIX socket failed: %s", sphSockError() );
2475 	umask ( iMask );
2476 
2477 	return iSock;
2478 }
2479 #endif // !USE_WINDOWS
2480 
2481 
sphCreateInetSocket(DWORD uAddr,int iPort)2482 int sphCreateInetSocket ( DWORD uAddr, int iPort )
2483 {
2484 	char sAddress[SPH_ADDRESS_SIZE];
2485 	sphFormatIP ( sAddress, SPH_ADDRESS_SIZE, uAddr );
2486 
2487 	if ( uAddr==htonl ( INADDR_ANY ) )
2488 		sphInfo ( "listening on all interfaces, port=%d", iPort );
2489 	else
2490 		sphInfo ( "listening on %s:%d", sAddress, iPort );
2491 
2492 	static struct sockaddr_in iaddr;
2493 	memset ( &iaddr, 0, sizeof(iaddr) );
2494 	iaddr.sin_family = AF_INET;
2495 	iaddr.sin_addr.s_addr = uAddr;
2496 	iaddr.sin_port = htons ( (short)iPort );
2497 
2498 	int iSock = socket ( AF_INET, SOCK_STREAM, 0 );
2499 	if ( iSock==-1 )
2500 		sphFatal ( "failed to create TCP socket: %s", sphSockError() );
2501 
2502 	int iOn = 1;
2503 	if ( setsockopt ( iSock, SOL_SOCKET, SO_REUSEADDR, (char*)&iOn, sizeof(iOn) ) )
2504 		sphWarning ( "setsockopt() failed: %s", sphSockError() );
2505 #ifdef TCP_NODELAY
2506 	if ( setsockopt ( iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&iOn, sizeof(iOn) ) )
2507 		sphWarning ( "setsockopt() failed: %s", sphSockError() );
2508 #endif
2509 
2510 	int iTries = 12;
2511 	int iRes;
2512 	do
2513 	{
2514 		iRes = bind ( iSock, (struct sockaddr *)&iaddr, sizeof(iaddr) );
2515 		if ( iRes==0 )
2516 			break;
2517 
2518 		sphInfo ( "bind() failed on %s, retrying...", sAddress );
2519 		sphSleepMsec ( 3000 );
2520 	} while ( --iTries>0 );
2521 	if ( iRes )
2522 		sphFatal ( "bind() failed on %s: %s", sAddress, sphSockError() );
2523 
2524 	return iSock;
2525 }
2526 
2527 
IsPortInRange(int iPort)2528 inline bool IsPortInRange ( int iPort )
2529 {
2530 	return ( iPort>0 ) && ( iPort<=0xFFFF );
2531 }
2532 
2533 
CheckPort(int iPort)2534 void CheckPort ( int iPort )
2535 {
2536 	if ( !IsPortInRange(iPort) )
2537 		sphFatal ( "port %d is out of range", iPort );
2538 }
2539 
2540 
ProtoByName(const CSphString & sProto)2541 ProtocolType_e ProtoByName ( const CSphString & sProto )
2542 {
2543 	if ( sProto=="sphinx" )			return PROTO_SPHINX;
2544 	else if ( sProto=="mysql41" )	return PROTO_MYSQL41;
2545 
2546 	sphFatal ( "unknown listen protocol type '%s'", sProto.cstr() ? sProto.cstr() : "(NULL)" );
2547 
2548 	// funny magic
2549 	// MSVC -O2 whines about unreachable code
2550 	// everyone else whines about missing return value
2551 #if !(USE_WINDOWS && defined(NDEBUG))
2552 	return PROTO_SPHINX;
2553 #endif
2554 }
2555 
2556 
2557 struct ListenerDesc_t
2558 {
2559 	ProtocolType_e	m_eProto;
2560 	CSphString		m_sUnix;
2561 	DWORD			m_uIP;
2562 	int				m_iPort;
2563 };
2564 
2565 
ParseListener(const char * sSpec)2566 ListenerDesc_t ParseListener ( const char * sSpec )
2567 {
2568 	ListenerDesc_t tRes;
2569 	tRes.m_eProto = PROTO_SPHINX;
2570 	tRes.m_sUnix = "";
2571 	tRes.m_uIP = htonl ( INADDR_ANY );
2572 	tRes.m_iPort = SPHINXAPI_PORT;
2573 
2574 	// split by colon
2575 	int iParts = 0;
2576 	CSphString sParts[3];
2577 
2578 	const char * sPart = sSpec;
2579 	for ( const char * p = sSpec; ; p++ )
2580 		if ( *p=='\0' || *p==':' )
2581 	{
2582 		if ( iParts==3 )
2583 			sphFatal ( "invalid listen format (too many fields)" );
2584 
2585 		sParts[iParts++].SetBinary ( sPart, p-sPart );
2586 		if ( !*p )
2587 			break; // bail out on zero
2588 
2589 		sPart = p+1;
2590 	}
2591 	assert ( iParts>=1 && iParts<=3 );
2592 
2593 	// handle UNIX socket case
2594 	// might be either name on itself (1 part), or name+protocol (2 parts)
2595 	sPart = sParts[0].cstr();
2596 	if ( sPart[0]=='/' )
2597 	{
2598 		if ( iParts>2 )
2599 			sphFatal ( "invalid listen format (too many fields)" );
2600 
2601 		if ( iParts==2 )
2602 			tRes.m_eProto = ProtoByName ( sParts[1] );
2603 
2604 #if USE_WINDOWS
2605 		sphFatal ( "UNIX sockets are not supported on Windows" );
2606 #else
2607 		tRes.m_sUnix = sPart;
2608 		return tRes;
2609 #endif
2610 	}
2611 
2612 	// check if it all starts with a valid port number
2613 	sPart = sParts[0].cstr();
2614 	int iLen = strlen(sPart);
2615 
2616 	bool bAllDigits = true;
2617 	for ( int i=0; i<iLen && bAllDigits; i++ )
2618 		if ( !isdigit ( sPart[i] ) )
2619 			bAllDigits = false;
2620 
2621 	int iPort = 0;
2622 	if ( bAllDigits && iLen<=5 )
2623 	{
2624 		iPort = atol(sPart);
2625 		CheckPort ( iPort ); // lets forbid ambiguous magic like 0:sphinx or 99999:mysql41
2626 	}
2627 
2628 	// handle TCP port case
2629 	// one part. might be either port name, or host name, or UNIX socket name
2630 	if ( iParts==1 )
2631 	{
2632 		if ( iPort )
2633 		{
2634 			// port name on itself
2635 			tRes.m_uIP = htonl ( INADDR_ANY );
2636 			tRes.m_iPort = iPort;
2637 		} else
2638 		{
2639 			// host name on itself
2640 			tRes.m_uIP = sphGetAddress ( sSpec, GETADDR_STRICT );
2641 			tRes.m_iPort = SPHINXAPI_PORT;
2642 		}
2643 		return tRes;
2644 	}
2645 
2646 	// two or three parts
2647 	if ( iPort )
2648 	{
2649 		// 1st part is a valid port number; must be port:proto
2650 		if ( iParts!=2 )
2651 			sphFatal ( "invalid listen format (expected port:proto, got extra trailing part in listen=%s)", sSpec );
2652 
2653 		tRes.m_uIP = htonl ( INADDR_ANY );
2654 		tRes.m_iPort = iPort;
2655 		tRes.m_eProto = ProtoByName ( sParts[1] );
2656 
2657 	} else
2658 	{
2659 		// 1st part must be a host name; must be host:port[:proto]
2660 		if ( iParts==3 )
2661 			tRes.m_eProto = ProtoByName ( sParts[2] );
2662 
2663 		tRes.m_iPort = atol ( sParts[1].cstr() );
2664 		CheckPort ( tRes.m_iPort );
2665 
2666 		tRes.m_uIP = sParts[0].IsEmpty()
2667 			? htonl ( INADDR_ANY )
2668 			: sphGetAddress ( sParts[0].cstr(), GETADDR_STRICT );
2669 	}
2670 	return tRes;
2671 }
2672 
2673 
AddListener(const CSphString & sListen)2674 void AddListener ( const CSphString & sListen )
2675 {
2676 	ListenerDesc_t tDesc = ParseListener ( sListen.cstr() );
2677 
2678 	Listener_t tListener;
2679 	tListener.m_eProto = tDesc.m_eProto;
2680 	tListener.m_bTcp = true;
2681 
2682 #if !USE_WINDOWS
2683 	if ( !tDesc.m_sUnix.IsEmpty() )
2684 	{
2685 		tListener.m_iSock = sphCreateUnixSocket ( tDesc.m_sUnix.cstr() );
2686 		tListener.m_bTcp = false;
2687 	} else
2688 #endif
2689 		tListener.m_iSock = sphCreateInetSocket ( tDesc.m_uIP, tDesc.m_iPort );
2690 
2691 	g_dListeners.Add ( tListener );
2692 }
2693 
2694 
sphSetSockNB(int iSock)2695 int sphSetSockNB ( int iSock )
2696 {
2697 	#if USE_WINDOWS
2698 		u_long uMode = 1;
2699 		return ioctlsocket ( iSock, FIONBIO, &uMode );
2700 	#else
2701 		return fcntl ( iSock, F_SETFL, O_NONBLOCK );
2702 	#endif
2703 }
2704 
2705 class CloseOnDestroy : public ISphNoncopyable
2706 {
2707 	int m_id;
2708 public:
CloseOnDestroy(int id)2709 	explicit CloseOnDestroy ( int id ) : m_id ( id ) {}
~CloseOnDestroy()2710 	~CloseOnDestroy() { close ( m_id ); }
2711 };
2712 
2713 /// wait until socket is readable or writable
sphPoll(int iSock,int64_t tmTimeout,bool bWrite=false)2714 int sphPoll ( int iSock, int64_t tmTimeout, bool bWrite=false )
2715 {
2716 #if HAVE_EPOLL
2717 	int eid = epoll_create ( 1 );
2718 	CloseOnDestroy dEid ( eid );
2719 	epoll_event dEvent;
2720 	dEvent.events = bWrite ? EPOLLOUT : EPOLLIN;
2721 	epoll_ctl ( eid, EPOLL_CTL_ADD, iSock, &dEvent );
2722 	// do poll
2723 	return ::epoll_wait ( eid, &dEvent, 1, int ( tmTimeout/1000 ) );
2724 #elif HAVE_POLL
2725 	struct pollfd pfd;
2726 	pfd.fd = iSock;
2727 	pfd.events = bWrite ? POLLOUT : POLLIN;
2728 
2729 	return ::poll ( &pfd, 1, int ( tmTimeout/1000 ) );
2730 #else
2731 	fd_set fdSet;
2732 	FD_ZERO ( &fdSet );
2733 	sphFDSet ( iSock, &fdSet );
2734 
2735 	struct timeval tv;
2736 	tv.tv_sec = (int)( tmTimeout / 1000000 );
2737 	tv.tv_usec = (int)( tmTimeout % 1000000 );
2738 
2739 	return ::select ( iSock+1, bWrite ? NULL : &fdSet, bWrite ? &fdSet : NULL, NULL, &tv );
2740 #endif
2741 }
2742 
2743 /// check if a socket is still connected
sphSockEof(int iSock)2744 bool sphSockEof ( int iSock )
2745 {
2746 	if ( iSock<0 )
2747 		return true;
2748 
2749 	char cBuf;
2750 #if HAVE_EPOLL
2751 	int eid = epoll_create ( 1 );
2752 	CloseOnDestroy dEid ( eid );
2753 	epoll_event dEvent;
2754 	dEvent.events = EPOLLPRI | EPOLLIN;
2755 	epoll_ctl ( eid, EPOLL_CTL_ADD, iSock, &dEvent );
2756 	if ( ::epoll_wait ( eid, &dEvent, 1, 0 )<0 )
2757 		return true;
2758 
2759 	if ( dEvent.events & (EPOLLPRI|EPOLLIN) )
2760 #elif HAVE_POLL
2761 	struct pollfd pfd;
2762 	pfd.fd = iSock;
2763 	pfd.events = POLLPRI | POLLIN;
2764 	if ( ::poll ( &pfd, 1, 0 )<0 )
2765 		return true;
2766 
2767 	if ( pfd.revents & (POLLIN|POLLPRI) )
2768 #else
2769 	fd_set fdrSet, fdeSet;
2770 	FD_ZERO ( &fdrSet );
2771 	FD_ZERO ( &fdeSet );
2772 	sphFDSet ( iSock, &fdrSet );
2773 	sphFDSet ( iSock, &fdeSet );
2774 	struct timeval tv = {0};
2775 	if ( ::select ( iSock+1, &fdrSet, NULL, &fdeSet, &tv )<0 )
2776 		return true;
2777 
2778 	if ( FD_ISSET ( iSock, &fdrSet ) || FD_ISSET ( iSock, &fdeSet ) )
2779 #endif
2780 		if ( ::recv ( iSock, &cBuf, sizeof(cBuf), MSG_PEEK )<=0 )
2781 			if ( sphSockGetErrno()!=EWOULDBLOCK )
2782 				return true;
2783 	return false;
2784 }
2785 
2786 
sphSockRead(int iSock,void * buf,int iLen,int iReadTimeout,bool bIntr)2787 int sphSockRead ( int iSock, void * buf, int iLen, int iReadTimeout, bool bIntr )
2788 {
2789 	assert ( iLen>0 );
2790 
2791 	int64_t tmMaxTimer = sphMicroTimer() + I64C(1000000)*Max ( 1, iReadTimeout ); // in microseconds
2792 	int iLeftBytes = iLen; // bytes to read left
2793 
2794 	char * pBuf = (char*) buf;
2795 	int iRes = -1, iErr = 0;
2796 
2797 	while ( iLeftBytes>0 )
2798 	{
2799 		int64_t tmMicroLeft = tmMaxTimer - sphMicroTimer();
2800 		if ( tmMicroLeft<=0 )
2801 			break; // timed out
2802 
2803 #if USE_WINDOWS
2804 		// Windows EINTR emulation
2805 		// Ctrl-C will not interrupt select on Windows, so let's handle that manually
2806 		// forcibly limit select() to 100 ms, and check flag afterwards
2807 		if ( bIntr )
2808 			tmMicroLeft = Min ( tmMicroLeft, 100000 );
2809 #endif
2810 
2811 		// wait until there is data
2812 		iRes = sphPoll ( iSock, tmMicroLeft );
2813 
2814 		// if there was EINTR, retry
2815 		// if any other error, bail
2816 		if ( iRes==-1 )
2817 		{
2818 			// only let SIGTERM (of all them) to interrupt, and only if explicitly allowed
2819 			iErr = sphSockGetErrno();
2820 			if ( iErr==EINTR && !( g_bGotSigterm && bIntr ))
2821 				continue;
2822 
2823 			if ( iErr==EINTR )
2824 				sphLogDebug ( "sphSockRead: select got SIGTERM, exit -1" );
2825 
2826 			sphSockSetErrno ( iErr );
2827 			return -1;
2828 		}
2829 
2830 		// if there was a timeout, report it as an error
2831 		if ( iRes==0 )
2832 		{
2833 #if USE_WINDOWS
2834 			// Windows EINTR emulation
2835 			if ( bIntr )
2836 			{
2837 				// got that SIGTERM
2838 				if ( g_bGotSigterm )
2839 				{
2840 					sphLogDebug ( "sphSockRead: got SIGTERM emulation on Windows, exit -1" );
2841 					sphSockSetErrno ( EINTR );
2842 					return -1;
2843 				}
2844 
2845 				// timeout might not be fully over just yet, so re-loop
2846 				continue;
2847 			}
2848 #endif
2849 
2850 			sphSockSetErrno ( ETIMEDOUT );
2851 			return -1;
2852 		}
2853 
2854 		// try to receive next chunk
2855 		iRes = sphSockRecv ( iSock, pBuf, iLeftBytes );
2856 
2857 		// if there was eof, we're done
2858 		if ( iRes==0 )
2859 		{
2860 			sphSockSetErrno ( ECONNRESET );
2861 			return -1;
2862 		}
2863 
2864 		// if there was EINTR, retry
2865 		// if any other error, bail
2866 		if ( iRes==-1 )
2867 		{
2868 			// only let SIGTERM (of all them) to interrupt, and only if explicitly allowed
2869 			iErr = sphSockGetErrno();
2870 			if ( iErr==EINTR && !( g_bGotSigterm && bIntr ))
2871 				continue;
2872 
2873 			if ( iErr==EINTR )
2874 				sphLogDebug ( "sphSockRead: select got SIGTERM, exit -1" );
2875 
2876 			sphSockSetErrno ( iErr );
2877 			return -1;
2878 		}
2879 
2880 		// update
2881 		pBuf += iRes;
2882 		iLeftBytes -= iRes;
2883 
2884 		// avoid partial buffer loss in case of signal during the 2nd (!) read
2885 		bIntr = false;
2886 	}
2887 
2888 	// if there was a timeout, report it as an error
2889 	if ( iLeftBytes!=0 )
2890 	{
2891 		sphSockSetErrno ( ETIMEDOUT );
2892 		return -1;
2893 	}
2894 
2895 	return iLen;
2896 }
2897 
2898 /////////////////////////////////////////////////////////////////////////////
2899 // NETWORK BUFFERS
2900 /////////////////////////////////////////////////////////////////////////////
2901 
2902 /// dynamic response buffer
2903 /// to remove ISphNoncopyable just add copy c-tor and operator=
2904 /// splitting application network packets in several sphSocketSend()s causes
2905 /// Nagle's algorithm to wait delayead ACKs (google for it if you want)
2906 /// That's why we use relocation here instead of static-sized buffer.
2907 /// To be twice sure we're switching off Nagle's algorithm in all sockets.
2908 class NetOutputBuffer_c : public ISphNoncopyable
2909 {
2910 public:
2911 	CSphQueryProfile *	m_pProfile;
2912 
2913 public:
2914 	explicit	NetOutputBuffer_c ( int iSock );
~NetOutputBuffer_c()2915 	~NetOutputBuffer_c() { SafeDeleteArray ( m_pBuffer ); }
2916 
SendInt(int iValue)2917 	bool		SendInt ( int iValue )			{ return SendT<int> ( htonl ( iValue ) ); }
SendAsDword(int64_t iValue)2918 	bool		SendAsDword ( int64_t iValue ) ///< sends the 32bit MAX_UINT if the value is greater than it.
2919 		{
2920 			if ( iValue < 0 )
2921 				return SendDword ( 0 );
2922 			if ( iValue > UINT_MAX )
2923 				return SendDword ( UINT_MAX );
2924 			return SendDword ( DWORD(iValue) );
2925 		}
SendDword(DWORD iValue)2926 	bool		SendDword ( DWORD iValue )		{ return SendT<DWORD> ( htonl ( iValue ) ); }
SendWord(WORD iValue)2927 	bool		SendWord ( WORD iValue )		{ return SendT<WORD> ( htons ( iValue ) ); }
SendFloat(float fValue)2928 	bool		SendFloat ( float fValue )		{ return SendT<DWORD> ( htonl ( sphF2DW ( fValue ) ) ); }
SendByte(BYTE uValue)2929 	bool		SendByte ( BYTE uValue )		{ return SendT<BYTE> ( uValue ); }
2930 
SendLSBDword(DWORD v)2931 	bool SendLSBDword ( DWORD v )
2932 	{
2933 		SendByte ( (BYTE)( v & 0xff ) );
2934 		SendByte ( (BYTE)( (v>>8) & 0xff ) );
2935 		SendByte ( (BYTE)( (v>>16) & 0xff ) );
2936 		return SendByte ( (BYTE)( (v>>24) & 0xff) );
2937 	}
2938 
SendUint64(uint64_t iValue)2939 	bool SendUint64 ( uint64_t iValue )
2940 	{
2941 		SendT<DWORD> ( htonl ( (DWORD)(iValue>>32) ) );
2942 		return SendT<DWORD> ( htonl ( (DWORD)(iValue & 0xffffffffUL) ) );
2943 	}
2944 
2945 #if USE_64BIT
SendDocid(SphDocID_t iValue)2946 	bool		SendDocid ( SphDocID_t iValue )	{ return SendUint64 ( iValue ); }
2947 #else
SendDocid(SphDocID_t iValue)2948 	bool		SendDocid ( SphDocID_t iValue )	{ return SendDword ( iValue ); }
2949 #endif
2950 
2951 	bool		SendString ( const char * sStr );
2952 
2953 	bool		SendMysqlInt ( int iVal );
2954 	bool		SendMysqlString ( const char * sStr );
2955 
2956 	bool		Flush ( bool bUnfreeze=false );
GetError()2957 	bool		GetError () { return m_bError; }
GetSentCount()2958 	int			GetSentCount () { return m_iSent; }
2959 	void		FreezeBlock ( const char * sError, int iLen );
2960 
2961 protected:
2962 	BYTE *		m_pBuffer;			///< my dynamic buffer
2963 	int			m_iBufferSize;		///< my dynamic buffer size
2964 	BYTE *		m_pBufferPtr;			///< my current buffer position
2965 	int			m_iSock;			///< my socket
2966 	bool		m_bError;			///< if there were any write errors
2967 	int			m_iSent;
2968 	const char *m_sError;			///< fallback message if the frozen buf overloaded
2969 	int			m_iErrorLength;
2970 	bool		m_bFlushEnabled;	///< in frozen state we never flush until special command
2971 	BYTE *		m_pSize;			///< the pointer to the size of frozen block
2972 
2973 protected:
2974 	bool		SetError ( bool bValue );	///< set error flag
2975 	bool		ResizeIf ( int iToAdd );	///< flush if there's not enough free space to add iToAdd bytes
2976 
2977 public:
2978 	bool							SendBytes ( const void * pBuf, int iLen );	///< (was) protected to avoid network-vs-host order bugs
2979 	template < typename T > bool	SendT ( T tValue );							///< (was) protected to avoid network-vs-host order bugs
2980 };
2981 
2982 
2983 /// generic request buffer
2984 class InputBuffer_c
2985 {
2986 public:
2987 					InputBuffer_c ( const BYTE * pBuf, int iLen );
~InputBuffer_c()2988 	virtual			~InputBuffer_c () {}
2989 
GetInt()2990 	int				GetInt () { return ntohl ( GetT<int> () ); }
GetWord()2991 	WORD			GetWord () { return ntohs ( GetT<WORD> () ); }
GetDword()2992 	DWORD			GetDword () { return ntohl ( GetT<DWORD> () ); }
GetLSBDword()2993 	DWORD			GetLSBDword () { return GetByte() + ( GetByte()<<8 ) + ( GetByte()<<16 ) + ( GetByte()<<24 ); }
GetUint64()2994 	uint64_t		GetUint64() { uint64_t uRes = GetDword(); return (uRes<<32)+GetDword(); }
GetByte()2995 	BYTE			GetByte () { return GetT<BYTE> (); }
GetFloat()2996 	float			GetFloat () { return sphDW2F ( ntohl ( GetT<DWORD> () ) ); }
2997 	CSphString		GetString ();
2998 	CSphString		GetRawString ( int iLen );
2999 	bool			GetString ( CSphVector<BYTE> & dBuffer );
3000 	int				GetDwords ( DWORD ** pBuffer, int iMax, const char * sErrorTemplate );
GetError()3001 	bool			GetError () { return m_bError; }
3002 	bool			GetBytes ( void * pBuf, int iLen );
3003 
3004 	template < typename T > bool	GetDwords ( CSphVector<T> & dBuffer, int iMax, const char * sErrorTemplate );
3005 	template < typename T > bool	GetQwords ( CSphVector<T> & dBuffer, int iMax, const char * sErrorTemplate );
3006 
3007 	virtual void	SendErrorReply ( const char *, ... ) __attribute__ ( ( format ( printf, 2, 3 ) ) ) = 0;
3008 
3009 protected:
3010 	const BYTE *	m_pBuf;
3011 	const BYTE *	m_pCur;
3012 	bool			m_bError;
3013 	int				m_iLen;
3014 
3015 protected:
SetError(bool bError)3016 	void						SetError ( bool bError ) { m_bError = bError; }
3017 	template < typename T > T	GetT ();
3018 };
3019 
3020 
3021 /// simple memory request buffer
3022 class MemInputBuffer_c : public InputBuffer_c
3023 {
3024 public:
MemInputBuffer_c(const BYTE * pBuf,int iLen)3025 					MemInputBuffer_c ( const BYTE * pBuf, int iLen ) : InputBuffer_c ( pBuf, iLen ) {}
SendErrorReply(const char *,...)3026 	virtual void	SendErrorReply ( const char *, ... ) __attribute__ ( ( format ( printf, 2, 3 ) ) ) {}
3027 };
3028 
3029 
3030 /// simple network request buffer
3031 class NetInputBuffer_c : public InputBuffer_c
3032 {
3033 public:
3034 	explicit		NetInputBuffer_c ( int iSock );
3035 	virtual			~NetInputBuffer_c ();
3036 
3037 	bool			ReadFrom ( int iLen, int iTimeout, bool bIntr=false, bool bAppend=false );
ReadFrom(int iLen)3038 	bool			ReadFrom ( int iLen ) { return ReadFrom ( iLen, g_iReadTimeout ); }
3039 
3040 	virtual void	SendErrorReply ( const char *, ... ) __attribute__ ( ( format ( printf, 2, 3 ) ) );
3041 
GetBufferPtr() const3042 	const BYTE *	GetBufferPtr () const { return m_pBuf; }
IsIntr() const3043 	bool			IsIntr () const { return m_bIntr; }
3044 
3045 protected:
3046 	static const int	NET_MINIBUFFER_SIZE = 4096;
3047 
3048 	int					m_iSock;
3049 	bool				m_bIntr;
3050 
3051 	BYTE				m_dMinibufer[NET_MINIBUFFER_SIZE];
3052 	int					m_iMaxibuffer;
3053 	BYTE *				m_pMaxibuffer;
3054 };
3055 
3056 /////////////////////////////////////////////////////////////////////////////
3057 
NetOutputBuffer_c(int iSock)3058 NetOutputBuffer_c::NetOutputBuffer_c ( int iSock )
3059 	: m_pProfile ( NULL )
3060 	, m_iBufferSize ( NETOUTBUF )
3061 	, m_iSock ( iSock )
3062 	, m_bError ( false )
3063 	, m_iSent ( 0 )
3064 	, m_bFlushEnabled ( true )
3065 {
3066 	assert ( m_iSock>0 );
3067 	m_pBuffer = new BYTE [ m_iBufferSize ];
3068 	m_pBufferPtr = m_pBuffer;
3069 }
3070 
3071 
SendT(T tValue)3072 template < typename T > bool NetOutputBuffer_c::SendT ( T tValue )
3073 {
3074 	if ( m_bError )
3075 		return false;
3076 
3077 	ResizeIf ( sizeof(T) );
3078 
3079 	sphUnalignedWrite ( m_pBufferPtr, tValue );
3080 	m_pBufferPtr += sizeof(T);
3081 	assert ( m_pBufferPtr<m_pBuffer+m_iBufferSize );
3082 	return true;
3083 }
3084 
3085 
SendString(const char * sStr)3086 bool NetOutputBuffer_c::SendString ( const char * sStr )
3087 {
3088 	if ( m_bError )
3089 		return false;
3090 
3091 	ResizeIf ( sizeof(DWORD) );
3092 
3093 	int iLen = sStr ? strlen(sStr) : 0;
3094 	SendInt ( iLen );
3095 	return SendBytes ( sStr, iLen );
3096 }
3097 
3098 
MysqlPackedLen(int iLen)3099 int MysqlPackedLen ( int iLen )
3100 {
3101 	if ( iLen<251 )
3102 		return 1;
3103 	if ( iLen<=0xffff )
3104 		return 3;
3105 	if ( iLen<=0xffffff )
3106 		return 4;
3107 	return 9;
3108 }
3109 
3110 
MysqlPackedLen(const char * sStr)3111 int MysqlPackedLen ( const char * sStr )
3112 {
3113 	int iLen = strlen(sStr);
3114 	return MysqlPackedLen ( iLen ) + iLen;
3115 }
3116 
3117 
3118 
3119 // encodes Mysql Length-coded binary
MysqlPack(void * pBuffer,int iValue)3120 void * MysqlPack ( void * pBuffer, int iValue )
3121 {
3122 	char * pOutput = (char*)pBuffer;
3123 	if ( iValue<0 )
3124 		return (void*)pOutput;
3125 
3126 	if ( iValue<251 )
3127 	{
3128 		*pOutput++ = (char)iValue;
3129 		return (void*)pOutput;
3130 	}
3131 
3132 	if ( iValue<=0xFFFF )
3133 	{
3134 		*pOutput++ = '\xFC';
3135 		*pOutput++ = (char)iValue;
3136 		*pOutput++ = (char)( iValue>>8 );
3137 		return (void*)pOutput;
3138 	}
3139 
3140 	if ( iValue<=0xFFFFFF )
3141 	{
3142 		*pOutput++ = '\xFD';
3143 		*pOutput++ = (char)iValue;
3144 		*pOutput++ = (char)( iValue>>8 );
3145 		*pOutput++ = (char)( iValue>>16 );
3146 		return (void *) pOutput;
3147 	}
3148 
3149 	*pOutput++ = '\xFE';
3150 	*pOutput++ = (char)iValue;
3151 	*pOutput++ = (char)( iValue>>8 );
3152 	*pOutput++ = (char)( iValue>>16 );
3153 	*pOutput++ = (char)( iValue>>24 );
3154 	*pOutput++ = 0;
3155 	*pOutput++ = 0;
3156 	*pOutput++ = 0;
3157 	*pOutput++ = 0;
3158 	return (void*)pOutput;
3159 }
3160 
MysqlUnpack(InputBuffer_c & tReq,DWORD * pSize)3161 int MysqlUnpack ( InputBuffer_c & tReq, DWORD * pSize )
3162 {
3163 	assert ( pSize );
3164 
3165 	int iRes = tReq.GetByte();
3166 	--*pSize;
3167 	if ( iRes < 251 )
3168 		return iRes;
3169 
3170 	if ( iRes==0xFC )
3171 	{
3172 		*pSize -=2;
3173 		return tReq.GetByte() + ((int)tReq.GetByte()<<8);
3174 	}
3175 
3176 	if ( iRes==0xFD )
3177 	{
3178 		*pSize -= 3;
3179 		return tReq.GetByte() + ((int)tReq.GetByte()<<8) + ((int)tReq.GetByte()<<16);
3180 	}
3181 
3182 	if ( iRes==0xFE )
3183 		iRes = tReq.GetByte() + ((int)tReq.GetByte()<<8) + ((int)tReq.GetByte()<<16) + ((int)tReq.GetByte()<<24);
3184 
3185 	tReq.GetByte();
3186 	tReq.GetByte();
3187 	tReq.GetByte();
3188 	tReq.GetByte();
3189 	*pSize -= 8;
3190 	return iRes;
3191 }
3192 
3193 
SendMysqlInt(int iVal)3194 bool NetOutputBuffer_c::SendMysqlInt ( int iVal )
3195 {
3196 	if ( m_bError )
3197 		return false;
3198 	BYTE dBuf[12];
3199 	BYTE * pBuf = (BYTE*) MysqlPack ( dBuf, iVal );
3200 	return SendBytes ( dBuf, (int)( pBuf-dBuf ) );
3201 }
3202 
3203 
SendMysqlString(const char * sStr)3204 bool NetOutputBuffer_c::SendMysqlString ( const char * sStr )
3205 {
3206 	if ( m_bError )
3207 		return false;
3208 
3209 	int iLen = strlen(sStr);
3210 
3211 	BYTE dBuf[12];
3212 	BYTE * pBuf = (BYTE*) MysqlPack ( dBuf, iLen );
3213 	SendBytes ( dBuf, (int)( pBuf-dBuf ) );
3214 	return SendBytes ( sStr, iLen );
3215 }
3216 
3217 
SendBytes(const void * pBuf,int iLen)3218 bool NetOutputBuffer_c::SendBytes ( const void * pBuf, int iLen )
3219 {
3220 	BYTE * pMy = (BYTE*)pBuf;
3221 	while ( iLen>0 && !m_bError )
3222 	{
3223 		int iLeft = m_iBufferSize - ( m_pBufferPtr-m_pBuffer );
3224 		if ( iLen<=iLeft )
3225 		{
3226 			memcpy ( m_pBufferPtr, pMy, iLen );
3227 			m_pBufferPtr += iLen;
3228 			break;
3229 		}
3230 
3231 		ResizeIf ( iLen );
3232 	}
3233 	return !m_bError;
3234 }
3235 
3236 
Flush(bool bUnfreeze)3237 bool NetOutputBuffer_c::Flush ( bool bUnfreeze )
3238 {
3239 	if ( m_bError )
3240 		return false;
3241 
3242 	int iLen = m_pBufferPtr-m_pBuffer;
3243 	if ( iLen==0 )
3244 		return true;
3245 
3246 	if ( g_bGotSigterm )
3247 		sphLogDebug ( "SIGTERM in NetOutputBuffer::Flush" );
3248 
3249 	if ( bUnfreeze )
3250 	{
3251 		BYTE * pBuf = m_pBufferPtr;
3252 		m_pBufferPtr = m_pSize;
3253 		SendDword ( pBuf-m_pSize-4 );
3254 		m_pBufferPtr = pBuf;
3255 		m_bFlushEnabled = true;
3256 	}
3257 
3258 	// buffer overloaded. It is fail. Send the error message.
3259 	if ( !m_bFlushEnabled )
3260 	{
3261 		sphLogDebug ( "NetOutputBuffer with disabled flush is overloaded" );
3262 		m_pBufferPtr = m_pBuffer;
3263 		SendBytes ( m_sError, m_iErrorLength );
3264 		iLen = m_pBufferPtr-m_pBuffer;
3265 		if ( iLen==0 )
3266 			return true;
3267 	}
3268 
3269 	assert ( iLen>0 );
3270 	assert ( iLen<=(int)m_iBufferSize );
3271 	char * pBuffer = reinterpret_cast<char *> ( m_pBuffer );
3272 
3273 	ESphQueryState eOld = SPH_QSTATE_TOTAL;
3274 	if ( m_pProfile )
3275 		eOld = m_pProfile->Switch ( SPH_QSTATE_NET_WRITE );
3276 
3277 	const int64_t tmMaxTimer = sphMicroTimer() + g_iWriteTimeout*1000000; // in microseconds
3278 	while ( !m_bError )
3279 	{
3280 		int iRes = sphSockSend ( m_iSock, pBuffer, iLen );
3281 		if ( iRes < 0 )
3282 		{
3283 			int iErrno = sphSockGetErrno();
3284 			if ( iErrno==EINTR ) // interrupted before any data was sent; just loop
3285 				continue;
3286 			if ( iErrno!=EAGAIN && iErrno!=EWOULDBLOCK )
3287 			{
3288 				sphWarning ( "send() failed: %d: %s", iErrno, sphSockError(iErrno) );
3289 				m_bError = true;
3290 				break;
3291 			}
3292 		} else
3293 		{
3294 			m_iSent += iRes;
3295 			pBuffer += iRes;
3296 			iLen -= iRes;
3297 			if ( iLen==0 )
3298 				break;
3299 		}
3300 
3301 		// wait until we can write
3302 		int64_t tmMicroLeft = tmMaxTimer - sphMicroTimer();
3303 		if ( tmMicroLeft>0 )
3304 			iRes = sphPoll ( m_iSock, tmMicroLeft, true );
3305 		else
3306 			iRes = 0; // time out
3307 
3308 		switch ( iRes )
3309 		{
3310 			case 1: // ready for writing
3311 				break;
3312 
3313 			case 0: // timed out
3314 			{
3315 				sphWarning ( "timed out while trying to flush network buffers" );
3316 				m_bError = true;
3317 				break;
3318 			}
3319 
3320 			case -1: // error
3321 			{
3322 				int iErrno = sphSockGetErrno();
3323 				if ( iErrno==EINTR )
3324 					break;
3325 				sphWarning ( "select() failed: %d: %s", iErrno, sphSockError(iErrno) );
3326 				m_bError = true;
3327 				break;
3328 			}
3329 		}
3330 	}
3331 
3332 	if ( m_pProfile )
3333 		m_pProfile->Switch ( eOld );
3334 
3335 	m_pBufferPtr = m_pBuffer;
3336 	return !m_bError;
3337 }
3338 
FreezeBlock(const char * sError,int iLen)3339 void NetOutputBuffer_c::FreezeBlock ( const char * sError, int iLen )
3340 {
3341 	m_sError = sError;
3342 	m_iErrorLength = iLen;
3343 	m_bFlushEnabled = false;
3344 	// reserve the DWORD for the size
3345 	m_pSize = m_pBufferPtr;
3346 	SendDword ( 0 );
3347 }
3348 
3349 
ResizeIf(int iToAdd)3350 bool NetOutputBuffer_c::ResizeIf ( int iToAdd )
3351 {
3352 	if ( ( m_pBufferPtr+iToAdd )>=( m_pBuffer+m_iBufferSize ) )
3353 	{
3354 		int iOldBufferSize = m_iBufferSize;
3355 		m_iBufferSize *= 2;
3356 		int iOffset1 = ( m_pBufferPtr-m_pBuffer );
3357 		int iOffset2 = ( m_pSize-m_pBuffer );
3358 		BYTE * pNew = new BYTE [ m_iBufferSize ];
3359 		memcpy ( pNew, m_pBuffer, iOldBufferSize );
3360 		SafeDeleteArray ( m_pBuffer );
3361 		m_pBuffer = pNew;
3362 		m_pBufferPtr = ( m_pBuffer+iOffset1 );
3363 		m_pSize = ( m_pBuffer+iOffset2 );
3364 	}
3365 
3366 	return !m_bError;
3367 }
3368 
3369 /////////////////////////////////////////////////////////////////////////////
3370 
InputBuffer_c(const BYTE * pBuf,int iLen)3371 InputBuffer_c::InputBuffer_c ( const BYTE * pBuf, int iLen )
3372 	: m_pBuf ( pBuf )
3373 	, m_pCur ( pBuf )
3374 	, m_bError ( !pBuf || iLen<0 )
3375 	, m_iLen ( iLen )
3376 {}
3377 
3378 
GetT()3379 template < typename T > T InputBuffer_c::GetT ()
3380 {
3381 	if ( m_bError || ( m_pCur+sizeof(T) > m_pBuf+m_iLen ) )
3382 	{
3383 		SetError ( true );
3384 		return 0;
3385 	}
3386 
3387 	T iRes = sphUnalignedRead ( *(T*)m_pCur );
3388 	m_pCur += sizeof(T);
3389 	return iRes;
3390 }
3391 
3392 
GetString()3393 CSphString InputBuffer_c::GetString ()
3394 {
3395 	CSphString sRes;
3396 
3397 	int iLen = GetInt ();
3398 	if ( m_bError || iLen<0 || iLen>g_iMaxPacketSize || ( m_pCur+iLen > m_pBuf+m_iLen ) )
3399 	{
3400 		SetError ( true );
3401 		return sRes;
3402 	}
3403 
3404 	if ( iLen )
3405 		sRes.SetBinary ( (char*)m_pCur, iLen );
3406 
3407 	m_pCur += iLen;
3408 	return sRes;
3409 }
3410 
3411 
GetRawString(int iLen)3412 CSphString InputBuffer_c::GetRawString ( int iLen )
3413 {
3414 	CSphString sRes;
3415 
3416 	if ( m_bError || iLen<0 || iLen>g_iMaxPacketSize || ( m_pCur+iLen > m_pBuf+m_iLen ) )
3417 	{
3418 		SetError ( true );
3419 		return sRes;
3420 	}
3421 
3422 	if ( iLen )
3423 		sRes.SetBinary ( (char*)m_pCur, iLen );
3424 
3425 	m_pCur += iLen;
3426 	return sRes;
3427 }
3428 
3429 
GetString(CSphVector<BYTE> & dBuffer)3430 bool InputBuffer_c::GetString ( CSphVector<BYTE> & dBuffer )
3431 {
3432 	int iLen = GetInt ();
3433 	if ( m_bError || iLen<0 || iLen>g_iMaxPacketSize || ( m_pCur+iLen > m_pBuf+m_iLen ) )
3434 	{
3435 		SetError ( true );
3436 		return false;
3437 	}
3438 
3439 	if ( !iLen )
3440 		return true;
3441 
3442 	int iSize = dBuffer.GetLength();
3443 	dBuffer.Resize ( iSize + iLen + 1 );
3444 	dBuffer[iSize+iLen] = '\0';
3445 	return GetBytes ( dBuffer.Begin()+iSize, iLen );
3446 }
3447 
3448 
GetBytes(void * pBuf,int iLen)3449 bool InputBuffer_c::GetBytes ( void * pBuf, int iLen )
3450 {
3451 	assert ( pBuf );
3452 	assert ( iLen>0 && iLen<=g_iMaxPacketSize );
3453 
3454 	if ( m_bError || ( m_pCur+iLen > m_pBuf+m_iLen ) )
3455 	{
3456 		SetError ( true );
3457 		return false;
3458 	}
3459 
3460 	memcpy ( pBuf, m_pCur, iLen );
3461 	m_pCur += iLen;
3462 	return true;
3463 }
3464 
3465 
GetDwords(DWORD ** ppBuffer,int iMax,const char * sErrorTemplate)3466 int InputBuffer_c::GetDwords ( DWORD ** ppBuffer, int iMax, const char * sErrorTemplate )
3467 {
3468 	assert ( ppBuffer );
3469 	assert ( !(*ppBuffer) );
3470 
3471 	int iCount = GetInt ();
3472 	if ( iCount<0 || iCount>iMax )
3473 	{
3474 		SendErrorReply ( sErrorTemplate, iCount, iMax );
3475 		SetError ( true );
3476 		return -1;
3477 	}
3478 	if ( iCount )
3479 	{
3480 		assert ( !(*ppBuffer) ); // potential leak
3481 		(*ppBuffer) = new DWORD [ iCount ];
3482 		if ( !GetBytes ( (*ppBuffer), sizeof(DWORD)*iCount ) )
3483 		{
3484 			SafeDeleteArray ( (*ppBuffer) );
3485 			return -1;
3486 		}
3487 		for ( int i=0; i<iCount; i++ )
3488 			(*ppBuffer)[i] = htonl ( (*ppBuffer)[i] );
3489 	}
3490 	return iCount;
3491 }
3492 
3493 
GetDwords(CSphVector<T> & dBuffer,int iMax,const char * sErrorTemplate)3494 template < typename T > bool InputBuffer_c::GetDwords ( CSphVector<T> & dBuffer, int iMax, const char * sErrorTemplate )
3495 {
3496 	int iCount = GetInt ();
3497 	if ( iCount<0 || iCount>iMax )
3498 	{
3499 		SendErrorReply ( sErrorTemplate, iCount, iMax );
3500 		SetError ( true );
3501 		return false;
3502 	}
3503 
3504 	dBuffer.Resize ( iCount );
3505 	ARRAY_FOREACH ( i, dBuffer )
3506 		dBuffer[i] = GetDword ();
3507 
3508 	if ( m_bError )
3509 		dBuffer.Reset ();
3510 
3511 	return !m_bError;
3512 }
3513 
3514 
GetQwords(CSphVector<T> & dBuffer,int iMax,const char * sErrorTemplate)3515 template < typename T > bool InputBuffer_c::GetQwords ( CSphVector<T> & dBuffer, int iMax, const char * sErrorTemplate )
3516 {
3517 	int iCount = GetInt ();
3518 	if ( iCount<0 || iCount>iMax )
3519 	{
3520 		SendErrorReply ( sErrorTemplate, iCount, iMax );
3521 		SetError ( true );
3522 		return false;
3523 	}
3524 
3525 	dBuffer.Resize ( iCount );
3526 	ARRAY_FOREACH ( i, dBuffer )
3527 		dBuffer[i] = GetUint64 ();
3528 
3529 	if ( m_bError )
3530 		dBuffer.Reset ();
3531 
3532 	return !m_bError;
3533 }
3534 /////////////////////////////////////////////////////////////////////////////
3535 
NetInputBuffer_c(int iSock)3536 NetInputBuffer_c::NetInputBuffer_c ( int iSock )
3537 	: InputBuffer_c ( m_dMinibufer, sizeof(m_dMinibufer) )
3538 	, m_iSock ( iSock )
3539 	, m_bIntr ( false )
3540 	, m_iMaxibuffer ( 0 )
3541 	, m_pMaxibuffer ( NULL )
3542 {}
3543 
3544 
~NetInputBuffer_c()3545 NetInputBuffer_c::~NetInputBuffer_c ()
3546 {
3547 	SafeDeleteArray ( m_pMaxibuffer );
3548 }
3549 
3550 
ReadFrom(int iLen,int iTimeout,bool bIntr,bool bAppend)3551 bool NetInputBuffer_c::ReadFrom ( int iLen, int iTimeout, bool bIntr, bool bAppend )
3552 {
3553 	assert (!( bAppend && m_pCur!=m_pBuf && m_pBuf!=m_pMaxibuffer )); // only allow appends to untouched maxi-buffers
3554 	int iCur = bAppend ? m_iLen : 0;
3555 
3556 	m_bIntr = false;
3557 	if ( iLen<=0 || iLen>g_iMaxPacketSize || m_iSock<0 )
3558 		return false;
3559 
3560 	BYTE * pBuf = m_dMinibufer + iCur;
3561 	if ( ( iCur+iLen )>NET_MINIBUFFER_SIZE )
3562 	{
3563 		if ( ( iCur+iLen )>m_iMaxibuffer )
3564 		{
3565 			if ( iCur )
3566 			{
3567 				BYTE * pNew = new BYTE [ iCur+iLen ];
3568 				memcpy ( pNew, m_pCur, iCur );
3569 				SafeDeleteArray ( m_pMaxibuffer );
3570 				m_pMaxibuffer = pNew;
3571 				m_iMaxibuffer = iCur+iLen;
3572 			} else
3573 			{
3574 				SafeDeleteArray ( m_pMaxibuffer );
3575 				m_pMaxibuffer = new BYTE [ iLen ];
3576 				m_iMaxibuffer = iLen;
3577 			}
3578 		}
3579 		pBuf = m_pMaxibuffer;
3580 	}
3581 
3582 	m_pCur = m_pBuf = pBuf;
3583 	int iGot = sphSockRead ( m_iSock, pBuf + iCur, iLen, iTimeout, bIntr );
3584 	if ( g_bGotSigterm )
3585 	{
3586 		sphLogDebug ( "NetInputBuffer_c::ReadFrom: got SIGTERM, return false" );
3587 		m_bError = true;
3588 		m_bIntr = true;
3589 		return false;
3590 	}
3591 
3592 	m_bError = ( iGot!=iLen );
3593 	m_bIntr = m_bError && ( sphSockPeekErrno()==EINTR );
3594 	m_iLen = m_bError ? 0 : iCur+iLen;
3595 	return !m_bError;
3596 }
3597 
3598 
SendErrorReply(const char * sTemplate,...)3599 void NetInputBuffer_c::SendErrorReply ( const char * sTemplate, ... )
3600 {
3601 	char dBuf [ 2048 ];
3602 
3603 	const int iHeaderLen = 12;
3604 	const int iMaxStrLen = sizeof(dBuf) - iHeaderLen - 1;
3605 
3606 	// fill header
3607 	WORD * p0 = (WORD*)&dBuf[0];
3608 	p0[0] = htons ( SEARCHD_ERROR ); // error code
3609 	p0[1] = 0; // version doesn't matter
3610 
3611 	// fill error string
3612 	char * sBuf = dBuf + iHeaderLen;
3613 
3614 	va_list ap;
3615 	va_start ( ap, sTemplate );
3616 	vsnprintf ( sBuf, iMaxStrLen, sTemplate, ap );
3617 	va_end ( ap );
3618 
3619 	sBuf[iMaxStrLen] = '\0';
3620 	int iStrLen = strlen(sBuf);
3621 
3622 	// fixup lengths
3623 	DWORD * p4 = (DWORD*)&dBuf[4];
3624 	p4[0] = htonl ( 4+iStrLen );
3625 	p4[1] = htonl ( iStrLen );
3626 
3627 	// send!
3628 	sphSockSend ( m_iSock, dBuf, iHeaderLen+iStrLen );
3629 
3630 	// --console logging
3631 	if ( g_bOptNoDetach && g_eLogFormat!=LOG_FORMAT_SPHINXQL )
3632 		sphInfo ( "query error: %s", sBuf );
3633 }
3634 
3635 // fix MSVC 2005 fuckup
3636 #if USE_WINDOWS
3637 #pragma conform(forScope,on)
3638 #endif
3639 
3640 /////////////////////////////////////////////////////////////////////////////
3641 // DISTRIBUTED QUERIES
3642 /////////////////////////////////////////////////////////////////////////////
3643 
3644 enum HAStrategies_e {
3645 	HA_RANDOM,
3646 	HA_ROUNDROBIN,
3647 	HA_AVOIDDEAD,
3648 	HA_AVOIDERRORS,
3649 	HA_AVOIDDEADTM,			///< the same as HA_AVOIDDEAD, but uses just min timeout instead of weighted random
3650 	HA_AVOIDERRORSTM,		///< the same as HA_AVOIDERRORS, but uses just min timeout instead of weighted random
3651 
3652 	HA_DEFAULT = HA_RANDOM
3653 };
3654 
3655 class InterWorkerStorage : public ISphNoncopyable
3656 {
3657 	CSphProcessSharedMutex *	m_pProcMutex;	///< mutex for IPC workers (fork-based), also IPC storage
3658 	CSphMutex *					m_pThdMutex;	///< mutex for thread workers
3659 	BYTE *						m_pBuffer;		///< inter-workers storage
3660 
3661 public:
3662 
InterWorkerStorage()3663 	explicit InterWorkerStorage ()
3664 		: m_pProcMutex ( NULL )
3665 		, m_pThdMutex ( NULL )
3666 		, m_pBuffer ( NULL )
3667 	{}
3668 
~InterWorkerStorage()3669 	~InterWorkerStorage()
3670 	{
3671 		if ( m_pThdMutex )
3672 		{
3673 			m_pThdMutex->Done();
3674 			SafeDelete ( m_pThdMutex );
3675 		}
3676 
3677 		if ( m_pProcMutex )
3678 		{
3679 			SafeDelete ( m_pProcMutex );
3680 		} else if ( m_pBuffer )
3681 		{
3682 			SafeDeleteArray ( m_pBuffer );
3683 		}
3684 	}
3685 
Init(int iBufSize)3686 	void Init ( int iBufSize )
3687 	{
3688 		assert ( !m_pBuffer );
3689 		assert ( !m_pProcMutex );
3690 		assert ( !m_pThdMutex );
3691 
3692 
3693 		CSphString sError, sWarning;
3694 		// do we need the ipc-shared buffer and ipc-shared mutex to work with?
3695 		if ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK )
3696 		{
3697 			m_pProcMutex = new CSphProcessSharedMutex ( iBufSize );
3698 			m_pBuffer = m_pProcMutex->GetSharedData();
3699 		} else
3700 			m_pBuffer = new BYTE[iBufSize];
3701 
3702 		if ( !m_pBuffer )
3703 		{
3704 			sphWarning ( "Unabled to share the agent statistics" );
3705 			SafeDelete ( m_pProcMutex );
3706 			return;
3707 		}
3708 
3709 		// simple mutex is also necessary, since even in some kind of fork/prefork we also have dist_threads.
3710 		m_pThdMutex = new CSphMutex();
3711 		m_pThdMutex->Init();
3712 	}
3713 
GetSharedData()3714 	inline BYTE* GetSharedData()
3715 	{
3716 		return m_pBuffer;
3717 	}
3718 
Lock()3719 	inline bool Lock()
3720 	{
3721 		if ( m_pThdMutex && !m_pThdMutex->Lock() )
3722 			return false;
3723 
3724 		if ( m_pProcMutex )
3725 			m_pProcMutex->Lock();
3726 
3727 		return true;
3728 	}
3729 
Unlock()3730 	inline bool Unlock()
3731 	{
3732 		if ( m_pProcMutex )
3733 			m_pProcMutex->Unlock();
3734 
3735 		if ( m_pThdMutex && !m_pThdMutex->Unlock() )
3736 			return false;
3737 
3738 		return true;
3739 	}
3740 };
3741 
LogAgentWeights(const WORD * pOldWeights,const WORD * pCurWeights,const int64_t * pTimers,const CSphVector<AgentDesc_t> & dAgents)3742 static void LogAgentWeights ( const WORD * pOldWeights, const WORD * pCurWeights, const int64_t * pTimers, const CSphVector<AgentDesc_t> & dAgents )
3743 {
3744 	if ( g_eLogLevel<SPH_LOG_DEBUG )
3745 		return;
3746 
3747 	ARRAY_FOREACH ( i, dAgents )
3748 		sphLogDebug ( "client=%s:%d, mirror=%d, weight=%d, %d, timer=" INT64_FMT, dAgents[i].m_sHost.cstr(), dAgents[i].m_iPort, i, pCurWeights[i], pOldWeights[i], pTimers[i] );
3749 }
3750 
3751 /// remote agent descriptor (stored in a global hash)
3752 struct MetaAgentDesc_t
3753 {
3754 private:
3755 	CSphVector<AgentDesc_t> m_dAgents;
3756 	WORD *					m_pWeights; /// pointer not owned, pointee IPC-shared
3757 	int *					m_pRRCounter; /// pointer not owned, pointee IPC-shared
3758 	InterWorkerStorage *	m_pLock; /// pointer not owned, lock for threads/IPC
3759 	DWORD					m_uTimestamp;
3760 	HAStrategies_e			m_eStrategy;
3761 
3762 public:
MetaAgentDesc_tMetaAgentDesc_t3763 	MetaAgentDesc_t ()
3764 		: m_pWeights ( NULL )
3765 		, m_pRRCounter ( NULL )
3766 		, m_pLock ( NULL )
3767 		, m_uTimestamp ( HostDashboard_t::GetCurSeconds() )
3768 		, m_eStrategy ( HA_DEFAULT )
3769 	{}
3770 
MetaAgentDesc_tMetaAgentDesc_t3771 	MetaAgentDesc_t ( const MetaAgentDesc_t & rhs )
3772 	{
3773 		*this = rhs;
3774 	}
3775 
SetPersistentMetaAgentDesc_t3776 	inline void SetPersistent ()
3777 	{
3778 		ARRAY_FOREACH ( i, m_dAgents )
3779 			m_dAgents[i].m_bPersistent = true;
3780 	}
3781 
SetBlackholeMetaAgentDesc_t3782 	void SetBlackhole ()
3783 	{
3784 		ARRAY_FOREACH ( i, m_dAgents )
3785 			m_dAgents[i].m_bBlackhole = true;
3786 	}
3787 
SetStrategyMetaAgentDesc_t3788 	void SetStrategy ( HAStrategies_e eStrategy )
3789 	{
3790 		m_eStrategy = eStrategy;
3791 	}
3792 
SetHADataMetaAgentDesc_t3793 	inline void SetHAData ( int * pRRCounter, WORD * pWeights, InterWorkerStorage * pLock )
3794 	{
3795 		m_pRRCounter = pRRCounter;
3796 		m_pWeights = pWeights;
3797 		m_pLock = pLock;
3798 	}
3799 
GetAgentMetaAgentDesc_t3800 	AgentDesc_t * GetAgent ( int iAgent )
3801 	{
3802 		assert ( iAgent>=0 );
3803 		return &m_dAgents[iAgent];
3804 	}
3805 
NewAgentMetaAgentDesc_t3806 	AgentDesc_t * NewAgent()
3807 	{
3808 		AgentDesc_t & tAgent = m_dAgents.Add();
3809 		return & tAgent;
3810 	}
3811 
LastAgentMetaAgentDesc_t3812 	AgentDesc_t * LastAgent()
3813 	{
3814 		assert ( m_dAgents.GetLength()>0 );
3815 		return &m_dAgents.Last();
3816 	}
3817 
RRAgentMetaAgentDesc_t3818 	AgentDesc_t * RRAgent ()
3819 	{
3820 		assert ( m_pRRCounter );
3821 		assert ( m_pLock );
3822 
3823 		if ( m_dAgents.GetLength()==1 )
3824 			return GetAgent(0);
3825 
3826 		CSphScopedLock<InterWorkerStorage> tLock ( *m_pLock );
3827 
3828 		++*m_pRRCounter;
3829 		if ( *m_pRRCounter<0 || *m_pRRCounter>(m_dAgents.GetLength()-1) )
3830 			*m_pRRCounter=0;
3831 
3832 
3833 		AgentDesc_t * pAgent = GetAgent ( *m_pRRCounter );
3834 		return pAgent;
3835 	}
3836 
RandAgentMetaAgentDesc_t3837 	AgentDesc_t * RandAgent ()
3838 	{
3839 		return GetAgent ( sphRand() % m_dAgents.GetLength() );
3840 	}
3841 
WeightedRandAgentMetaAgentDesc_t3842 	void WeightedRandAgent ( int * pBestAgent, CSphVector<int> & dCandidates )
3843 	{
3844 		assert ( m_pWeights );
3845 		assert ( pBestAgent );
3846 		assert ( m_pLock );
3847 		CSphScopedLock<InterWorkerStorage> tLock ( *m_pLock );
3848 		DWORD uBound = m_pWeights[*pBestAgent];
3849 		DWORD uLimit = uBound;
3850 		ARRAY_FOREACH ( i, dCandidates )
3851 			uLimit += m_pWeights[dCandidates[i]];
3852 		DWORD uChance = sphRand() % uLimit;
3853 
3854 		if ( uChance<=uBound )
3855 			return;
3856 
3857 		ARRAY_FOREACH ( i, dCandidates )
3858 		{
3859 			uBound += m_pWeights[dCandidates[i]];
3860 			*pBestAgent = dCandidates[i];
3861 			if ( uChance<=uBound )
3862 				break;
3863 		}
3864 	}
3865 
GetCommonStatMetaAgentDesc_t3866 	inline const HostDashboard_t& GetCommonStat ( int iAgent ) const
3867 	{
3868 		return g_pStats->m_dDashboard.m_dItemStats[m_dAgents[iAgent].m_iDashIndex];
3869 	}
3870 
GetBestDelayMetaAgentDesc_t3871 	inline int64_t GetBestDelay ( int * pBestAgent, CSphVector<int> & dCandidates ) const
3872 	{
3873 		assert ( pBestAgent );
3874 		int64_t iBestAnswerTime = GetCommonStat ( *pBestAgent ).m_iLastAnswerTime
3875 			- GetCommonStat ( *pBestAgent ).m_iLastQueryTime;
3876 		ARRAY_FOREACH ( i, dCandidates )
3877 		{
3878 			if ( iBestAnswerTime > ( GetCommonStat ( dCandidates[i] ).m_iLastAnswerTime
3879 				- GetCommonStat ( dCandidates[i] ).m_iLastQueryTime ) )
3880 			{
3881 				*pBestAgent = dCandidates[i];
3882 				iBestAnswerTime = GetCommonStat ( *pBestAgent ).m_iLastAnswerTime
3883 					- GetCommonStat ( *pBestAgent ).m_iLastQueryTime;
3884 			}
3885 		}
3886 		return iBestAnswerTime;
3887 	}
3888 
3889 
StDiscardDeadMetaAgentDesc_t3890 	AgentDesc_t * StDiscardDead ()
3891 	{
3892 		if ( !g_pStats )
3893 			return RandAgent();
3894 
3895 		if ( m_dAgents.GetLength()==1 )
3896 			return GetAgent(0);
3897 
3898 		// threshold errors-a-row to be counted as dead
3899 		int iDeadThr = 3;
3900 
3901 		int iBestAgent = -1;
3902 		int64_t iErrARow = -1;
3903 		int64_t iThisErrARow = -1;
3904 		CSphVector<int> dCandidates;
3905 		CSphFixedVector<int64_t> dTimers ( m_dAgents.GetLength() );
3906 
3907 		ARRAY_FOREACH ( i, m_dAgents )
3908 		{
3909 			// no locks for g_pStats since we just reading, and read data is not critical.
3910 			const HostDashboard_t & dDash = GetCommonStat ( i );
3911 
3912 			AgentDash_t dDashStat;
3913 			dDash.GetDashStat ( &dDashStat, 1 ); // look at last 30..90 seconds.
3914 			uint64_t uQueries = 0;
3915 			for ( int j=0; j<eMaxCounters; ++j )
3916 				uQueries += dDashStat.m_iStats[j];
3917 			if ( uQueries > 0 )
3918 				dTimers[i] = dDashStat.m_iStats[eTotalMsecs]/uQueries;
3919 			else
3920 				dTimers[i] = 0;
3921 
3922 			iThisErrARow = ( dDash.m_iErrorsARow<=iDeadThr ) ? 0 : dDash.m_iErrorsARow;
3923 
3924 			if ( iErrARow < 0 )
3925 				iErrARow = iThisErrARow;
3926 
3927 			// 2. Among good nodes - select the one(s) with lowest errors/query rating
3928 			if ( iErrARow > iThisErrARow )
3929 			{
3930 				dCandidates.Reset();
3931 				iBestAgent = i;
3932 				iErrARow = iThisErrARow;
3933 			} else if ( iErrARow==iThisErrARow )
3934 			{
3935 				if ( iBestAgent>=0 )
3936 					dCandidates.Add ( iBestAgent );
3937 				iBestAgent = i;
3938 			}
3939 		}
3940 
3941 		// check if it is a time to recalculate the agent's weights
3942 		if ( m_pWeights && dTimers.GetLength () && HostDashboard_t::IsHalfPeriodChanged ( &m_uTimestamp ) )
3943 		{
3944 			CSphFixedVector<WORD> dWeights ( m_dAgents.GetLength() );
3945 			memcpy ( dWeights.Begin(), m_pWeights, sizeof(dWeights[0]) * dWeights.GetLength() );
3946 			RebalanceWeights ( dTimers, dWeights.Begin() );
3947 
3948 			assert ( m_pLock );
3949 			CSphScopedLock<InterWorkerStorage> tLock ( *m_pLock );
3950 			LogAgentWeights ( m_pWeights, dWeights.Begin(), dTimers.Begin(), m_dAgents );
3951 			memcpy ( m_pWeights, dWeights.Begin(), sizeof(dWeights[0]) * dWeights.GetLength() );
3952 		}
3953 
3954 		// nothing to select, sorry. Just plain RR...
3955 		if ( iBestAgent < 0 )
3956 		{
3957 			sphLogDebug ( "HA selector discarded all the candidates and just fall into simple Random" );
3958 			return RandAgent();
3959 		}
3960 
3961 		// only one node with lowest error rating. Return it.
3962 		if ( !dCandidates.GetLength() )
3963 		{
3964 			sphLogDebug ( "client=%s:%d, HA selected %d node with best num of errors a row (" INT64_FMT ")", m_dAgents[iBestAgent].m_sHost.cstr(), m_dAgents[iBestAgent].m_iPort, iBestAgent, iErrARow );
3965 			return &m_dAgents[iBestAgent];
3966 		}
3967 
3968 		// several nodes. Let's select the one.
3969 		WeightedRandAgent ( &iBestAgent, dCandidates );
3970 		if ( g_eLogLevel>=SPH_LOG_VERBOSE_DEBUG )
3971 		{
3972 			float fAge = 0.0;
3973 			const char * sLogStr = NULL;
3974 			const HostDashboard_t & dDash = GetCommonStat ( iBestAgent );
3975 			fAge = ( dDash.m_iLastAnswerTime-dDash.m_iLastQueryTime ) / 1000.0f;
3976 			sLogStr = "client=%s:%d, HA selected %d node by weighted random, with best EaR (" INT64_FMT "), last answered in %.3f milliseconds";
3977 			sphLogDebugv ( sLogStr, m_dAgents[iBestAgent].m_sHost.cstr (), m_dAgents[iBestAgent].m_iPort, iBestAgent, iErrARow, fAge );
3978 		}
3979 
3980 		return &m_dAgents[iBestAgent];
3981 	}
3982 
StLowErrorsMetaAgentDesc_t3983 	AgentDesc_t * StLowErrors()
3984 	{
3985 		if ( !g_pStats )
3986 			return RandAgent();
3987 
3988 		if ( m_dAgents.GetLength()==1 )
3989 			return GetAgent(0);
3990 
3991 		// how much error rating is allowed
3992 		float fAllowedErrorRating = 0.03f; // i.e. 3 errors per 100 queries is still ok
3993 
3994 		int iBestAgent = -1;
3995 		float fBestCriticalErrors = 1.0;
3996 		float fBestAllErrors = 1.0;
3997 		CSphVector<int> dCandidates;
3998 		CSphFixedVector<int64_t> dTimers ( m_dAgents.GetLength() );
3999 
4000 		ARRAY_FOREACH ( i, m_dAgents )
4001 		{
4002 			// no locks for g_pStats since we just reading, and read data is not critical.
4003 			const HostDashboard_t & dDash = GetCommonStat ( i );
4004 
4005 			AgentDash_t dDashStat;
4006 			dDash.GetDashStat ( &dDashStat, 1 ); // look at last 30..90 seconds.
4007 			uint64_t uQueries = 0;
4008 			uint64_t uCriticalErrors = 0;
4009 			uint64_t uAllErrors = 0;
4010 			uint64_t uSuccesses = 0;
4011 			for ( int j=0; j<eMaxCounters; ++j )
4012 			{
4013 				if ( j==eNetworkCritical )
4014 					uCriticalErrors = uQueries;
4015 				else if ( j==eNetworkNonCritical )
4016 				{
4017 					uAllErrors = uQueries;
4018 					uSuccesses = dDashStat.m_iStats[j];
4019 				}
4020 				uQueries += dDashStat.m_iStats[j];
4021 			}
4022 
4023 			if ( uQueries > 0 )
4024 				dTimers[i] = dDashStat.m_iStats[eTotalMsecs]/uQueries;
4025 			else
4026 				dTimers[i] = 0;
4027 
4028 			// 1. No successes queries last period (it includes the pings). Skip such node!
4029 			if ( !uSuccesses )
4030 				continue;
4031 
4032 			if ( uQueries )
4033 			{
4034 				// 2. Among good nodes - select the one(s) with lowest errors/query rating
4035 				float fCriticalErrors = (float) uCriticalErrors/uQueries;
4036 				float fAllErrors = (float) uAllErrors/uQueries;
4037 				if ( fCriticalErrors<=fAllowedErrorRating )
4038 					fCriticalErrors = 0.0f;
4039 				if ( fAllErrors<=fAllowedErrorRating )
4040 					fAllErrors = 0.0f;
4041 				if ( fCriticalErrors < fBestCriticalErrors )
4042 				{
4043 					dCandidates.Reset();
4044 					iBestAgent = i;
4045 					fBestCriticalErrors = fCriticalErrors;
4046 					fBestAllErrors = fAllErrors;
4047 				} else if ( fCriticalErrors==fBestCriticalErrors )
4048 				{
4049 					if ( fAllErrors < fBestAllErrors )
4050 					{
4051 						dCandidates.Reset();
4052 						iBestAgent = i;
4053 						fBestAllErrors = fAllErrors;
4054 					} else if ( fAllErrors==fBestAllErrors )
4055 					{
4056 						if ( iBestAgent>=0 )
4057 							dCandidates.Add ( iBestAgent );
4058 						iBestAgent = i;
4059 					}
4060 				}
4061 			}
4062 		}
4063 
4064 		// check if it is a time to recalculate the agent's weights
4065 		if ( m_pWeights && dTimers.GetLength () && HostDashboard_t::IsHalfPeriodChanged ( &m_uTimestamp ) )
4066 		{
4067 			CSphFixedVector<WORD> dWeights ( m_dAgents.GetLength () );
4068 			memcpy ( dWeights.Begin(), m_pWeights, sizeof(dWeights[0]) * dWeights.GetLength() );
4069 			RebalanceWeights ( dTimers, dWeights.Begin () );
4070 
4071 			assert ( m_pLock );
4072 			CSphScopedLock<InterWorkerStorage> tLock ( *m_pLock );
4073 			LogAgentWeights ( m_pWeights, dWeights.Begin (), dTimers.Begin (), m_dAgents );
4074 			memcpy ( m_pWeights, dWeights.Begin (), sizeof ( dWeights[0] ) * dWeights.GetLength () );
4075 		}
4076 
4077 
4078 		// nothing to select, sorry. Just plain RR...
4079 		if ( iBestAgent < 0 )
4080 		{
4081 			sphLogDebug ( "HA selector discarded all the candidates and just fall into simple Random" );
4082 			return RandAgent();
4083 		}
4084 
4085 		// only one node with lowest error rating. Return it.
4086 		if ( !dCandidates.GetLength() )
4087 		{
4088 			sphLogDebug ( "client=%s:%d, HA selected %d node with best error rating (%.2f)", m_dAgents[iBestAgent].m_sHost.cstr (), m_dAgents[iBestAgent].m_iPort, iBestAgent, fBestCriticalErrors );
4089 			return &m_dAgents[iBestAgent];
4090 		}
4091 
4092 		// several nodes. Let's select the one.
4093 		WeightedRandAgent ( &iBestAgent, dCandidates );
4094 		if ( g_eLogLevel>=SPH_LOG_VERBOSE_DEBUG )
4095 		{
4096 			float fAge = 0.0f;
4097 			const char * sLogStr = NULL;
4098 			const HostDashboard_t & dDash = GetCommonStat ( iBestAgent );
4099 			fAge = ( dDash.m_iLastAnswerTime-dDash.m_iLastQueryTime ) / 1000.0f;
4100 			sLogStr = "client=%s:%d, HA selected %d node by weighted random, with best error rating (%.2f), answered %f seconds ago";
4101 			sphLogDebugv ( sLogStr, m_dAgents[iBestAgent].m_sHost.cstr (), m_dAgents[iBestAgent].m_iPort, iBestAgent, fBestCriticalErrors, fAge );
4102 		}
4103 
4104 		return &m_dAgents[iBestAgent];
4105 	}
4106 
4107 
GetRRAgentMetaAgentDesc_t4108 	AgentDesc_t * GetRRAgent ()
4109 	{
4110 		switch ( m_eStrategy )
4111 		{
4112 		case HA_AVOIDDEAD:
4113 			return StDiscardDead();
4114 		case HA_AVOIDERRORS:
4115 			return StLowErrors();
4116 		case HA_ROUNDROBIN:
4117 			return RRAgent();
4118 		default:
4119 			return RandAgent();
4120 		}
4121 	}
4122 
4123 
IsHAMetaAgentDesc_t4124 	inline bool IsHA() const
4125 	{
4126 		return m_dAgents.GetLength() > 1;
4127 	}
4128 
GetLengthMetaAgentDesc_t4129 	inline int GetLength() const
4130 	{
4131 		return m_dAgents.GetLength();
4132 	}
4133 
QueuePingsMetaAgentDesc_t4134 	void QueuePings()
4135 	{
4136 		if ( !IsHA() )
4137 			return;
4138 
4139 		if ( g_pStats )
4140 		{
4141 			g_tStatsMutex.Lock();
4142 			ARRAY_FOREACH ( i, m_dAgents )
4143 				g_pStats->m_dDashboard.m_dItemStats[m_dAgents[i].m_iDashIndex].m_bNeedPing = true;
4144 			g_tStatsMutex.Unlock();
4145 		}
4146 	}
4147 
GetAgentsMetaAgentDesc_t4148 	const CSphVector<AgentDesc_t>& GetAgents() const
4149 	{
4150 		return m_dAgents;
4151 	}
4152 
GetWeightsMetaAgentDesc_t4153 	const WORD* GetWeights() const
4154 	{
4155 		return m_pWeights;
4156 	}
4157 
operator =MetaAgentDesc_t4158 	MetaAgentDesc_t & operator= ( const MetaAgentDesc_t & rhs )
4159 	{
4160 		if ( this==&rhs )
4161 			return *this;
4162 		m_dAgents = rhs.GetAgents();
4163 		m_pWeights = rhs.m_pWeights;
4164 		m_pRRCounter = rhs.m_pRRCounter;
4165 		m_pLock = rhs.m_pLock;
4166 		m_eStrategy = rhs.m_eStrategy;
4167 		return *this;
4168 	}
4169 };
4170 
4171 /// remote agent state
4172 enum AgentState_e
4173 {
4174 	AGENT_UNUSED = 0,				///< agent is unused for this request
4175 	AGENT_CONNECTING,				///< connecting to agent in progress, write handshake on socket ready
4176 	AGENT_HANDSHAKE,				///< waiting for "VER x" hello, read response on socket ready
4177 	AGENT_ESTABLISHED,				///< handshake completed. Ready to sent query, write query on socket ready
4178 	AGENT_QUERYED,					///< query sent, waiting for reply. read reply on socket ready
4179 	AGENT_PREREPLY,					///< query sent, activity detected, need to read reply
4180 	AGENT_REPLY,					///< reading reply
4181 	AGENT_RETRY						///< should retry
4182 };
4183 
4184 /// remote agent connection (local per-query state)
4185 struct AgentConn_t : public AgentDesc_t
4186 {
4187 	int				m_iSock;		///< socket number, -1 if not connected
4188 	bool			m_bFresh;		///< just created persistent connection, need SEARCHD_COMMAND_PERSIST
4189 	AgentState_e	m_eState;		///< current state
4190 
4191 	bool			m_bSuccess;		///< whether last request was successful (ie. there are available results)
4192 	CSphString		m_sFailure;		///< failure message
4193 
4194 	int				m_iReplyStatus;	///< reply status code
4195 	int				m_iReplySize;	///< how many reply bytes are there
4196 	int				m_iReplyRead;	///< how many reply bytes are alredy received
4197 	BYTE *			m_pReplyBuf;	///< reply buffer
4198 
4199 	CSphVector<CSphQueryResult>		m_dResults;		///< multi-query results
4200 
4201 	int64_t			m_iWall;		///< wall time spent vs this agent
4202 	int64_t			m_iWaited;		///< statistics of waited
4203 	int64_t			m_iStartQuery;	///< the timestamp of the latest request
4204 	int64_t			m_iEndQuery;	///< the timestamp of the end of the latest operation
4205 	int				m_iWorkerTag;	///< worker tag
4206 	int				m_iStoreTag;
4207 	int				m_iWeight;
4208 	bool			m_bPing;
4209 
4210 public:
AgentConn_tAgentConn_t4211 	AgentConn_t ()
4212 		: m_iSock ( -1 )
4213 		, m_bFresh ( true )
4214 		, m_eState ( AGENT_UNUSED )
4215 		, m_bSuccess ( false )
4216 		, m_iReplyStatus ( -1 )
4217 		, m_iReplySize ( 0 )
4218 		, m_iReplyRead ( 0 )
4219 		, m_pReplyBuf ( NULL )
4220 		, m_iWall ( 0 )
4221 		, m_iWaited ( 0 )
4222 		, m_iStartQuery ( 0 )
4223 		, m_iEndQuery ( 0 )
4224 		, m_iWorkerTag ( -1 )
4225 		, m_iStoreTag ( 0 )
4226 		, m_iWeight ( -1 )
4227 		, m_bPing ( false )
4228 	{}
4229 
~AgentConn_tAgentConn_t4230 	~AgentConn_t ()
4231 	{
4232 		Close ( false );
4233 		if ( m_bPersistent )
4234 			m_dPersPool.ReturnConnection ( m_iSock );
4235 	}
4236 
CloseAgentConn_t4237 	void Close ( bool bClosePersist=true )
4238 	{
4239 		SafeDeleteArray ( m_pReplyBuf );
4240 		if ( m_iSock>0 )
4241 		{
4242 			m_bFresh = false;
4243 			if ( ( m_bPersistent && bClosePersist ) || !m_bPersistent )
4244 			{
4245 				sphSockClose ( m_iSock );
4246 				m_iSock = -1;
4247 				m_bFresh = true;
4248 			}
4249 			if ( m_eState!=AGENT_RETRY )
4250 				m_eState = AGENT_UNUSED;
4251 		}
4252 		m_iWall += sphMicroTimer ();
4253 	}
4254 
4255 	void Fail ( eAgentStats eStat, const char* sMessage, ... ) __attribute__ ( ( format ( printf, 3, 4 ) ) );
4256 
operator =AgentConn_t4257 	AgentConn_t & operator = ( const AgentDesc_t & rhs )
4258 	{
4259 		m_sHost = rhs.m_sHost;
4260 		m_iPort = rhs.m_iPort;
4261 		m_sPath = rhs.m_sPath;
4262 		m_sIndexes = rhs.m_sIndexes;
4263 		m_bBlackhole = rhs.m_bBlackhole;
4264 		m_iFamily = rhs.m_iFamily;
4265 		m_uAddr = rhs.m_uAddr;
4266 		m_iStatsIndex = rhs.m_iStatsIndex;
4267 		m_iDashIndex = rhs.m_iDashIndex;
4268 		m_bPersistent = rhs.m_bPersistent;
4269 		m_dPersPool = rhs.m_dPersPool;
4270 
4271 		return *this;
4272 	}
4273 
4274 	// works like =, but also adopt the persistent connection, if any.
TakeTraitsAgentConn_t4275 	void TakeTraits ( AgentDesc_t & rhs )
4276 	{
4277 		*this = rhs;
4278 		if ( m_bPersistent )
4279 		{
4280 			m_iSock = m_dPersPool.RentConnection();
4281 			if ( m_iSock==-2 ) // no free persistent connections. This connection will be not persistent
4282 				m_bPersistent = false;
4283 		}
4284 		m_bFresh = ( m_bPersistent && m_iSock<0 );
4285 	}
4286 };
4287 
4288 /// distributed index
4289 struct DistributedIndex_t
4290 {
4291 	CSphVector<MetaAgentDesc_t>		m_dAgents;					///< remote agents
4292 	CSphVector<CSphString>		m_dLocal;					///< local indexes
4293 	int							m_iAgentConnectTimeout;		///< in msec
4294 	int							m_iAgentQueryTimeout;		///< in msec
4295 	bool						m_bToDelete;				///< should be deleted
4296 	bool						m_bDivideRemoteRanges;			///< whether we divide big range onto agents or not
4297 	HAStrategies_e				m_eHaStrategy;				///< how to select the best of my agents
4298 	InterWorkerStorage *		m_pHAStorage;				///< IPC HA arrays
4299 
4300 public:
DistributedIndex_tDistributedIndex_t4301 	DistributedIndex_t ()
4302 		: m_iAgentConnectTimeout ( g_iAgentConnectTimeout )
4303 		, m_iAgentQueryTimeout ( g_iAgentQueryTimeout )
4304 		, m_bToDelete ( false )
4305 		, m_bDivideRemoteRanges ( false )
4306 		, m_eHaStrategy ( HA_DEFAULT )
4307 		, m_pHAStorage ( NULL )
4308 	{}
~DistributedIndex_tDistributedIndex_t4309 	~DistributedIndex_t()
4310 	{
4311 		// m_pHAStorage has to be freed separately.
4312 	}
GetAllAgentsDistributedIndex_t4313 	void GetAllAgents ( CSphVector<AgentConn_t> * pTarget ) const
4314 	{
4315 		assert ( pTarget );
4316 		ARRAY_FOREACH ( i, m_dAgents )
4317 			ARRAY_FOREACH ( j, m_dAgents[i].GetAgents() )
4318 			{
4319 				AgentDesc_t & dAgent = pTarget->Add();
4320 				dAgent = m_dAgents[i].GetAgents()[j];
4321 			}
4322 	}
4323 
ShareHACountersDistributedIndex_t4324 	void ShareHACounters()
4325 	{
4326 		int iSharedValues = 0;
4327 		int iRRCounters = 0;
4328 		ARRAY_FOREACH ( i, m_dAgents )
4329 			if ( m_dAgents[i].IsHA() )
4330 			{
4331 				iSharedValues += m_dAgents[i].GetLength();
4332 				++iRRCounters;
4333 			}
4334 
4335 		// nothing to share.
4336 		if ( !iSharedValues )
4337 			return;
4338 
4339 		// so, we need to share between workers iFloatValues floats and iRRCounters ints.
4340 		int iBufSize = iRRCounters * sizeof(int) + iSharedValues * sizeof(WORD) * 2; // NOLINT
4341 		m_pHAStorage = new InterWorkerStorage;
4342 		m_pHAStorage->Init ( iBufSize );
4343 
4344 		// do the sharing.
4345 		BYTE* pBuffer = m_pHAStorage->GetSharedData();
4346 		ARRAY_FOREACH ( i, m_dAgents )
4347 			if ( m_dAgents[i].IsHA() )
4348 			{
4349 				MetaAgentDesc_t & dAgent = m_dAgents[i];
4350 				WORD* pWeights = (WORD*) ( pBuffer + sizeof(int) ); // NOLINT
4351 				WORD dFrac = WORD ( 0xFFFF / dAgent.GetLength() );
4352 				ARRAY_FOREACH ( j, dAgent ) ///< works since dAgent has method GetLength()
4353 					pWeights[j] = dFrac;
4354 				dAgent.SetHAData ( (int*)pBuffer, pWeights, m_pHAStorage );
4355 				pBuffer += sizeof(int) + sizeof(float)*dAgent.GetLength(); // NOLINT
4356 			}
4357 	}
4358 
RemoveHACountersDistributedIndex_t4359 	void RemoveHACounters()
4360 	{
4361 		ARRAY_FOREACH ( i, m_dAgents )
4362 			if ( m_dAgents[i].IsHA() )
4363 				m_dAgents[i].SetHAData ( NULL, NULL, NULL );
4364 		SafeDelete ( m_pHAStorage );
4365 	}
4366 };
4367 
4368 /// global distributed index definitions hash
4369 static SmallStringHash_T < DistributedIndex_t >		g_hDistIndexes;
4370 
4371 /////////////////////////////////////////////////////////////////////////////
4372 
4373 struct IRequestBuilder_t : public ISphNoncopyable
4374 {
~IRequestBuilder_tIRequestBuilder_t4375 	virtual ~IRequestBuilder_t () {} // to avoid gcc4 warns
4376 	virtual void BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const = 0;
4377 };
4378 
4379 
4380 struct IReplyParser_t
4381 {
~IReplyParser_tIReplyParser_t4382 	virtual ~IReplyParser_t () {} // to avoid gcc4 warns
4383 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & tAgent ) const = 0;
4384 };
4385 
agent_stats_inc(AgentConn_t & tAgent,eAgentStats iCounter)4386 inline void agent_stats_inc ( AgentConn_t & tAgent, eAgentStats iCounter )
4387 {
4388 	if ( g_pStats && tAgent.m_iStatsIndex>=0 && tAgent.m_iStatsIndex<STATS_MAX_AGENTS )
4389 	{
4390 		g_tStatsMutex.Lock ();
4391 		uint64_t* pCurStat = g_pStats->m_dDashboard.m_dItemStats [ tAgent.m_iDashIndex ].GetCurrentStat()->m_iStats;
4392 		if ( iCounter==eMaxMsecs || iCounter==eAverageMsecs )
4393 		{
4394 			++pCurStat[eConnTries];
4395 			int64_t iConnTime = sphMicroTimer() - tAgent.m_iStartQuery;
4396 			if ( uint64_t(iConnTime)>pCurStat[eMaxMsecs] )
4397 				pCurStat[eMaxMsecs] = iConnTime;
4398 			if ( pCurStat[eConnTries]>1 )
4399 				pCurStat[eAverageMsecs] = (pCurStat[eAverageMsecs]*(pCurStat[eConnTries]-1)+iConnTime)/pCurStat[eConnTries];
4400 			else
4401 				pCurStat[eAverageMsecs] = iConnTime;
4402 		} else
4403 		{
4404 			g_pStats->m_dAgentStats.m_dItemStats [ tAgent.m_iStatsIndex ].m_iStats[iCounter]++;
4405 			if ( tAgent.m_iDashIndex>=0 && tAgent.m_iDashIndex<STATS_MAX_DASH )
4406 			{
4407 				++pCurStat[iCounter];
4408 				if ( iCounter>=eNoErrors && iCounter<eMaxCounters )
4409 					g_pStats->m_dDashboard.m_dItemStats [ tAgent.m_iDashIndex ].m_iErrorsARow = 0;
4410 				else
4411 					g_pStats->m_dDashboard.m_dItemStats [ tAgent.m_iDashIndex ].m_iErrorsARow += 1;
4412 				tAgent.m_iEndQuery = sphMicroTimer();
4413 				g_pStats->m_dDashboard.m_dItemStats [ tAgent.m_iDashIndex ].m_iLastQueryTime = tAgent.m_iStartQuery;
4414 				g_pStats->m_dDashboard.m_dItemStats [ tAgent.m_iDashIndex ].m_iLastAnswerTime = tAgent.m_iEndQuery;
4415 				// do not count query time for pings
4416 				// only count errors
4417 				if ( !tAgent.m_bPing )
4418 					pCurStat[eTotalMsecs]+=tAgent.m_iEndQuery-tAgent.m_iStartQuery;
4419 			}
4420 		}
4421 		g_tStatsMutex.Unlock ();
4422 	}
4423 }
4424 
Fail(eAgentStats eStat,const char * sMessage,...)4425 void AgentConn_t::Fail ( eAgentStats eStat, const char* sMessage, ... )
4426 {
4427 	Close ();
4428 	va_list ap;
4429 	va_start ( ap, sMessage );
4430 	m_sFailure.SetSprintfVa ( sMessage, ap );
4431 	va_end ( ap );
4432 	agent_stats_inc ( *this, eStat );
4433 }
4434 
4435 struct AgentConnectionContext_t
4436 {
4437 	const IRequestBuilder_t * m_pBuilder;
4438 	AgentConn_t	* m_pAgents;
4439 	int m_iAgentCount;
4440 	int m_iTimeout;
4441 	int m_iRetriesMax;
4442 	int m_iDelay;
4443 
AgentConnectionContext_tAgentConnectionContext_t4444 	AgentConnectionContext_t ()
4445 		: m_pBuilder ( NULL )
4446 		, m_pAgents ( NULL )
4447 		, m_iAgentCount ( 0 )
4448 		, m_iTimeout ( 0 )
4449 		, m_iRetriesMax ( 0 )
4450 		, m_iDelay ( 0 )
4451 	{}
4452 };
4453 
RemoteConnectToAgent(AgentConn_t & tAgent)4454 void RemoteConnectToAgent ( AgentConn_t & tAgent )
4455 {
4456 	bool bAgentRetry = ( tAgent.m_eState==AGENT_RETRY );
4457 	tAgent.m_eState = AGENT_UNUSED;
4458 
4459 	if ( tAgent.m_iSock>=0 ) // already connected
4460 	{
4461 		if ( !sphSockEof ( tAgent.m_iSock ) )
4462 		{
4463 			tAgent.m_eState = AGENT_ESTABLISHED;
4464 			tAgent.m_iStartQuery = sphMicroTimer();
4465 			tAgent.m_iWall -= tAgent.m_iStartQuery;
4466 			return;
4467 		}
4468 		tAgent.Close();
4469 	}
4470 
4471 	tAgent.m_bSuccess = false;
4472 
4473 	socklen_t len = 0;
4474 	struct sockaddr_storage ss;
4475 	memset ( &ss, 0, sizeof(ss) );
4476 	ss.ss_family = (short)tAgent.m_iFamily;
4477 	bool bUnixSocket = false;
4478 
4479 	if ( ss.ss_family==AF_INET )
4480 	{
4481 		struct sockaddr_in *in = (struct sockaddr_in *)&ss;
4482 		in->sin_port = htons ( (unsigned short)tAgent.m_iPort );
4483 		in->sin_addr.s_addr = tAgent.m_uAddr;
4484 		len = sizeof(*in);
4485 	}
4486 #if !USE_WINDOWS
4487 	else if ( ss.ss_family==AF_UNIX )
4488 	{
4489 		struct sockaddr_un *un = (struct sockaddr_un *)&ss;
4490 		snprintf ( un->sun_path, sizeof(un->sun_path), "%s", tAgent.m_sPath.cstr() );
4491 		len = sizeof(*un);
4492 		bUnixSocket = true;
4493 	}
4494 #endif
4495 
4496 	tAgent.m_iSock = socket ( tAgent.m_iFamily, SOCK_STREAM, 0 );
4497 	if ( tAgent.m_iSock<0 )
4498 	{
4499 		tAgent.m_sFailure.SetSprintf ( "socket() failed: %s", sphSockError() );
4500 		return;
4501 	}
4502 
4503 	if ( sphSetSockNB ( tAgent.m_iSock )<0 )
4504 	{
4505 		tAgent.m_sFailure.SetSprintf ( "sphSetSockNB() failed: %s", sphSockError() );
4506 		return;
4507 	}
4508 
4509 #ifdef TCP_NODELAY
4510 	int iOn = 1;
4511 	if ( !bUnixSocket && setsockopt ( tAgent.m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&iOn, sizeof(iOn) ) )
4512 	{
4513 		tAgent.m_sFailure.SetSprintf ( "setsockopt() failed: %s", sphSockError() );
4514 		return;
4515 	}
4516 #endif
4517 
4518 	// count connects
4519 	if ( g_pStats )
4520 	{
4521 		g_tStatsMutex.Lock();
4522 		g_pStats->m_iAgentConnect++;
4523 		g_pStats->m_iAgentRetry += ( bAgentRetry );
4524 		g_tStatsMutex.Unlock();
4525 	}
4526 
4527 	tAgent.m_iStartQuery = sphMicroTimer();
4528 	tAgent.m_iWall -= tAgent.m_iStartQuery;
4529 	if ( connect ( tAgent.m_iSock, (struct sockaddr*)&ss, len )<0 )
4530 	{
4531 		int iErr = sphSockGetErrno();
4532 		if ( iErr!=EINPROGRESS && iErr!=EINTR && iErr!=EWOULDBLOCK ) // check for EWOULDBLOCK is for winsock only
4533 		{
4534 			tAgent.Fail ( eConnectFailures, "connect() failed: errno=%d, %s", iErr, sphSockError(iErr) );
4535 			tAgent.m_eState = AGENT_RETRY; // do retry on connect() failures
4536 			return;
4537 
4538 		} else
4539 		{
4540 			// connection in progress
4541 			tAgent.m_eState = AGENT_CONNECTING;
4542 		}
4543 	} else
4544 	{
4545 		// connect() success
4546 		agent_stats_inc ( tAgent, eMaxMsecs );
4547 		// send the client's proto version right now to avoid w-w-r pattern.
4548 		NetOutputBuffer_c tOut ( tAgent.m_iSock );
4549 		tOut.SendDword ( SPHINX_CLIENT_VERSION );
4550 		tOut.Flush (); // FIXME! handle flush failure?
4551 		// socket connected, ready to read hello message
4552 		tAgent.m_eState = AGENT_HANDSHAKE;
4553 	}
4554 }
4555 
4556 #if HAVE_EPOLL
4557 // copy-pasted version with epoll; plain version below
4558 // process states AGENT_CONNECTING, AGENT_HANDSHAKE, AGENT_ESTABLISHED and notes AGENT_QUERYED
4559 // called in serial order with RemoteConnectToAgents (so, the context is NOT changed during the call).
RemoteQueryAgents(AgentConnectionContext_t * pCtx)4560 int RemoteQueryAgents ( AgentConnectionContext_t * pCtx )
4561 {
4562 	assert ( pCtx->m_iTimeout>=0 );
4563 	assert ( pCtx->m_pAgents );
4564 	assert ( pCtx->m_iAgentCount );
4565 
4566 	int iAgents = 0;
4567 	int64_t tmMaxTimer = sphMicroTimer() + pCtx->m_iTimeout*1000; // in microseconds
4568 
4569 	int eid = epoll_create ( pCtx->m_iAgentCount );
4570 	CSphVector<epoll_event> dEvents ( pCtx->m_iAgentCount );
4571 	epoll_event dEvent;
4572 	int iEvents = 0;
4573 	bool bTimeout = false;
4574 
4575 	for ( ;; )
4576 	{
4577 		if ( !iEvents )
4578 		{
4579 			for ( int i=0; i<pCtx->m_iAgentCount; i++ )
4580 			{
4581 				AgentConn_t & tAgent = pCtx->m_pAgents[i];
4582 				// select only 'initial' agents - which are not send query response.
4583 				if ( tAgent.m_eState<AGENT_CONNECTING || tAgent.m_eState>AGENT_QUERYED )
4584 					continue;
4585 
4586 				assert ( !tAgent.m_sPath.IsEmpty() || tAgent.m_iPort>0 );
4587 				assert ( tAgent.m_iSock>0 );
4588 				if ( tAgent.m_iSock<=0 || ( tAgent.m_sPath.IsEmpty() && tAgent.m_iPort<=0 ) )
4589 				{
4590 					tAgent.Fail ( eConnectFailures, "invalid agent in querying. Socket %d, Path %s, Port %d", tAgent.m_iSock, tAgent.m_sPath.cstr(), tAgent.m_iPort );
4591 					tAgent.m_eState = AGENT_RETRY; // do retry on connect() failures
4592 					continue;
4593 				}
4594 				dEvent.events = ( tAgent.m_eState==AGENT_CONNECTING || tAgent.m_eState==AGENT_ESTABLISHED ) ? EPOLLOUT : EPOLLIN;
4595 				dEvent.data.ptr = &tAgent;
4596 				epoll_ctl ( eid, EPOLL_CTL_ADD, tAgent.m_iSock, &dEvent );
4597 				++iEvents;
4598 			}
4599 		}
4600 
4601 		bool bDone = true;
4602 		for ( int i=0; i<pCtx->m_iAgentCount; i++ )
4603 		{
4604 			AgentConn_t & tAgent = pCtx->m_pAgents[i];
4605 			// select only 'initial' agents - which are not send query response.
4606 			if ( tAgent.m_eState<AGENT_CONNECTING || tAgent.m_eState>AGENT_QUERYED )
4607 				continue;
4608 			if ( tAgent.m_eState!=AGENT_QUERYED )
4609 				bDone = false;
4610 		}
4611 		if ( bDone )
4612 			break;
4613 
4614 		// compute timeout
4615 		int64_t tmSelect = sphMicroTimer();
4616 		int64_t tmMicroLeft = tmMaxTimer - tmSelect;
4617 		if ( tmMicroLeft<=0 )
4618 		{
4619 			bTimeout = true;
4620 			break; // FIXME? what about iTimeout==0 case?
4621 		}
4622 
4623 
4624 		// do poll
4625 		int iSelected = ::epoll_wait ( eid, dEvents.Begin(), dEvents.GetLength(), int( tmMicroLeft/1000 ) );
4626 
4627 		// update counters, and loop again if nothing happened
4628 		pCtx->m_pAgents->m_iWaited += sphMicroTimer() - tmSelect;
4629 		// todo: do we need to check for EINTR here? Or the fact of timeout is enough anyway?
4630 		if ( iSelected<=0 )
4631 			continue;
4632 
4633 		// ok, something did happen, so loop the agents and do them checks
4634 		for ( int i=0; i<iSelected; ++i )
4635 		{
4636 			AgentConn_t & tAgent = *(AgentConn_t*)dEvents[i].data.ptr;
4637 			bool bReadable = ( dEvents[i].events & EPOLLIN )!=0;
4638 			bool bWriteable = ( dEvents[i].events & EPOLLOUT )!=0;
4639 			bool bErr = ( ( dEvents[i].events & ( EPOLLERR | EPOLLHUP ) )!=0 );
4640 
4641 			if ( tAgent.m_eState==AGENT_CONNECTING && ( bWriteable || bErr ) )
4642 			{
4643 				if ( bErr )
4644 				{
4645 					epoll_ctl ( eid, EPOLL_CTL_DEL, tAgent.m_iSock, &dEvent );
4646 					--iEvents;
4647 
4648 					int iErr = 0;
4649 					socklen_t iErrLen = sizeof(iErr);
4650 					getsockopt ( tAgent.m_iSock, SOL_SOCKET, SO_ERROR, (char*)&iErr, &iErrLen );
4651 					// connect() failure
4652 					tAgent.Fail ( eConnectFailures, "connect() failed: errno=%d, %s", iErr, sphSockError(iErr) );
4653 					continue;
4654 				}
4655 
4656 				// connect() success
4657 				agent_stats_inc ( tAgent, eMaxMsecs );
4658 
4659 				// send the client's proto version right now to avoid w-w-r pattern.
4660 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
4661 				tOut.SendDword ( SPHINX_CLIENT_VERSION );
4662 				tOut.Flush (); // FIXME! handle flush failure?
4663 				dEvent.events = EPOLLIN;
4664 				dEvent.data.ptr = &tAgent;
4665 				epoll_ctl ( eid, EPOLL_CTL_MOD, tAgent.m_iSock, &dEvent );
4666 
4667 				tAgent.m_eState = AGENT_HANDSHAKE;
4668 
4669 				continue;
4670 			}
4671 
4672 			// check if hello was received
4673 			if ( tAgent.m_eState==AGENT_HANDSHAKE && bReadable )
4674 			{
4675 				// read reply
4676 				int iRemoteVer;
4677 				int iRes = sphSockRecv ( tAgent.m_iSock, (char*)&iRemoteVer, sizeof(iRemoteVer) );
4678 				if ( iRes!=sizeof(iRemoteVer) )
4679 				{
4680 					epoll_ctl ( eid, EPOLL_CTL_DEL, tAgent.m_iSock, &dEvent );
4681 					--iEvents;
4682 					if ( iRes<0 )
4683 					{
4684 						// network error
4685 						int iErr = sphSockGetErrno();
4686 						tAgent.Fail ( eNetworkErrors, "handshake failure (errno=%d, msg=%s)", iErr, sphSockError(iErr) );
4687 					} else if ( iRes>0 )
4688 					{
4689 						// incomplete reply
4690 						tAgent.Fail ( eWrongReplies, "handshake failure (exp=%d, recv=%d)", (int)sizeof(iRemoteVer), iRes );
4691 					} else
4692 					{
4693 						// agent closed the connection
4694 						// this might happen in out-of-sync connect-accept case; so let's retry
4695 						tAgent.Fail ( eUnexpectedClose, "handshake failure (connection was closed)" );
4696 						tAgent.m_eState = AGENT_RETRY;
4697 					}
4698 					continue;
4699 				}
4700 
4701 				iRemoteVer = ntohl ( iRemoteVer );
4702 				if (!( iRemoteVer==SPHINX_SEARCHD_PROTO || iRemoteVer==0x01000000UL ) ) // workaround for all the revisions that sent it in host order...
4703 				{
4704 					tAgent.Fail ( eWrongReplies, "handshake failure (unexpected protocol version=%d)", iRemoteVer );
4705 					continue;
4706 				}
4707 
4708 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
4709 				// check if we need to reset the persistent connection
4710 				if ( tAgent.m_bFresh && tAgent.m_bPersistent )
4711 				{
4712 					tOut.SendWord ( SEARCHD_COMMAND_PERSIST );
4713 					tOut.SendWord ( 0 ); // dummy version
4714 					tOut.SendInt ( 4 ); // request body length
4715 					tOut.SendInt ( 1 ); // set persistent to 1.
4716 					tOut.Flush ();
4717 					tAgent.m_bFresh = false;
4718 				}
4719 
4720 				tAgent.m_eState = AGENT_ESTABLISHED;
4721 				dEvent.events = EPOLLOUT;
4722 				dEvent.data.ptr = &tAgent;
4723 				epoll_ctl ( eid, EPOLL_CTL_MOD, tAgent.m_iSock, &dEvent );
4724 				continue;
4725 			}
4726 
4727 			if ( tAgent.m_eState==AGENT_ESTABLISHED && bWriteable )
4728 			{
4729 				// send request
4730 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
4731 				pCtx->m_pBuilder->BuildRequest ( tAgent, tOut );
4732 				tOut.Flush (); // FIXME! handle flush failure?
4733 				tAgent.m_eState = AGENT_QUERYED;
4734 				iAgents++;
4735 				dEvent.events = EPOLLIN;
4736 				dEvent.data.ptr = &tAgent;
4737 				epoll_ctl ( eid, EPOLL_CTL_MOD, tAgent.m_iSock, &dEvent );
4738 				continue;
4739 			}
4740 
4741 			// check if queried agent replied while we were querying others
4742 			if ( tAgent.m_eState==AGENT_QUERYED && bReadable )
4743 			{
4744 				// do not account agent wall time from here; agent is probably ready
4745 				tAgent.m_iWall += sphMicroTimer();
4746 				tAgent.m_eState = AGENT_PREREPLY;
4747 				epoll_ctl ( eid, EPOLL_CTL_DEL, tAgent.m_iSock, &dEvent );
4748 				--iEvents;
4749 				continue;
4750 			}
4751 		}
4752 	}
4753 	close ( eid );
4754 
4755 	// check if connection timed out
4756 	for ( int i=0; i<pCtx->m_iAgentCount; i++ )
4757 	{
4758 		AgentConn_t & tAgent = pCtx->m_pAgents[i];
4759 		if ( bTimeout && ( tAgent.m_eState!=AGENT_QUERYED && tAgent.m_eState!=AGENT_UNUSED && tAgent.m_eState!=AGENT_RETRY && tAgent.m_eState!=AGENT_PREREPLY
4760 			&& tAgent.m_eState!=AGENT_REPLY ) )
4761 		{
4762 			// technically, we can end up here via two different routes
4763 			// a) connect() never finishes in given time frame
4764 			// b) agent actually accept()s the connection but keeps silence
4765 			// however, there's no way to tell the two from each other
4766 			// so we just account both cases as connect() failure
4767 			tAgent.Fail ( eTimeoutsConnect, "connect() timed out" );
4768 			tAgent.m_eState = AGENT_RETRY; // do retry on connect() failures
4769 		}
4770 	}
4771 
4772 	return iAgents;
4773 }
4774 
4775 // epoll version. Plain version below
4776 // processing states AGENT_QUERY, AGENT_PREREPLY and AGENT_REPLY
4777 // may work in parallel with RemoteQueryAgents, so the state MAY change duirng a call.
RemoteWaitForAgents(CSphVector<AgentConn_t> & dAgents,int iTimeout,IReplyParser_t & tParser)4778 int RemoteWaitForAgents ( CSphVector<AgentConn_t> & dAgents, int iTimeout, IReplyParser_t & tParser )
4779 {
4780 	assert ( iTimeout>=0 );
4781 
4782 	int iAgents = 0;
4783 	int64_t tmMaxTimer = sphMicroTimer() + iTimeout*1000; // in microseconds
4784 
4785 	int eid = epoll_create ( dAgents.GetLength() );
4786 	CSphVector<epoll_event> dEvents ( dAgents.GetLength() );
4787 	epoll_event dEvent;
4788 	int iEvents = 0;
4789 	bool bTimeout = false;
4790 
4791 	for ( ;; )
4792 	{
4793 		if ( !iEvents )
4794 		{
4795 			bool bDone = true;
4796 			ARRAY_FOREACH ( iAgent, dAgents )
4797 			{
4798 				AgentConn_t & tAgent = dAgents[iAgent];
4799 				if ( tAgent.m_bBlackhole )
4800 					continue;
4801 
4802 				if ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_REPLY || tAgent.m_eState==AGENT_PREREPLY )
4803 				{
4804 					assert ( !tAgent.m_sPath.IsEmpty() || tAgent.m_iPort>0 );
4805 					assert ( tAgent.m_iSock>0 );
4806 					dEvent.events = EPOLLIN;
4807 					dEvent.data.ptr = &tAgent;
4808 					epoll_ctl ( eid, EPOLL_CTL_ADD, tAgent.m_iSock, &dEvent );
4809 					++iEvents;
4810 					bDone = false;
4811 				}
4812 			}
4813 
4814 			if ( bDone )
4815 				break;
4816 		}
4817 
4818 
4819 
4820 		int64_t tmSelect = sphMicroTimer();
4821 		int64_t tmMicroLeft = tmMaxTimer - tmSelect;
4822 		if ( tmMicroLeft<=0 ) // FIXME? what about iTimeout==0 case?
4823 		{
4824 			bTimeout = true;
4825 			break;
4826 		}
4827 
4828 		int iSelected = ::epoll_wait ( eid, dEvents.Begin(), dEvents.GetLength(), int( tmMicroLeft/1000 ) );
4829 		dAgents.Begin()->m_iWaited += sphMicroTimer() - tmSelect;
4830 
4831 		if ( iSelected<=0 )
4832 			continue;
4833 
4834 		for ( int i=0; i<iSelected; ++i )
4835 		{
4836 			AgentConn_t & tAgent = *(AgentConn_t*)dEvents[i].data.ptr;
4837 			if ( tAgent.m_bBlackhole )
4838 				continue;
4839 			if (!( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_REPLY || tAgent.m_eState==AGENT_PREREPLY ))
4840 				continue;
4841 
4842 			if (!( dEvents[i].events & POLLIN ))
4843 				continue;
4844 
4845 			// if there was no reply yet, read reply header
4846 			bool bFailure = true;
4847 			bool bWarnings = false;
4848 
4849 			for ( ;; )
4850 			{
4851 				if ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_PREREPLY )
4852 				{
4853 					if ( tAgent.m_eState==AGENT_PREREPLY )
4854 					{
4855 						tAgent.m_iWall -= sphMicroTimer();
4856 						tAgent.m_eState = AGENT_QUERYED;
4857 					}
4858 
4859 					// try to read
4860 					struct
4861 					{
4862 						WORD	m_iStatus;
4863 						WORD	m_iVer;
4864 						int		m_iLength;
4865 					} tReplyHeader;
4866 					STATIC_SIZE_ASSERT ( tReplyHeader, 8 );
4867 
4868 					if ( sphSockRecv ( tAgent.m_iSock, (char*)&tReplyHeader, sizeof(tReplyHeader) )!=sizeof(tReplyHeader) )
4869 					{
4870 						// bail out if failed
4871 						tAgent.m_sFailure.SetSprintf ( "failed to receive reply header" );
4872 						agent_stats_inc ( tAgent, eNetworkErrors );
4873 						break;
4874 					}
4875 
4876 					tReplyHeader.m_iStatus = ntohs ( tReplyHeader.m_iStatus );
4877 					tReplyHeader.m_iVer = ntohs ( tReplyHeader.m_iVer );
4878 					tReplyHeader.m_iLength = ntohl ( tReplyHeader.m_iLength );
4879 
4880 					// check the packet
4881 					if ( tReplyHeader.m_iLength<0 || tReplyHeader.m_iLength>g_iMaxPacketSize ) // FIXME! add reasonable max packet len too
4882 					{
4883 						tAgent.m_sFailure.SetSprintf ( "invalid packet size (status=%d, len=%d, max_packet_size=%d)",
4884 							tReplyHeader.m_iStatus, tReplyHeader.m_iLength, g_iMaxPacketSize );
4885 						agent_stats_inc ( tAgent, eWrongReplies );
4886 						break;
4887 					}
4888 
4889 					// header received, switch the status
4890 					assert ( tAgent.m_pReplyBuf==NULL );
4891 					tAgent.m_eState = AGENT_REPLY;
4892 					tAgent.m_pReplyBuf = new BYTE [ tReplyHeader.m_iLength ];
4893 					tAgent.m_iReplySize = tReplyHeader.m_iLength;
4894 					tAgent.m_iReplyRead = 0;
4895 					tAgent.m_iReplyStatus = tReplyHeader.m_iStatus;
4896 
4897 					if ( !tAgent.m_pReplyBuf )
4898 					{
4899 						// bail out if failed
4900 						tAgent.m_sFailure.SetSprintf ( "failed to alloc %d bytes for reply buffer", tAgent.m_iReplySize );
4901 						break;
4902 					}
4903 				}
4904 
4905 				// if we are reading reply, read another chunk
4906 				if ( tAgent.m_eState==AGENT_REPLY )
4907 				{
4908 					// do read
4909 					assert ( tAgent.m_iReplyRead<tAgent.m_iReplySize );
4910 					int iRes = sphSockRecv ( tAgent.m_iSock, (char*)tAgent.m_pReplyBuf+tAgent.m_iReplyRead,
4911 						tAgent.m_iReplySize-tAgent.m_iReplyRead );
4912 
4913 					// bail out if read failed
4914 					if ( iRes<=0 )
4915 					{
4916 						tAgent.m_sFailure.SetSprintf ( "failed to receive reply body: %s", sphSockError() );
4917 						agent_stats_inc ( tAgent, eNetworkErrors );
4918 						break;
4919 					}
4920 
4921 					assert ( iRes>0 );
4922 					assert ( tAgent.m_iReplyRead+iRes<=tAgent.m_iReplySize );
4923 					tAgent.m_iReplyRead += iRes;
4924 				}
4925 
4926 				// if reply was fully received, parse it
4927 				if ( tAgent.m_eState==AGENT_REPLY && tAgent.m_iReplyRead==tAgent.m_iReplySize )
4928 				{
4929 					MemInputBuffer_c tReq ( tAgent.m_pReplyBuf, tAgent.m_iReplySize );
4930 
4931 					// absolve thy former sins
4932 					tAgent.m_sFailure = "";
4933 
4934 					// check for general errors/warnings first
4935 					if ( tAgent.m_iReplyStatus==SEARCHD_WARNING )
4936 					{
4937 						CSphString sAgentWarning = tReq.GetString ();
4938 						tAgent.m_sFailure.SetSprintf ( "remote warning: %s", sAgentWarning.cstr() );
4939 						bWarnings = true;
4940 
4941 					} else if ( tAgent.m_iReplyStatus==SEARCHD_RETRY )
4942 					{
4943 						tAgent.m_eState = AGENT_RETRY;
4944 						CSphString sAgentError = tReq.GetString ();
4945 						tAgent.m_sFailure.SetSprintf ( "remote warning: %s", sAgentError.cstr() );
4946 						break;
4947 
4948 					} else if ( tAgent.m_iReplyStatus!=SEARCHD_OK )
4949 					{
4950 						CSphString sAgentError = tReq.GetString ();
4951 						tAgent.m_sFailure.SetSprintf ( "remote error: %s", sAgentError.cstr() );
4952 						break;
4953 					}
4954 
4955 					// call parser
4956 					if ( !tParser.ParseReply ( tReq, tAgent ) )
4957 						break;
4958 
4959 					// check if there was enough data
4960 					if ( tReq.GetError() )
4961 					{
4962 						tAgent.m_sFailure.SetSprintf ( "incomplete reply" );
4963 						agent_stats_inc ( tAgent, eWrongReplies );
4964 						break;
4965 					}
4966 
4967 					epoll_ctl ( eid, EPOLL_CTL_DEL, tAgent.m_iSock, &dEvent );
4968 					--iEvents;
4969 					// all is well
4970 					iAgents++;
4971 					tAgent.Close ( false );
4972 					tAgent.m_bSuccess = true;
4973 				}
4974 
4975 				bFailure = false;
4976 				break;
4977 			}
4978 
4979 			if ( bFailure )
4980 			{
4981 				epoll_ctl ( eid, EPOLL_CTL_DEL, tAgent.m_iSock, &dEvent );
4982 				--iEvents;
4983 				tAgent.Close ();
4984 				tAgent.m_dResults.Reset ();
4985 			} else if ( tAgent.m_bSuccess )
4986 			{
4987 				ARRAY_FOREACH_COND ( i, tAgent.m_dResults, !bWarnings )
4988 					bWarnings = !tAgent.m_dResults[i].m_sWarning.IsEmpty();
4989 				agent_stats_inc ( tAgent, bWarnings ? eWarnings : eNoErrors );
4990 			}
4991 		}
4992 	}
4993 
4994 	close ( eid );
4995 
4996 	// close timed-out agents
4997 	ARRAY_FOREACH ( iAgent, dAgents )
4998 	{
4999 		AgentConn_t & tAgent = dAgents[iAgent];
5000 		if ( tAgent.m_bBlackhole )
5001 			tAgent.Close ();
5002 		else if ( bTimeout && ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_PREREPLY ||
5003 			( tAgent.m_eState==AGENT_REPLY && tAgent.m_iReplyRead!=tAgent.m_iReplySize ) ) )
5004 		{
5005 			assert ( !tAgent.m_dResults.GetLength() );
5006 			assert ( !tAgent.m_bSuccess );
5007 			tAgent.Fail ( eTimeoutsQuery, "query timed out" );
5008 		}
5009 	}
5010 
5011 	return iAgents;
5012 }
5013 
5014 #else // !HAVE_EPOLL
5015 
5016 // process states AGENT_CONNECTING, AGENT_HANDSHAKE, AGENT_ESTABLISHED and notes AGENT_QUERYED
5017 // called in serial order with RemoteConnectToAgents (so, the context is NOT changed during the call).
RemoteQueryAgents(AgentConnectionContext_t * pCtx)5018 int RemoteQueryAgents ( AgentConnectionContext_t * pCtx )
5019 {
5020 	assert ( pCtx->m_iTimeout>=0 );
5021 	assert ( pCtx->m_pAgents );
5022 	assert ( pCtx->m_iAgentCount );
5023 
5024 	int iAgents = 0;
5025 	int64_t tmMaxTimer = sphMicroTimer() + pCtx->m_iTimeout*1000; // in microseconds
5026 	CSphVector<int> dWorkingSet;
5027 	dWorkingSet.Reserve ( pCtx->m_iAgentCount );
5028 
5029 #if HAVE_POLL
5030 	CSphVector<struct pollfd> fds;
5031 	fds.Reserve ( pCtx->m_iAgentCount );
5032 #endif
5033 
5034 	// main connection loop
5035 	// break if a) all connects in AGENT_QUERY state, or b) timeout
5036 	for ( ;; )
5037 	{
5038 		// prepare socket sets
5039 		dWorkingSet.Reset();
5040 #if HAVE_POLL
5041 		fds.Reset();
5042 #else
5043 		int iMax = 0;
5044 		fd_set fdsRead, fdsWrite;
5045 		FD_ZERO ( &fdsRead );
5046 		FD_ZERO ( &fdsWrite );
5047 #endif
5048 
5049 		bool bDone = true;
5050 		for ( int i=0; i<pCtx->m_iAgentCount; i++ )
5051 		{
5052 			AgentConn_t & tAgent = pCtx->m_pAgents[i];
5053 			// select only 'initial' agents - which are not send query response.
5054 			if ( tAgent.m_eState<AGENT_CONNECTING || tAgent.m_eState>AGENT_QUERYED )
5055 				continue;
5056 
5057 			assert ( !tAgent.m_sPath.IsEmpty() || tAgent.m_iPort>0 );
5058 			assert ( tAgent.m_iSock>0 );
5059 			if ( tAgent.m_iSock<=0 || ( tAgent.m_sPath.IsEmpty() && tAgent.m_iPort<=0 ) )
5060 			{
5061 				tAgent.Fail ( eConnectFailures, "invalid agent in querying. Socket %d, Path %s, Port %d",
5062 					tAgent.m_iSock, tAgent.m_sPath.cstr(), tAgent.m_iPort );
5063 				tAgent.m_eState = AGENT_RETRY; // do retry on connect() failures
5064 				continue;
5065 			}
5066 
5067 			bool bWr = ( tAgent.m_eState==AGENT_CONNECTING || tAgent.m_eState==AGENT_ESTABLISHED );
5068 			dWorkingSet.Add(i);
5069 #if HAVE_POLL
5070 			pollfd& pfd = fds.Add();
5071 			pfd.fd = tAgent.m_iSock;
5072 			pfd.events = bWr ? POLLOUT : POLLIN;
5073 #else
5074 			sphFDSet ( tAgent.m_iSock, bWr ? &fdsWrite : &fdsRead );
5075 			iMax = Max ( iMax, tAgent.m_iSock );
5076 #endif
5077 			if ( tAgent.m_eState!=AGENT_QUERYED )
5078 				bDone = false;
5079 		}
5080 
5081 		if ( bDone )
5082 			break;
5083 
5084 		// compute timeout
5085 		int64_t tmSelect = sphMicroTimer();
5086 		int64_t tmMicroLeft = tmMaxTimer - tmSelect;
5087 		if ( tmMicroLeft<=0 )
5088 			break; // FIXME? what about iTimeout==0 case?
5089 
5090 		// do poll
5091 #if HAVE_POLL
5092 		int iSelected = ::poll ( fds.Begin(), fds.GetLength(), int( tmMicroLeft/1000 ) );
5093 #else
5094 		struct timeval tvTimeout;
5095 		tvTimeout.tv_sec = (int)( tmMicroLeft/ 1000000 ); // full seconds
5096 		tvTimeout.tv_usec = (int)( tmMicroLeft % 1000000 ); // microseconds
5097 		int iSelected = ::select ( 1+iMax, &fdsRead, &fdsWrite, NULL, &tvTimeout ); // exceptfds are OOB only
5098 #endif
5099 
5100 		// update counters, and loop again if nothing happened
5101 		pCtx->m_pAgents->m_iWaited += sphMicroTimer() - tmSelect;
5102 		// todo: do we need to check for EINTR here? Or the fact of timeout is enough anyway?
5103 		if ( iSelected<=0 )
5104 			continue;
5105 
5106 		// ok, something did happen, so loop the agents and do them checks
5107 		ARRAY_FOREACH ( i, dWorkingSet )
5108 		{
5109 			AgentConn_t & tAgent = pCtx->m_pAgents[dWorkingSet[i]];
5110 
5111 #if HAVE_POLL
5112 			bool bReadable = ( fds[i].revents & POLLIN )!=0;
5113 			bool bWriteable = ( fds[i].revents & POLLOUT )!=0;
5114 			bool bErr = ( ( fds[i].revents & ( POLLERR | POLLHUP ) )!=0 );
5115 #else
5116 			bool bReadable = FD_ISSET ( tAgent.m_iSock, &fdsRead )!=0;
5117 			bool bWriteable = FD_ISSET ( tAgent.m_iSock, &fdsWrite )!=0;
5118 			bool bErr = !bWriteable; // just poll and check for error
5119 #endif
5120 
5121 			if ( tAgent.m_eState==AGENT_CONNECTING && ( bWriteable || bErr ) )
5122 			{
5123 				if ( bErr )
5124 				{
5125 					// check if connection completed
5126 					// tricky part, with select, we MUST use write-set ONLY here at this check
5127 					// even though we can't tell connect() success from just OS send buffer availability
5128 					// but any check involving read-set just never ever completes, so...
5129 					int iErr = 0;
5130 					socklen_t iErrLen = sizeof(iErr);
5131 					getsockopt ( tAgent.m_iSock, SOL_SOCKET, SO_ERROR, (char*)&iErr, &iErrLen );
5132 					if ( iErr )
5133 					{
5134 						// connect() failure
5135 						tAgent.Fail ( eConnectFailures, "connect() failed: errno=%d, %s", iErr, sphSockError(iErr) );
5136 					}
5137 					continue;
5138 				}
5139 
5140 				assert ( bWriteable ); // should never get empty or readable state
5141 				// connect() success
5142 				agent_stats_inc ( tAgent, eMaxMsecs );
5143 
5144 				// send the client's proto version right now to avoid w-w-r pattern.
5145 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
5146 				tOut.SendDword ( SPHINX_CLIENT_VERSION );
5147 				tOut.Flush (); // FIXME! handle flush failure?
5148 
5149 				tAgent.m_eState = AGENT_HANDSHAKE;
5150 				continue;
5151 			}
5152 
5153 			// check if hello was received
5154 			if ( tAgent.m_eState==AGENT_HANDSHAKE && bReadable )
5155 			{
5156 				// read reply
5157 				int iRemoteVer;
5158 				int iRes = sphSockRecv ( tAgent.m_iSock, (char*)&iRemoteVer, sizeof(iRemoteVer) );
5159 				if ( iRes!=sizeof(iRemoteVer) )
5160 				{
5161 					if ( iRes<0 )
5162 					{
5163 						int iErr = sphSockGetErrno();
5164 						tAgent.Fail ( eNetworkErrors, "handshake failure (errno=%d, msg=%s)", iErr, sphSockError(iErr) );
5165 					} else if ( iRes>0 )
5166 					{
5167 						// incomplete reply
5168 						tAgent.Fail ( eWrongReplies, "handshake failure (exp=%d, recv=%d)", (int)sizeof(iRemoteVer), iRes );
5169 					} else
5170 					{
5171 						// agent closed the connection
5172 						// this might happen in out-of-sync connect-accept case; so let's retry
5173 						tAgent.Fail ( eUnexpectedClose, "handshake failure (connection was closed)" );
5174 						tAgent.m_eState = AGENT_RETRY;
5175 					}
5176 					continue;
5177 				}
5178 
5179 				iRemoteVer = ntohl ( iRemoteVer );
5180 				if (!( iRemoteVer==SPHINX_SEARCHD_PROTO || iRemoteVer==0x01000000UL ) ) // workaround for all the revisions that sent it in host order...
5181 				{
5182 					tAgent.Fail ( eWrongReplies, "handshake failure (unexpected protocol version=%d)", iRemoteVer );
5183 					continue;
5184 				}
5185 
5186 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
5187 				// check if we need to reset the persistent connection
5188 				if ( tAgent.m_bFresh && tAgent.m_bPersistent )
5189 				{
5190 					tOut.SendWord ( SEARCHD_COMMAND_PERSIST );
5191 					tOut.SendWord ( 0 ); // dummy version
5192 					tOut.SendInt ( 4 ); // request body length
5193 					tOut.SendInt ( 1 ); // set persistent to 1.
5194 					tOut.Flush ();
5195 					tAgent.m_bFresh = false;
5196 				}
5197 
5198 				tAgent.m_eState = AGENT_ESTABLISHED;
5199 				continue;
5200 			}
5201 
5202 			if ( tAgent.m_eState==AGENT_ESTABLISHED && bWriteable )
5203 			{
5204 				// send request
5205 				NetOutputBuffer_c tOut ( tAgent.m_iSock );
5206 				pCtx->m_pBuilder->BuildRequest ( tAgent, tOut );
5207 				tOut.Flush (); // FIXME! handle flush failure?
5208 				tAgent.m_eState = AGENT_QUERYED;
5209 				iAgents++;
5210 				continue;
5211 			}
5212 
5213 			// check if queried agent replied while we were querying others
5214 			if ( tAgent.m_eState==AGENT_QUERYED && bReadable )
5215 			{
5216 				// do not account agent wall time from here; agent is probably ready
5217 				tAgent.m_iWall += sphMicroTimer();
5218 				tAgent.m_eState = AGENT_PREREPLY;
5219 				continue;
5220 			}
5221 		}
5222 	}
5223 
5224 
5225 	// check if connection timed out
5226 	for ( int i=0; i<pCtx->m_iAgentCount; i++ )
5227 	{
5228 		AgentConn_t & tAgent = pCtx->m_pAgents[i];
5229 		if ( tAgent.m_eState!=AGENT_QUERYED && tAgent.m_eState!=AGENT_UNUSED && tAgent.m_eState!=AGENT_RETRY && tAgent.m_eState!=AGENT_PREREPLY
5230 			&& tAgent.m_eState!=AGENT_REPLY )
5231 		{
5232 			// technically, we can end up here via two different routes
5233 			// a) connect() never finishes in given time frame
5234 			// b) agent actually accept()s the connection but keeps silence
5235 			// however, there's no way to tell the two from each other
5236 			// so we just account both cases as connect() failure
5237 			tAgent.Fail ( eTimeoutsConnect, "connect() timed out" );
5238 			tAgent.m_eState = AGENT_RETRY; // do retry on connect() failures
5239 		}
5240 	}
5241 
5242 	return iAgents;
5243 }
5244 
5245 // processing states AGENT_QUERY, AGENT_PREREPLY and AGENT_REPLY
RemoteWaitForAgents(CSphVector<AgentConn_t> & dAgents,int iTimeout,IReplyParser_t & tParser)5246 int RemoteWaitForAgents ( CSphVector<AgentConn_t> & dAgents, int iTimeout, IReplyParser_t & tParser )
5247 {
5248 	assert ( iTimeout>=0 );
5249 
5250 	int iAgents = 0;
5251 	int64_t tmMaxTimer = sphMicroTimer() + iTimeout*1000; // in microseconds
5252 	bool bTimeout = false;
5253 
5254 	CSphVector<int> dWorkingSet;
5255 	dWorkingSet.Reserve ( dAgents.GetLength() );
5256 
5257 #if HAVE_POLL
5258 	CSphVector<struct pollfd> fds;
5259 	fds.Reserve ( dAgents.GetLength() );
5260 #endif
5261 
5262 	for ( ;; )
5263 	{
5264 		dWorkingSet.Reset();
5265 
5266 #if HAVE_POLL
5267 		fds.Reset();
5268 #else
5269 		int iMax = 0;
5270 		fd_set fdsRead;
5271 		FD_ZERO ( &fdsRead );
5272 #endif
5273 		bool bDone = true;
5274 		ARRAY_FOREACH ( iAgent, dAgents )
5275 		{
5276 			AgentConn_t & tAgent = dAgents[iAgent];
5277 			if ( tAgent.m_bBlackhole )
5278 				continue;
5279 
5280 			if ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_REPLY || tAgent.m_eState==AGENT_PREREPLY )
5281 			{
5282 				assert ( !tAgent.m_sPath.IsEmpty() || tAgent.m_iPort>0 );
5283 				assert ( tAgent.m_iSock>0 );
5284 				dWorkingSet.Add(iAgent);
5285 #if HAVE_POLL
5286 				pollfd & pfd = fds.Add();
5287 				pfd.fd = tAgent.m_iSock;
5288 				pfd.events = POLLIN;
5289 #else
5290 				sphFDSet ( tAgent.m_iSock, &fdsRead );
5291 				iMax = Max ( iMax, tAgent.m_iSock );
5292 #endif
5293 				bDone = false;
5294 			}
5295 		}
5296 
5297 		if ( bDone )
5298 			break;
5299 
5300 		int64_t tmSelect = sphMicroTimer();
5301 		int64_t tmMicroLeft = tmMaxTimer - tmSelect;
5302 		if ( tmMicroLeft<=0 ) // FIXME? what about iTimeout==0 case?
5303 		{
5304 			bTimeout = true;
5305 			break;
5306 		}
5307 
5308 #if HAVE_POLL
5309 		int iSelected = ::poll ( fds.Begin(), fds.GetLength(), int( tmMicroLeft/1000 ) );
5310 #else
5311 		struct timeval tvTimeout;
5312 		tvTimeout.tv_sec = (int)( tmMicroLeft / 1000000 ); // full seconds
5313 		tvTimeout.tv_usec = (int)( tmMicroLeft % 1000000 ); // microseconds
5314 		int iSelected = ::select ( 1+iMax, &fdsRead, NULL, NULL, &tvTimeout );
5315 #endif
5316 
5317 		dAgents.Begin()->m_iWaited += sphMicroTimer() - tmSelect;
5318 
5319 		if ( iSelected<=0 )
5320 			continue;
5321 
5322 		ARRAY_FOREACH ( i, dWorkingSet )
5323 		{
5324 			AgentConn_t & tAgent = dAgents[dWorkingSet[i]];
5325 			if ( tAgent.m_bBlackhole )
5326 				continue;
5327 			if (!( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_REPLY || tAgent.m_eState==AGENT_PREREPLY ))
5328 				continue;
5329 
5330 #if HAVE_POLL
5331 			if (!( fds[i].revents & POLLIN ))
5332 #else
5333 			if ( !FD_ISSET ( tAgent.m_iSock, &fdsRead ) )
5334 #endif
5335 				continue;
5336 
5337 			// if there was no reply yet, read reply header
5338 			bool bFailure = true;
5339 			bool bWarnings = false;
5340 			for ( ;; )
5341 			{
5342 				if ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_PREREPLY )
5343 				{
5344 					if ( tAgent.m_eState==AGENT_PREREPLY )
5345 					{
5346 						tAgent.m_iWall -= sphMicroTimer();
5347 						tAgent.m_eState = AGENT_QUERYED;
5348 					}
5349 
5350 					// try to read
5351 					struct
5352 					{
5353 						WORD	m_iStatus;
5354 						WORD	m_iVer;
5355 						int		m_iLength;
5356 					} tReplyHeader;
5357 					STATIC_SIZE_ASSERT ( tReplyHeader, 8 );
5358 
5359 					if ( sphSockRecv ( tAgent.m_iSock, (char*)&tReplyHeader, sizeof(tReplyHeader) )!=sizeof(tReplyHeader) )
5360 					{
5361 						// bail out if failed
5362 						tAgent.m_sFailure.SetSprintf ( "failed to receive reply header" );
5363 						agent_stats_inc ( tAgent, eNetworkErrors );
5364 						break;
5365 					}
5366 
5367 					tReplyHeader.m_iStatus = ntohs ( tReplyHeader.m_iStatus );
5368 					tReplyHeader.m_iVer = ntohs ( tReplyHeader.m_iVer );
5369 					tReplyHeader.m_iLength = ntohl ( tReplyHeader.m_iLength );
5370 
5371 					// check the packet
5372 					if ( tReplyHeader.m_iLength<0 || tReplyHeader.m_iLength>g_iMaxPacketSize ) // FIXME! add reasonable max packet len too
5373 					{
5374 						tAgent.m_sFailure.SetSprintf ( "invalid packet size (status=%d, len=%d, max_packet_size=%d)",
5375 							tReplyHeader.m_iStatus, tReplyHeader.m_iLength, g_iMaxPacketSize );
5376 						agent_stats_inc ( tAgent, eWrongReplies );
5377 						break;
5378 					}
5379 
5380 					// header received, switch the status
5381 					assert ( tAgent.m_pReplyBuf==NULL );
5382 					tAgent.m_eState = AGENT_REPLY;
5383 					tAgent.m_pReplyBuf = new BYTE [ tReplyHeader.m_iLength ];
5384 					tAgent.m_iReplySize = tReplyHeader.m_iLength;
5385 					tAgent.m_iReplyRead = 0;
5386 					tAgent.m_iReplyStatus = tReplyHeader.m_iStatus;
5387 
5388 					if ( !tAgent.m_pReplyBuf )
5389 					{
5390 						// bail out if failed
5391 						tAgent.m_sFailure.SetSprintf ( "failed to alloc %d bytes for reply buffer", tAgent.m_iReplySize );
5392 						break;
5393 					}
5394 				}
5395 
5396 				// if we are reading reply, read another chunk
5397 				if ( tAgent.m_eState==AGENT_REPLY )
5398 				{
5399 					// do read
5400 					assert ( tAgent.m_iReplyRead<tAgent.m_iReplySize );
5401 					int iRes = sphSockRecv ( tAgent.m_iSock, (char*)tAgent.m_pReplyBuf+tAgent.m_iReplyRead,
5402 						tAgent.m_iReplySize-tAgent.m_iReplyRead );
5403 
5404 					// bail out if read failed
5405 					if ( iRes<=0 )
5406 					{
5407 						tAgent.m_sFailure.SetSprintf ( "failed to receive reply body: %s", sphSockError() );
5408 						agent_stats_inc ( tAgent, eNetworkErrors );
5409 						break;
5410 					}
5411 
5412 					assert ( iRes>0 );
5413 					assert ( tAgent.m_iReplyRead+iRes<=tAgent.m_iReplySize );
5414 					tAgent.m_iReplyRead += iRes;
5415 				}
5416 
5417 				// if reply was fully received, parse it
5418 				if ( tAgent.m_eState==AGENT_REPLY && tAgent.m_iReplyRead==tAgent.m_iReplySize )
5419 				{
5420 					MemInputBuffer_c tReq ( tAgent.m_pReplyBuf, tAgent.m_iReplySize );
5421 
5422 					// absolve thy former sins
5423 					tAgent.m_sFailure = "";
5424 
5425 					// check for general errors/warnings first
5426 					if ( tAgent.m_iReplyStatus==SEARCHD_WARNING )
5427 					{
5428 						CSphString sAgentWarning = tReq.GetString ();
5429 						tAgent.m_sFailure.SetSprintf ( "remote warning: %s", sAgentWarning.cstr() );
5430 						bWarnings = true;
5431 
5432 					} else if ( tAgent.m_iReplyStatus==SEARCHD_RETRY )
5433 					{
5434 						tAgent.m_eState = AGENT_RETRY;
5435 						CSphString sAgentError = tReq.GetString ();
5436 						tAgent.m_sFailure.SetSprintf ( "remote warning: %s", sAgentError.cstr() );
5437 						break;
5438 
5439 					} else if ( tAgent.m_iReplyStatus!=SEARCHD_OK )
5440 					{
5441 						CSphString sAgentError = tReq.GetString ();
5442 						tAgent.m_sFailure.SetSprintf ( "remote error: %s", sAgentError.cstr() );
5443 						break;
5444 					}
5445 
5446 					// call parser
5447 					if ( !tParser.ParseReply ( tReq, tAgent ) )
5448 						break;
5449 
5450 					// check if there was enough data
5451 					if ( tReq.GetError() )
5452 					{
5453 						tAgent.m_sFailure.SetSprintf ( "incomplete reply" );
5454 						agent_stats_inc ( tAgent, eWrongReplies );
5455 						break;
5456 					}
5457 
5458 					// all is well
5459 					iAgents++;
5460 					tAgent.Close ( false );
5461 					tAgent.m_bSuccess = true;
5462 				}
5463 
5464 				bFailure = false;
5465 				break;
5466 			}
5467 
5468 			if ( bFailure )
5469 			{
5470 				tAgent.Close ();
5471 				tAgent.m_dResults.Reset ();
5472 			} else if ( tAgent.m_bSuccess )
5473 			{
5474 				ARRAY_FOREACH_COND ( i, tAgent.m_dResults, !bWarnings )
5475 					bWarnings = !tAgent.m_dResults[i].m_sWarning.IsEmpty();
5476 				agent_stats_inc ( tAgent, bWarnings ? eWarnings : eNoErrors );
5477 			}
5478 		}
5479 	}
5480 
5481 	// close timed-out agents
5482 	ARRAY_FOREACH ( iAgent, dAgents )
5483 	{
5484 		AgentConn_t & tAgent = dAgents[iAgent];
5485 		if ( tAgent.m_bBlackhole )
5486 			tAgent.Close ();
5487 		else if ( bTimeout && ( tAgent.m_eState==AGENT_QUERYED || tAgent.m_eState==AGENT_PREREPLY ||
5488 				( tAgent.m_eState==AGENT_REPLY && tAgent.m_iReplyRead!=tAgent.m_iReplySize ) ) )
5489 		{
5490 			assert ( !tAgent.m_dResults.GetLength() );
5491 			assert ( !tAgent.m_bSuccess );
5492 			tAgent.Fail ( eTimeoutsQuery, "query timed out" );
5493 		}
5494 	}
5495 
5496 	return iAgents;
5497 }
5498 
5499 #endif // HAVE_EPOLL
5500 
5501 struct AgentWorkContext_t;
5502 typedef void ( *ThdWorker_fn ) ( AgentWorkContext_t * );
5503 
5504 struct AgentWorkContext_t : public AgentConnectionContext_t
5505 {
5506 	ThdWorker_fn	m_pfn;			///< work functor & flag of dummy element
5507 	int64_t			m_tmWait;
5508 	int				m_iRetries;
5509 	int				m_iAgentsDone;
5510 
AgentWorkContext_tAgentWorkContext_t5511 	AgentWorkContext_t ()
5512 		: m_pfn ( NULL )
5513 		, m_tmWait ( 0 )
5514 		, m_iRetries ( 0 )
5515 		, m_iAgentsDone ( 0 )
5516 	{
5517 	}
5518 };
5519 
5520 class ThdWorkPool_t : ISphNoncopyable
5521 {
5522 private:
5523 	CSphMutex m_tDataLock;
5524 	CSphMutex m_tStatLock;
5525 public:
5526 	CSphAutoEvent m_tChanged;
5527 private:
5528 	AgentWorkContext_t * m_dData;	// works array
5529 	int m_iLen;
5530 
5531 	int m_iHead;					// ring buffer begin
5532 	int m_iTail;					// ring buffer end
5533 
5534 	volatile int m_iWorksCount;			// count of works to be done
5535 	volatile int m_iAgentsDone;				// count of agents that finished their works
5536 	volatile int m_iAgentsReported;			// count of agents that reported of their work done
5537 
5538 	CrashQuery_t m_tCrashQuery;		// query that got reported on crash
5539 
5540 public:
ThdWorkPool_t(int iLen)5541 	explicit ThdWorkPool_t ( int iLen )
5542 	{
5543 		m_tCrashQuery = SphCrashLogger_c::GetQuery(); // transfer query info for crash logger to new thread
5544 
5545 		m_iLen = iLen+1;
5546 		m_iTail = m_iHead = 0;
5547 		m_iWorksCount = 0;
5548 		m_iAgentsDone = m_iAgentsReported = 0;
5549 
5550 		m_dData = new AgentWorkContext_t[m_iLen];
5551 #ifndef NDEBUG
5552 		for ( int i=0; i<m_iLen; i++ ) // to make sure that we don't rewrite valid elements
5553 			m_dData[i] = AgentWorkContext_t();
5554 #endif
5555 
5556 		m_tDataLock.Init();
5557 		m_tStatLock.Init();
5558 		m_tChanged.Init ( &m_tStatLock );
5559 	}
5560 
~ThdWorkPool_t()5561 	~ThdWorkPool_t ()
5562 	{
5563 		m_tChanged.Done();
5564 		m_tStatLock.Done();
5565 		m_tDataLock.Done();
5566 		SafeDeleteArray ( m_dData );
5567 	}
5568 
Pop()5569 	AgentWorkContext_t Pop()
5570 	{
5571 		AgentWorkContext_t tRes;
5572 		if ( m_iTail==m_iHead ) // quick path for empty pool
5573 			return tRes;
5574 
5575 		CSphScopedLock<CSphMutex> tData ( m_tDataLock ); // lock on create, unlock on destroy
5576 
5577 		if ( m_iTail==m_iHead ) // it might be empty now as another thread could steal work till that moment
5578 			return tRes;
5579 
5580 		tRes = m_dData[m_iHead];
5581 		assert ( tRes.m_pfn );
5582 #ifndef NDEBUG
5583 		m_dData[m_iHead] = AgentWorkContext_t(); // to make sure that we don't rewrite valid elements
5584 #endif
5585 		m_iHead = ( m_iHead+1 ) % m_iLen;
5586 
5587 		return tRes;
5588 	}
5589 
Push(const AgentWorkContext_t & tElem)5590 	void Push ( const AgentWorkContext_t & tElem )
5591 	{
5592 		if ( !tElem.m_pfn )
5593 			return;
5594 
5595 		m_tDataLock.Lock();
5596 		RawPush ( tElem );
5597 		m_tDataLock.Unlock();
5598 	}
5599 
RawPush(const AgentWorkContext_t & tElem)5600 	void RawPush ( const AgentWorkContext_t & tElem )
5601 	{
5602 		assert ( !m_dData[m_iTail].m_pfn ); // to make sure that we don't rewrite valid elements
5603 		m_dData[m_iTail] = tElem;
5604 		m_iTail = ( m_iTail+1 ) % m_iLen;
5605 	}
5606 
GetReadyCount()5607 	int GetReadyCount ()
5608 	{
5609 		// it could be better to lock here to get accurate value of m_iAgentsDone
5610 		// however that make lock contention of 1 sec on 1000 query ~ total 3.2sec vs 2.2 sec ( trunk )
5611 		int iAgentsDone = m_iAgentsDone;
5612 		int iNowDone = iAgentsDone - m_iAgentsReported;
5613 		m_iAgentsReported = iAgentsDone;
5614 		return iNowDone;
5615 	}
5616 
GetReadyTotal()5617 	int GetReadyTotal ()
5618 	{
5619 		return m_iAgentsDone;
5620 	}
5621 
HasIncompleteWorks() const5622 	bool HasIncompleteWorks () const
5623 	{
5624 		return ( m_iWorksCount>0 );
5625 	}
5626 
SetWorksCount(int iWorkers)5627 	void SetWorksCount ( int iWorkers )
5628 	{
5629 		m_iWorksCount = iWorkers;
5630 	}
5631 
PoolThreadFunc(void * pArg)5632 	static void PoolThreadFunc ( void * pArg )
5633 	{
5634 		ThdWorkPool_t * pPool = (ThdWorkPool_t *)pArg;
5635 
5636 		SphCrashLogger_c::SetLastQuery ( pPool->m_tCrashQuery );
5637 
5638 		int iSpinCount = 0;
5639 		int iPopCount = 0;
5640 		AgentWorkContext_t tNext;
5641 		for ( ;; )
5642 		{
5643 			if ( !tNext.m_pfn ) // pop new work if current is done
5644 			{
5645 				iSpinCount = 0;
5646 				++iPopCount;
5647 				tNext = pPool->Pop();
5648 				if ( !tNext.m_pfn ) // if there is no work at queue - worker done
5649 					break;
5650 			}
5651 
5652 			tNext.m_pfn ( &tNext );
5653 			if ( tNext.m_iAgentsDone || !tNext.m_pfn )
5654 			{
5655 				CSphScopedLock<CSphMutex> tStat ( pPool->m_tStatLock );
5656 				pPool->m_iAgentsDone += tNext.m_iAgentsDone;
5657 				pPool->m_iWorksCount -= ( tNext.m_pfn==NULL );
5658 				pPool->m_tChanged.SetEvent();
5659 			}
5660 
5661 			iSpinCount++;
5662 			if ( iSpinCount>1 && iSpinCount<4 ) // it could be better not to do the same work
5663 			{
5664 				pPool->Push ( tNext );
5665 				tNext = AgentWorkContext_t();
5666 			} else if ( pPool->m_iWorksCount>1 && iPopCount>pPool->m_iWorksCount ) // should sleep on queue wrap
5667 			{
5668 				iPopCount = 0;
5669 				sphSleepMsec ( 1 );
5670 			}
5671 		}
5672 	}
5673 };
5674 
5675 
5676 void ThdWorkParallel ( AgentWorkContext_t * );
ThdWorkWait(AgentWorkContext_t * pCtx)5677 void ThdWorkWait ( AgentWorkContext_t * pCtx )
5678 {
5679 	pCtx->m_pfn = ( pCtx->m_tmWait<sphMicroTimer() ) ? ThdWorkWait : ThdWorkParallel;
5680 }
5681 
SetNextRetry(AgentWorkContext_t * pCtx)5682 void SetNextRetry ( AgentWorkContext_t * pCtx )
5683 {
5684 	pCtx->m_pfn = NULL;
5685 	pCtx->m_pAgents->m_eState = AGENT_UNUSED;
5686 
5687 	if ( !pCtx->m_iRetriesMax || !pCtx->m_iDelay || pCtx->m_iRetries>pCtx->m_iRetriesMax )
5688 		return;
5689 
5690 	int64_t tmNextTry = sphMicroTimer() + pCtx->m_iDelay*1000;
5691 	pCtx->m_pfn = ThdWorkWait;
5692 	pCtx->m_iRetries++;
5693 	pCtx->m_tmWait = tmNextTry;
5694 	pCtx->m_pAgents->m_eState = AGENT_RETRY;
5695 }
5696 
ThdWorkParallel(AgentWorkContext_t * pCtx)5697 void ThdWorkParallel ( AgentWorkContext_t * pCtx )
5698 {
5699 	RemoteConnectToAgent ( *pCtx->m_pAgents );
5700 	if ( pCtx->m_pAgents->m_eState==AGENT_UNUSED )
5701 	{
5702 		SetNextRetry ( pCtx );
5703 		return;
5704 	}
5705 
5706 	RemoteQueryAgents ( pCtx );
5707 	if ( pCtx->m_pAgents->m_eState==AGENT_RETRY ) // next round of connect try
5708 	{
5709 		SetNextRetry ( pCtx );
5710 	} else
5711 	{
5712 		pCtx->m_pfn = NULL;
5713 		pCtx->m_iAgentsDone = 1;
5714 	}
5715 }
5716 
5717 
ThdWorkSequental(AgentWorkContext_t * pCtx)5718 void ThdWorkSequental ( AgentWorkContext_t * pCtx )
5719 {
5720 	if ( pCtx->m_iRetries )
5721 		sphSleepMsec ( pCtx->m_iDelay );
5722 
5723 	for ( int iAgent=0; iAgent<pCtx->m_iAgentCount; iAgent++ )
5724 	{
5725 		AgentConn_t & tAgent = pCtx->m_pAgents[iAgent];
5726 		if ( !pCtx->m_iRetries || tAgent.m_eState==AGENT_RETRY )
5727 		{
5728 			RemoteConnectToAgent ( tAgent );
5729 		}
5730 	}
5731 
5732 	pCtx->m_iAgentsDone += RemoteQueryAgents ( pCtx );
5733 
5734 	int iToRetry = 0;
5735 	for ( int i=0; i<pCtx->m_iAgentCount; i++ )
5736 		iToRetry += ( pCtx->m_pAgents[i].m_eState==AGENT_RETRY );
5737 
5738 	if ( iToRetry )
5739 		pCtx->m_iRetries++;
5740 
5741 	pCtx->m_pfn = NULL;
5742 	if ( iToRetry && pCtx->m_iRetriesMax && pCtx->m_iRetries<=pCtx->m_iRetriesMax )
5743 		pCtx->m_pfn = ThdWorkSequental;
5744 }
5745 
5746 
5747 class CSphRemoteAgentsController : ISphNoncopyable
5748 {
5749 public:
CSphRemoteAgentsController(int iThreads,CSphVector<AgentConn_t> & dAgents,const IRequestBuilder_t & tBuilder,int iTimeout,int iRetryMax=0,int iDelay=0)5750 	CSphRemoteAgentsController ( int iThreads, CSphVector<AgentConn_t> & dAgents,
5751 		const IRequestBuilder_t & tBuilder, int iTimeout, int iRetryMax=0, int iDelay=0 )
5752 		: m_tWorkerPool ( dAgents.GetLength() )
5753 	{
5754 		assert ( dAgents.GetLength() );
5755 
5756 		iThreads = Max ( 1, Min ( iThreads, dAgents.GetLength() ) );
5757 		m_dThds.Resize ( iThreads );
5758 
5759 		AgentWorkContext_t tCtx;
5760 		tCtx.m_pBuilder = &tBuilder;
5761 		tCtx.m_iAgentCount = 1;
5762 		tCtx.m_pfn = ThdWorkParallel;
5763 		tCtx.m_iDelay = iDelay;
5764 		tCtx.m_iRetriesMax = iRetryMax;
5765 		tCtx.m_iTimeout = iTimeout;
5766 
5767 
5768 		if ( iThreads>1 )
5769 		{
5770 			m_tWorkerPool.SetWorksCount ( dAgents.GetLength() );
5771 			ARRAY_FOREACH ( i, dAgents )
5772 			{
5773 				tCtx.m_pAgents = dAgents.Begin()+i;
5774 				m_tWorkerPool.RawPush ( tCtx );
5775 			}
5776 		} else
5777 		{
5778 			m_tWorkerPool.SetWorksCount ( 1 );
5779 			tCtx.m_pAgents = dAgents.Begin();
5780 			tCtx.m_iAgentCount = dAgents.GetLength();
5781 			tCtx.m_pfn = ThdWorkSequental;
5782 			m_tWorkerPool.RawPush ( tCtx );
5783 		}
5784 
5785 		ARRAY_FOREACH ( i, m_dThds )
5786 			SphCrashLogger_c::ThreadCreate ( m_dThds.Begin()+i, ThdWorkPool_t::PoolThreadFunc, &m_tWorkerPool );
5787 	}
5788 
~CSphRemoteAgentsController()5789 	~CSphRemoteAgentsController ()
5790 	{
5791 		ARRAY_FOREACH ( i, m_dThds )
5792 			sphThreadJoin ( m_dThds.Begin()+i );
5793 
5794 		m_dThds.Resize ( 0 );
5795 	}
5796 
5797 	// check that there are no works to do
IsDone()5798 	bool IsDone ()
5799 	{
5800 		return m_tWorkerPool.HasIncompleteWorks()==0;
5801 	}
5802 
5803 	// block execution while there are works to do
Finish()5804 	int Finish ()
5805 	{
5806 		while ( !IsDone() )
5807 			WaitAgentsEvent();
5808 
5809 		return m_tWorkerPool.GetReadyTotal();
5810 	}
5811 
5812 	// check that some agents are done at this iteration
HasReadyAgents()5813 	bool HasReadyAgents ()
5814 	{
5815 		return ( m_tWorkerPool.GetReadyCount()>0 );
5816 	}
WaitAgentsEvent()5817 	void WaitAgentsEvent ()
5818 	{
5819 		m_tWorkerPool.m_tChanged.WaitEvent();
5820 	}
5821 
5822 private:
5823 	ThdWorkPool_t m_tWorkerPool;
5824 	CSphVector<SphThread_t> m_dThds;
5825 };
5826 
5827 
5828 /////////////////////////////////////////////////////////////////////////////
5829 // SEARCH HANDLER
5830 /////////////////////////////////////////////////////////////////////////////
5831 
5832 struct SearchRequestBuilder_t : public IRequestBuilder_t
5833 {
SearchRequestBuilder_tSearchRequestBuilder_t5834 	SearchRequestBuilder_t ( const CSphVector<CSphQuery> & dQueries, int iStart, int iEnd, int iDivideLimits )
5835 		: m_dQueries ( dQueries ), m_iStart ( iStart ), m_iEnd ( iEnd ), m_iDivideLimits ( iDivideLimits )
5836 	{}
5837 
5838 	virtual void		BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const;
5839 
5840 protected:
5841 	int					CalcQueryLen ( const char * sIndexes, const CSphQuery & q, bool bAgentWeight ) const;
5842 	void				SendQuery ( const char * sIndexes, NetOutputBuffer_c & tOut, const CSphQuery & q, bool bAgentWeight, int iWeight ) const;
5843 
5844 protected:
5845 	const CSphVector<CSphQuery> &		m_dQueries;
5846 	const int							m_iStart;
5847 	const int							m_iEnd;
5848 	const int							m_iDivideLimits;
5849 };
5850 
5851 
5852 struct SearchReplyParser_t : public IReplyParser_t, public ISphNoncopyable
5853 {
SearchReplyParser_tSearchReplyParser_t5854 	SearchReplyParser_t ( int iStart, int iEnd, CSphVector<DWORD> & dMvaStorage, CSphVector<BYTE> & dStringsStorage )
5855 		: m_iStart ( iStart )
5856 		, m_iEnd ( iEnd )
5857 		, m_dMvaStorage ( dMvaStorage )
5858 		, m_dStringsStorage ( dStringsStorage )
5859 	{}
5860 
5861 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & tAgent ) const;
5862 
5863 protected:
5864 	int					m_iStart;
5865 	int					m_iEnd;
5866 	CSphVector<DWORD> &	m_dMvaStorage;
5867 	CSphVector<BYTE> &	m_dStringsStorage;
5868 };
5869 
5870 /////////////////////////////////////////////////////////////////////////////
5871 
CalcQueryLen(const char * sIndexes,const CSphQuery & q,bool bAgentWeight) const5872 int SearchRequestBuilder_t::CalcQueryLen ( const char * sIndexes, const CSphQuery & q, bool bAgentWeight ) const
5873 {
5874 	int iReqSize = 156 + 2*sizeof(SphDocID_t) + 4*q.m_iWeights
5875 		+ q.m_sSortBy.Length()
5876 		+ strlen ( sIndexes )
5877 		+ q.m_sGroupBy.Length()
5878 		+ q.m_sGroupSortBy.Length()
5879 		+ q.m_sGroupDistinct.Length()
5880 		+ q.m_sComment.Length()
5881 		+ q.m_sSelect.Length()
5882 		+ q.m_sOuterOrderBy.Length()
5883 		+ q.m_sUDRanker.Length()
5884 		+ q.m_sUDRankerOpts.Length()
5885 		+ q.m_sQueryTokenFilterLib.Length()
5886 		+ q.m_sQueryTokenFilterName.Length()
5887 		+ q.m_sQueryTokenFilterOpts.Length();
5888 	iReqSize += q.m_sRawQuery.IsEmpty()
5889 		? q.m_sQuery.Length()
5890 		: q.m_sRawQuery.Length();
5891 	if ( q.m_eRanker==SPH_RANK_EXPR || q.m_eRanker==SPH_RANK_EXPORT )
5892 		iReqSize += q.m_sRankerExpr.Length() + 4;
5893 	ARRAY_FOREACH ( j, q.m_dFilters )
5894 	{
5895 		const CSphFilterSettings & tFilter = q.m_dFilters[j];
5896 		iReqSize += 20 + tFilter.m_sAttrName.Length(); // string attr-name; int type; int exclude-flag; int equal-flag
5897 		switch ( tFilter.m_eType )
5898 		{
5899 			case SPH_FILTER_VALUES:		iReqSize += 4 + 8*tFilter.GetNumValues (); break; // int values-count; uint64[] values
5900 			case SPH_FILTER_RANGE:		iReqSize += 16; break; // uint64 min-val, max-val
5901 			case SPH_FILTER_FLOATRANGE:	iReqSize += 8; break; // int/float min-val,max-val
5902 			case SPH_FILTER_USERVAR:
5903 			case SPH_FILTER_STRING:		iReqSize += 4 + ( tFilter.m_dStrings.GetLength()==1 ? tFilter.m_dStrings[0].Length() : 0 ); break;
5904 			case SPH_FILTER_NULL:		iReqSize += 1; break; // boolean value
5905 			case SPH_FILTER_STRING_LIST:	// int values-count; string[] values
5906 				iReqSize += 4;
5907 				ARRAY_FOREACH ( iString, tFilter.m_dStrings )
5908 					iReqSize += 4 + tFilter.m_dStrings[iString].Length();
5909 				break;
5910 		}
5911 	}
5912 	if ( q.m_bGeoAnchor )
5913 		iReqSize += 16 + q.m_sGeoLatAttr.Length() + q.m_sGeoLongAttr.Length(); // string lat-attr, long-attr; float lat, long
5914 	if ( bAgentWeight )
5915 	{
5916 		iReqSize += 9; // "*" (length=1) + length itself + weight
5917 	} else
5918 	{
5919 		ARRAY_FOREACH ( i, q.m_dIndexWeights )
5920 			iReqSize += 8 + q.m_dIndexWeights[i].m_sName.Length(); // string index-name; int index-weight
5921 	}
5922 	ARRAY_FOREACH ( i, q.m_dFieldWeights )
5923 		iReqSize += 8 + q.m_dFieldWeights[i].m_sName.Length(); // string field-name; int field-weight
5924 	ARRAY_FOREACH ( i, q.m_dOverrides )
5925 		iReqSize += 12 + q.m_dOverrides[i].m_sAttr.Length() + // string attr-name; int type; int values-count
5926 			( q.m_dOverrides[i].m_eAttrType==SPH_ATTR_BIGINT ? 16 : 12 )*q.m_dOverrides[i].m_dValues.GetLength(); // ( bigint id; int/float/bigint value )[] values
5927 	if ( q.m_bHasOuter )
5928 		iReqSize += 4; // outer limit
5929 	if ( q.m_iMaxPredictedMsec>0 )
5930 		iReqSize += 4;
5931 	return iReqSize;
5932 }
5933 
5934 
5935 /// qflag means Query Flag
5936 /// names are internal to searchd and may be changed for clarity
5937 /// values are communicated over network between searchds and APIs and MUST NOT CHANGE
5938 enum
5939 {
5940 	QFLAG_REVERSE_SCAN			= 1UL << 0,
5941 	QFLAG_SORT_KBUFFER			= 1UL << 1,
5942 	QFLAG_MAX_PREDICTED_TIME	= 1UL << 2,
5943 	QFLAG_SIMPLIFY				= 1UL << 3,
5944 	QFLAG_PLAIN_IDF				= 1UL << 4,
5945 	QFLAG_GLOBAL_IDF			= 1UL << 5,
5946 	QFLAG_NORMALIZED_TF			= 1UL << 6,
5947 	QFLAG_LOCAL_DF				= 1UL << 7
5948 };
5949 
SendQuery(const char * sIndexes,NetOutputBuffer_c & tOut,const CSphQuery & q,bool bAgentWeight,int iWeight) const5950 void SearchRequestBuilder_t::SendQuery ( const char * sIndexes, NetOutputBuffer_c & tOut, const CSphQuery & q, bool bAgentWeight, int iWeight ) const
5951 {
5952 	// starting with command version 1.27, flags go first
5953 	// reason being, i might add flags that affect *any* of the subsequent data (eg. qflag_pack_ints)
5954 	DWORD uFlags = 0;
5955 	uFlags |= QFLAG_REVERSE_SCAN * q.m_bReverseScan;
5956 	uFlags |= QFLAG_SORT_KBUFFER * q.m_bSortKbuffer;
5957 	uFlags |= QFLAG_MAX_PREDICTED_TIME * ( q.m_iMaxPredictedMsec > 0 );
5958 	uFlags |= QFLAG_SIMPLIFY * q.m_bSimplify;
5959 	uFlags |= QFLAG_PLAIN_IDF * q.m_bPlainIDF;
5960 	uFlags |= QFLAG_GLOBAL_IDF * q.m_bGlobalIDF;
5961 	uFlags |= QFLAG_NORMALIZED_TF * q.m_bNormalizedTFIDF;
5962 	uFlags |= QFLAG_LOCAL_DF * q.m_bLocalDF;
5963 	tOut.SendDword ( uFlags );
5964 
5965 	// The Search Legacy
5966 	tOut.SendInt ( 0 ); // offset is 0
5967 	if ( !q.m_bHasOuter )
5968 	{
5969 		if ( m_iDivideLimits==1 )
5970 			tOut.SendInt ( q.m_iMaxMatches ); // OPTIMIZE? normally, agent limit is max_matches, even if master limit is less
5971 		else // FIXME!!! that is broken with offset + limit
5972 			tOut.SendInt ( 1 + ( ( q.m_iOffset + q.m_iLimit )/m_iDivideLimits) );
5973 	} else
5974 	{
5975 		// with outer order by, inner limit must match between agent and master
5976 		tOut.SendInt ( q.m_iLimit );
5977 	}
5978 	tOut.SendInt ( (DWORD)q.m_eMode ); // match mode
5979 	tOut.SendInt ( (DWORD)q.m_eRanker ); // ranking mode
5980 	if ( q.m_eRanker==SPH_RANK_EXPR || q.m_eRanker==SPH_RANK_EXPORT )
5981 		tOut.SendString ( q.m_sRankerExpr.cstr() );
5982 	tOut.SendInt ( q.m_eSort ); // sort mode
5983 	tOut.SendString ( q.m_sSortBy.cstr() ); // sort attr
5984 	if ( q.m_sRawQuery.IsEmpty() )
5985 		tOut.SendString ( q.m_sQuery.cstr() );
5986 	else
5987 		tOut.SendString ( q.m_sRawQuery.cstr() ); // query
5988 	tOut.SendInt ( q.m_iWeights );
5989 	for ( int j=0; j<q.m_iWeights; j++ )
5990 		tOut.SendInt ( q.m_pWeights[j] ); // weights
5991 	tOut.SendString ( sIndexes ); // indexes
5992 	tOut.SendInt ( USE_64BIT ); // id range bits
5993 	tOut.SendDocid ( 0 ); // default full id range (any client range must be in filters at this stage)
5994 	tOut.SendDocid ( DOCID_MAX );
5995 	tOut.SendInt ( q.m_dFilters.GetLength() );
5996 	ARRAY_FOREACH ( j, q.m_dFilters )
5997 	{
5998 		const CSphFilterSettings & tFilter = q.m_dFilters[j];
5999 		tOut.SendString ( tFilter.m_sAttrName.cstr() );
6000 		tOut.SendInt ( tFilter.m_eType );
6001 		switch ( tFilter.m_eType )
6002 		{
6003 			case SPH_FILTER_VALUES:
6004 				tOut.SendInt ( tFilter.GetNumValues () );
6005 				for ( int k = 0; k < tFilter.GetNumValues (); k++ )
6006 					tOut.SendUint64 ( tFilter.GetValue ( k ) );
6007 				break;
6008 
6009 			case SPH_FILTER_RANGE:
6010 				tOut.SendUint64 ( tFilter.m_iMinValue );
6011 				tOut.SendUint64 ( tFilter.m_iMaxValue );
6012 				break;
6013 
6014 			case SPH_FILTER_FLOATRANGE:
6015 				tOut.SendFloat ( tFilter.m_fMinValue );
6016 				tOut.SendFloat ( tFilter.m_fMaxValue );
6017 				break;
6018 
6019 			case SPH_FILTER_USERVAR:
6020 			case SPH_FILTER_STRING:
6021 				tOut.SendString ( tFilter.m_dStrings.GetLength()==1 ? tFilter.m_dStrings[0].cstr() : NULL );
6022 				break;
6023 
6024 			case SPH_FILTER_NULL:
6025 				tOut.SendByte ( tFilter.m_bHasEqual );
6026 				break;
6027 
6028 			case SPH_FILTER_STRING_LIST:
6029 				tOut.SendInt ( tFilter.m_dStrings.GetLength() );
6030 				ARRAY_FOREACH ( iString, tFilter.m_dStrings )
6031 					tOut.SendString ( tFilter.m_dStrings[iString].cstr() );
6032 				break;
6033 		}
6034 		tOut.SendInt ( tFilter.m_bExclude );
6035 		tOut.SendInt ( tFilter.m_bHasEqual );
6036 		tOut.SendInt ( 0 ); // back-port of master v13 git:edba5ab93a7313684525ed22eb2df7072c333d71
6037 	}
6038 	tOut.SendInt ( q.m_eGroupFunc );
6039 	tOut.SendString ( q.m_sGroupBy.cstr() );
6040 	if ( m_iDivideLimits==1 )
6041 		tOut.SendInt ( q.m_iMaxMatches );
6042 	else
6043 		tOut.SendInt ( 1+(q.m_iMaxMatches/m_iDivideLimits) ); // Reduce the max_matches also.
6044 	tOut.SendString ( q.m_sGroupSortBy.cstr() );
6045 	tOut.SendInt ( q.m_iCutoff );
6046 	tOut.SendInt ( q.m_iRetryCount );
6047 	tOut.SendInt ( q.m_iRetryDelay );
6048 	tOut.SendString ( q.m_sGroupDistinct.cstr() );
6049 	tOut.SendInt ( q.m_bGeoAnchor );
6050 	if ( q.m_bGeoAnchor )
6051 	{
6052 		tOut.SendString ( q.m_sGeoLatAttr.cstr() );
6053 		tOut.SendString ( q.m_sGeoLongAttr.cstr() );
6054 		tOut.SendFloat ( q.m_fGeoLatitude );
6055 		tOut.SendFloat ( q.m_fGeoLongitude );
6056 	}
6057 	if ( bAgentWeight )
6058 	{
6059 		tOut.SendInt ( 1 );
6060 		tOut.SendString ( "*" );
6061 		tOut.SendInt ( iWeight );
6062 	} else
6063 	{
6064 		tOut.SendInt ( q.m_dIndexWeights.GetLength() );
6065 		ARRAY_FOREACH ( i, q.m_dIndexWeights )
6066 		{
6067 			tOut.SendString ( q.m_dIndexWeights[i].m_sName.cstr() );
6068 			tOut.SendInt ( q.m_dIndexWeights[i].m_iValue );
6069 		}
6070 	}
6071 	tOut.SendDword ( q.m_uMaxQueryMsec );
6072 	tOut.SendInt ( q.m_dFieldWeights.GetLength() );
6073 	ARRAY_FOREACH ( i, q.m_dFieldWeights )
6074 	{
6075 		tOut.SendString ( q.m_dFieldWeights[i].m_sName.cstr() );
6076 		tOut.SendInt ( q.m_dFieldWeights[i].m_iValue );
6077 	}
6078 	tOut.SendString ( q.m_sComment.cstr() );
6079 	tOut.SendInt ( q.m_dOverrides.GetLength() );
6080 	ARRAY_FOREACH ( i, q.m_dOverrides )
6081 	{
6082 		const CSphAttrOverride & tEntry = q.m_dOverrides[i];
6083 		tOut.SendString ( tEntry.m_sAttr.cstr() );
6084 		tOut.SendDword ( tEntry.m_eAttrType );
6085 		tOut.SendInt ( tEntry.m_dValues.GetLength() );
6086 		ARRAY_FOREACH ( j, tEntry.m_dValues )
6087 		{
6088 			tOut.SendUint64 ( tEntry.m_dValues[j].m_uDocID );
6089 			switch ( tEntry.m_eAttrType )
6090 			{
6091 				case SPH_ATTR_FLOAT:	tOut.SendFloat ( tEntry.m_dValues[j].m_fValue ); break;
6092 				case SPH_ATTR_BIGINT:	tOut.SendUint64 ( tEntry.m_dValues[j].m_uValue ); break;
6093 				default:				tOut.SendDword ( (DWORD)tEntry.m_dValues[j].m_uValue ); break;
6094 			}
6095 		}
6096 	}
6097 	tOut.SendString ( q.m_sSelect.cstr() );
6098 	if ( q.m_iMaxPredictedMsec>0 )
6099 		tOut.SendInt ( q.m_iMaxPredictedMsec );
6100 
6101 	// emulate empty sud-select for agent (client ver 1.29) as master sends fixed outer offset+limits
6102 	tOut.SendString ( NULL );
6103 	tOut.SendInt ( 0 );
6104 	tOut.SendInt ( 0 );
6105 	tOut.SendInt ( q.m_bHasOuter );
6106 
6107 	// master-agent extensions
6108 	tOut.SendDword ( q.m_eCollation ); // v.1
6109 	tOut.SendString ( q.m_sOuterOrderBy.cstr() ); // v.2
6110 	if ( q.m_bHasOuter )
6111 		tOut.SendInt ( q.m_iOuterOffset + q.m_iOuterLimit );
6112 	tOut.SendInt ( q.m_iGroupbyLimit );
6113 	tOut.SendString ( q.m_sUDRanker.cstr() );
6114 	tOut.SendString ( q.m_sUDRankerOpts.cstr() );
6115 	tOut.SendString ( q.m_sQueryTokenFilterLib.cstr() );
6116 	tOut.SendString ( q.m_sQueryTokenFilterName.cstr() );
6117 	tOut.SendString ( q.m_sQueryTokenFilterOpts.cstr() );
6118 }
6119 
6120 
BuildRequest(AgentConn_t & tAgent,NetOutputBuffer_c & tOut) const6121 void SearchRequestBuilder_t::BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const
6122 {
6123 	const char* sIndexes = tAgent.m_sIndexes.cstr();
6124 	bool bAgentWeigth = ( tAgent.m_iWeight!=-1 );
6125 	int iReqLen = 8; // int num-queries
6126 	for ( int i=m_iStart; i<=m_iEnd; i++ )
6127 		iReqLen += CalcQueryLen ( sIndexes, m_dQueries[i], bAgentWeigth );
6128 
6129 	tOut.SendWord ( SEARCHD_COMMAND_SEARCH ); // command id
6130 	tOut.SendWord ( VER_COMMAND_SEARCH ); // command version
6131 	tOut.SendInt ( iReqLen ); // request body length
6132 
6133 	tOut.SendInt ( VER_MASTER );
6134 	tOut.SendInt ( m_iEnd-m_iStart+1 );
6135 	for ( int i=m_iStart; i<=m_iEnd; i++ )
6136 		SendQuery ( sIndexes, tOut, m_dQueries[i], bAgentWeigth, tAgent.m_iWeight );
6137 }
6138 
6139 /////////////////////////////////////////////////////////////////////////////
6140 
ParseReply(MemInputBuffer_c & tReq,AgentConn_t & tAgent) const6141 bool SearchReplyParser_t::ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & tAgent ) const
6142 {
6143 	int iResults = m_iEnd-m_iStart+1;
6144 	assert ( iResults>0 );
6145 
6146 	tAgent.m_dResults.Resize ( iResults );
6147 	for ( int iRes=0; iRes<iResults; iRes++ )
6148 		tAgent.m_dResults[iRes].m_iSuccesses = 0;
6149 
6150 	for ( int iRes=0; iRes<iResults; iRes++ )
6151 	{
6152 		CSphQueryResult & tRes = tAgent.m_dResults [ iRes ];
6153 		tRes.m_sError = "";
6154 		tRes.m_sWarning = "";
6155 
6156 		// get status and message
6157 		DWORD eStatus = tReq.GetDword ();
6158 		if ( eStatus!=SEARCHD_OK )
6159 		{
6160 			CSphString sMessage = tReq.GetString ();
6161 			switch ( eStatus )
6162 			{
6163 				case SEARCHD_ERROR:		tRes.m_sError = sMessage; continue;
6164 				case SEARCHD_RETRY:		tRes.m_sError = sMessage; break;
6165 				case SEARCHD_WARNING:	tRes.m_sWarning = sMessage; break;
6166 				default:				tAgent.m_sFailure.SetSprintf ( "internal error: unknown status %d", eStatus ); break;
6167 			}
6168 		}
6169 
6170 		// get schema
6171 		CSphRsetSchema & tSchema = tRes.m_tSchema;
6172 		tSchema.Reset ();
6173 
6174 		tSchema.m_dFields.Resize ( tReq.GetInt() ); // FIXME! add a sanity check
6175 		ARRAY_FOREACH ( j, tSchema.m_dFields )
6176 			tSchema.m_dFields[j].m_sName = tReq.GetString ();
6177 
6178 		int iNumAttrs = tReq.GetInt(); // FIXME! add a sanity check
6179 		for ( int j=0; j<iNumAttrs; j++ )
6180 		{
6181 			CSphColumnInfo tCol;
6182 			tCol.m_sName = tReq.GetString ();
6183 			tCol.m_eAttrType = (ESphAttr) tReq.GetDword (); // FIXME! add a sanity check
6184 			tSchema.AddDynamicAttr ( tCol ); // all attributes received from agents are dynamic
6185 		}
6186 
6187 		// get matches
6188 		int iMatches = tReq.GetInt ();
6189 		if ( iMatches<0 )
6190 		{
6191 			tAgent.m_sFailure.SetSprintf ( "invalid match count received (count=%d)", iMatches );
6192 			return false;
6193 		}
6194 
6195 		int bAgent64 = tReq.GetInt ();
6196 #if !USE_64BIT
6197 		if ( bAgent64 )
6198 			tAgent.m_sFailure.SetSprintf ( "id64 agent, id32 master, docids might be wrapped" );
6199 #endif
6200 
6201 		assert ( !tRes.m_dMatches.GetLength() );
6202 		if ( iMatches )
6203 		{
6204 			tRes.m_dMatches.Resize ( iMatches );
6205 			ARRAY_FOREACH ( i, tRes.m_dMatches )
6206 			{
6207 				CSphMatch & tMatch = tRes.m_dMatches[i];
6208 				tMatch.Reset ( tSchema.GetRowSize() );
6209 				tMatch.m_uDocID = bAgent64 ? (SphDocID_t)tReq.GetUint64() : tReq.GetDword();
6210 				tMatch.m_iWeight = tReq.GetInt ();
6211 				for ( int j=0; j<tSchema.GetAttrsCount(); j++ )
6212 				{
6213 					const CSphColumnInfo & tAttr = tSchema.GetAttr(j);
6214 					if ( tAttr.m_eAttrType==SPH_ATTR_UINT32SET || tAttr.m_eAttrType==SPH_ATTR_INT64SET )
6215 					{
6216 						tMatch.SetAttr ( tAttr.m_tLocator, m_dMvaStorage.GetLength() );
6217 
6218 						int iValues = tReq.GetDword ();
6219 						m_dMvaStorage.Add ( iValues );
6220 						if ( tAttr.m_eAttrType==SPH_ATTR_UINT32SET )
6221 						{
6222 							while ( iValues-- )
6223 								m_dMvaStorage.Add ( tReq.GetDword() );
6224 						} else
6225 						{
6226 							assert ( ( iValues%2 )==0 );
6227 							for ( ; iValues; iValues -= 2 )
6228 							{
6229 								uint64_t uMva = tReq.GetUint64();
6230 								m_dMvaStorage.Add ( (DWORD)uMva );
6231 								m_dMvaStorage.Add ( (DWORD)( uMva>>32 ) );
6232 							}
6233 						}
6234 
6235 					} else if ( tAttr.m_eAttrType==SPH_ATTR_FLOAT )
6236 					{
6237 						float fRes = tReq.GetFloat();
6238 						tMatch.SetAttr ( tAttr.m_tLocator, sphF2DW(fRes) );
6239 
6240 					} else if ( tAttr.m_eAttrType==SPH_ATTR_BIGINT )
6241 					{
6242 						tMatch.SetAttr ( tAttr.m_tLocator, tReq.GetUint64() );
6243 
6244 					} else if ( tAttr.m_eAttrType==SPH_ATTR_STRING || tAttr.m_eAttrType==SPH_ATTR_JSON )
6245 					{
6246 						int iLen = tReq.GetDword();
6247 						if ( !iLen )
6248 						{
6249 							tMatch.SetAttr ( tAttr.m_tLocator, 0 );
6250 						} else
6251 						{
6252 							int iOff = m_dStringsStorage.GetLength();
6253 							tMatch.SetAttr ( tAttr.m_tLocator, iOff );
6254 
6255 							m_dStringsStorage.Resize ( iOff+4+iLen );
6256 							int iPackedLen = sphPackStrlen ( m_dStringsStorage.Begin() + iOff, iLen );
6257 							tReq.GetBytes ( m_dStringsStorage.Begin() + iOff + iPackedLen, iLen );
6258 							m_dStringsStorage.Resize ( iOff+iPackedLen+iLen );
6259 						}
6260 
6261 					} else if ( tAttr.m_eAttrType==SPH_ATTR_STRINGPTR )
6262 					{
6263 						CSphString sValue = tReq.GetString();
6264 						tMatch.SetAttr ( tAttr.m_tLocator, (SphAttr_t) sValue.Leak() );
6265 					} else if ( tAttr.m_eAttrType==SPH_ATTR_FACTORS || tAttr.m_eAttrType==SPH_ATTR_FACTORS_JSON )
6266 					{
6267 						DWORD uLength = tReq.GetDword();
6268 						BYTE * pData = new BYTE[uLength];
6269 						*(DWORD *)pData = uLength;
6270 						tReq.GetBytes ( pData+sizeof(DWORD), uLength-sizeof(DWORD) );
6271 						tMatch.SetAttr ( tAttr.m_tLocator, (SphAttr_t) pData );
6272 
6273 					} else if ( tAttr.m_eAttrType==SPH_ATTR_JSON_FIELD )
6274 					{
6275 						ESphJsonType eJson = (ESphJsonType)tReq.GetByte();
6276 						if ( eJson==JSON_EOF )
6277 						{
6278 							tMatch.SetAttr ( tAttr.m_tLocator, 0 );
6279 						} else
6280 						{
6281 							int iOff = m_dStringsStorage.GetLength();
6282 							int64_t iTypeOffset = ( ( (int64_t)iOff ) | ( ( (int64_t)eJson )<<32 ) );
6283 							tMatch.SetAttr ( tAttr.m_tLocator, iTypeOffset );
6284 
6285 							// read node length if needed
6286 							int iLen = sphJsonNodeSize ( eJson, NULL );
6287 							if ( iLen<0 )
6288 								iLen = tReq.GetDword ();
6289 
6290 							m_dStringsStorage.Resize ( iOff+iLen );
6291 							tReq.GetBytes ( m_dStringsStorage.Begin()+iOff, iLen );
6292 						}
6293 
6294 					} else
6295 					{
6296 						tMatch.SetAttr ( tAttr.m_tLocator, tReq.GetDword() );
6297 					}
6298 				}
6299 			}
6300 		}
6301 
6302 		// read totals (retrieved count, total count, query time, word count)
6303 		int iRetrieved = tReq.GetInt ();
6304 		tRes.m_iTotalMatches = (unsigned int)tReq.GetInt ();
6305 		tRes.m_iQueryTime = tReq.GetInt ();
6306 
6307 		// agents always send IO/CPU stats to master
6308 		BYTE uStatMask = tReq.GetByte();
6309 		if ( uStatMask & 1 )
6310 		{
6311 			tRes.m_tIOStats.m_iReadTime = tReq.GetUint64();
6312 			tRes.m_tIOStats.m_iReadOps = tReq.GetDword();
6313 			tRes.m_tIOStats.m_iReadBytes = tReq.GetUint64();
6314 			tRes.m_tIOStats.m_iWriteTime = tReq.GetUint64();
6315 			tRes.m_tIOStats.m_iWriteOps = tReq.GetDword();
6316 			tRes.m_tIOStats.m_iWriteBytes = tReq.GetUint64();
6317 		}
6318 
6319 		if ( uStatMask & 2 )
6320 			tRes.m_iCpuTime = tReq.GetUint64();
6321 
6322 		if ( uStatMask & 4 )
6323 			tRes.m_iPredictedTime = tReq.GetUint64();
6324 
6325 		tRes.m_iAgentFetchedDocs = tReq.GetDword();
6326 		tRes.m_iAgentFetchedHits = tReq.GetDword();
6327 		tRes.m_iAgentFetchedSkips = tReq.GetDword();
6328 
6329 		const int iWordsCount = tReq.GetInt (); // FIXME! sanity check?
6330 		if ( iRetrieved!=iMatches )
6331 		{
6332 			tAgent.m_sFailure.SetSprintf ( "expected %d retrieved documents, got %d", iMatches, iRetrieved );
6333 			return false;
6334 		}
6335 
6336 		// read per-word stats
6337 		for ( int i=0; i<iWordsCount; i++ )
6338 		{
6339 			const CSphString sWord = tReq.GetString ();
6340 			const int64_t iDocs = (unsigned int)tReq.GetInt ();
6341 			const int64_t iHits = (unsigned int)tReq.GetInt ();
6342 			tReq.GetByte(); // statistics have no expanded terms for now
6343 
6344 			tRes.AddStat ( sWord, iDocs, iHits );
6345 		}
6346 
6347 		// mark this result as ok
6348 		tRes.m_iSuccesses = 1;
6349 	}
6350 
6351 	// all seems OK (and buffer length checks are performed by caller)
6352 	return true;
6353 }
6354 
6355 /////////////////////////////////////////////////////////////////////////////
6356 
6357 // returns true if incoming schema (src) is equal to existing (dst); false otherwise
MinimizeSchema(CSphRsetSchema & tDst,const ISphSchema & tSrc)6358 bool MinimizeSchema ( CSphRsetSchema & tDst, const ISphSchema & tSrc )
6359 {
6360 	// if dst is empty, result is also empty
6361 	if ( tDst.GetAttrsCount()==0 )
6362 		return tSrc.GetAttrsCount()==0;
6363 
6364 	// check for equality, and remove all dst attributes that are not present in src
6365 	CSphVector<CSphColumnInfo> dDst;
6366 	for ( int i=0; i<tDst.GetAttrsCount(); i++ )
6367 		dDst.Add ( tDst.GetAttr(i) );
6368 
6369 	bool bEqual = ( tDst.GetAttrsCount()==tSrc.GetAttrsCount() );
6370 	ARRAY_FOREACH ( i, dDst )
6371 	{
6372 		int iSrcIdx = tSrc.GetAttrIndex ( dDst[i].m_sName.cstr() );
6373 
6374 		// check for index mismatch
6375 		if ( iSrcIdx!=i )
6376 			bEqual = false;
6377 
6378 		// check for type/size mismatch (and fixup if needed)
6379 		if ( iSrcIdx>=0 )
6380 		{
6381 			const CSphColumnInfo & tSrcAttr = tSrc.GetAttr ( iSrcIdx );
6382 
6383 			// should seamlessly convert ( bool > float ) | ( bool > int > bigint )
6384 			ESphAttr eDst = dDst[i].m_eAttrType;
6385 			ESphAttr eSrc = tSrcAttr.m_eAttrType;
6386 			bool bSame = ( eDst==eSrc )
6387 				|| ( ( eDst==SPH_ATTR_FLOAT && eSrc==SPH_ATTR_BOOL ) || ( eDst==SPH_ATTR_BOOL && eSrc==SPH_ATTR_FLOAT ) )
6388 				|| ( ( eDst==SPH_ATTR_BOOL || eDst==SPH_ATTR_INTEGER || eDst==SPH_ATTR_BIGINT )
6389 					&& ( eSrc==SPH_ATTR_BOOL || eSrc==SPH_ATTR_INTEGER || eSrc==SPH_ATTR_BIGINT ) );
6390 
6391 			int iDstBitCount = dDst[i].m_tLocator.m_iBitCount;
6392 			int iSrcBitCount = tSrcAttr.m_tLocator.m_iBitCount;
6393 
6394 			if ( !bSame )
6395 			{
6396 				// different types? remove the attr
6397 				iSrcIdx = -1;
6398 				bEqual = false;
6399 
6400 			} else if ( iDstBitCount!=iSrcBitCount )
6401 			{
6402 				// different bit sizes? choose the max one
6403 				dDst[i].m_tLocator.m_iBitCount = Max ( iDstBitCount, iSrcBitCount );
6404 				bEqual = false;
6405 				if ( iDstBitCount<iSrcBitCount )
6406 					dDst[i].m_eAttrType = tSrcAttr.m_eAttrType;
6407 			}
6408 
6409 			if ( tSrcAttr.m_tLocator.m_iBitOffset!=dDst[i].m_tLocator.m_iBitOffset )
6410 			{
6411 				// different offsets? have to force target dynamic then, since we can't use one locator for all matches
6412 				bEqual = false;
6413 			}
6414 
6415 			if ( tSrcAttr.m_tLocator.m_bDynamic!=dDst[i].m_tLocator.m_bDynamic )
6416 			{
6417 				// different location? have to force target dynamic then
6418 				bEqual = false;
6419 			}
6420 		}
6421 
6422 		// check for presence
6423 		if ( iSrcIdx<0 )
6424 		{
6425 			dDst.Remove ( i );
6426 			i--;
6427 		}
6428 	}
6429 
6430 	if ( !bEqual )
6431 	{
6432 		CSphVector<CSphColumnInfo> dFields;
6433 		Swap ( dFields, tDst.m_dFields );
6434 		tDst.Reset();
6435 		ARRAY_FOREACH ( i, dDst )
6436 			tDst.AddDynamicAttr ( dDst[i] );
6437 		Swap ( dFields, tDst.m_dFields );
6438 
6439 	} else
6440 	{
6441 		tDst.SwapAttrs ( dDst );
6442 	}
6443 
6444 	return bEqual;
6445 }
6446 
ParseIndexList(const CSphString & sIndexes,CSphVector<CSphString> & dOut)6447 static void ParseIndexList ( const CSphString & sIndexes, CSphVector<CSphString> & dOut )
6448 {
6449 	CSphString sSplit = sIndexes;
6450 	char * p = (char*)sSplit.cstr();
6451 	while ( *p )
6452 	{
6453 		// skip non-alphas
6454 		while ( *p && !isalpha ( *p ) && !isdigit ( *p ) && *p!='_' ) p++;
6455 		if ( !(*p) ) break;
6456 
6457 		// FIXME?
6458 		// We no not check that index name shouldn't start with '_'.
6459 		// That means it's de facto allowed for API queries.
6460 		// But not for SphinxQL ones.
6461 
6462 		// this is my next index name
6463 		const char * sNext = p;
6464 		while ( isalpha ( *p ) || isdigit ( *p ) || *p=='_' ) p++;
6465 
6466 		assert ( sNext!=p );
6467 		if ( *p ) *p++ = '\0'; // if it was not the end yet, we'll continue from next char
6468 
6469 		dOut.Add ( sNext );
6470 	}
6471 }
6472 
6473 
CheckQuery(const CSphQuery & tQuery,CSphString & sError)6474 static void CheckQuery ( const CSphQuery & tQuery, CSphString & sError )
6475 {
6476 	#define LOC_ERROR(_msg) { sError.SetSprintf ( _msg ); return; }
6477 	#define LOC_ERROR1(_msg,_arg1) { sError.SetSprintf ( _msg, _arg1 ); return; }
6478 	#define LOC_ERROR2(_msg,_arg1,_arg2) { sError.SetSprintf ( _msg, _arg1, _arg2 ); return; }
6479 
6480 	sError = NULL;
6481 
6482 	if ( tQuery.m_eMode<0 || tQuery.m_eMode>SPH_MATCH_TOTAL )
6483 		LOC_ERROR1 ( "invalid match mode %d", tQuery.m_eMode );
6484 
6485 	if ( tQuery.m_eRanker<0 || tQuery.m_eRanker>SPH_RANK_TOTAL )
6486 		LOC_ERROR1 ( "invalid ranking mode %d", tQuery.m_eRanker );
6487 
6488 	if ( tQuery.m_iMaxMatches<1 )
6489 		LOC_ERROR ( "max_matches can not be less than one" );
6490 
6491 	if ( tQuery.m_iOffset<0 || tQuery.m_iOffset>=tQuery.m_iMaxMatches )
6492 		LOC_ERROR2 ( "offset out of bounds (offset=%d, max_matches=%d)",
6493 			tQuery.m_iOffset, tQuery.m_iMaxMatches );
6494 
6495 	if ( tQuery.m_iLimit<0 )
6496 		LOC_ERROR1 ( "limit out of bounds (limit=%d)", tQuery.m_iLimit );
6497 
6498 	if ( tQuery.m_iCutoff<0 )
6499 		LOC_ERROR1 ( "cutoff out of bounds (cutoff=%d)", tQuery.m_iCutoff );
6500 
6501 	if ( ( tQuery.m_iRetryCount!=g_iAgentRetryCount )
6502 		&& ( tQuery.m_iRetryCount<0 || tQuery.m_iRetryCount>MAX_RETRY_COUNT ) )
6503 			LOC_ERROR1 ( "retry count out of bounds (count=%d)", tQuery.m_iRetryCount );
6504 
6505 	if ( ( tQuery.m_iRetryCount!=g_iAgentRetryDelay )
6506 		&& ( tQuery.m_iRetryDelay<0 || tQuery.m_iRetryDelay>MAX_RETRY_DELAY ) )
6507 			LOC_ERROR1 ( "retry delay out of bounds (delay=%d)", tQuery.m_iRetryDelay );
6508 
6509 	if ( tQuery.m_iOffset>0 && tQuery.m_bHasOuter )
6510 		LOC_ERROR1 ( "inner offset must be 0 when using outer order by (offset=%d)", tQuery.m_iOffset );
6511 
6512 	#undef LOC_ERROR
6513 	#undef LOC_ERROR1
6514 	#undef LOC_ERROR2
6515 }
6516 
6517 
PrepareQueryEmulation(CSphQuery * pQuery)6518 void PrepareQueryEmulation ( CSphQuery * pQuery )
6519 {
6520 	assert ( pQuery && pQuery->m_sRawQuery.cstr() );
6521 
6522 	// sort filters
6523 	ARRAY_FOREACH ( i, pQuery->m_dFilters )
6524 		pQuery->m_dFilters[i].m_dValues.Sort();
6525 
6526 	// sort overrides
6527 	ARRAY_FOREACH ( i, pQuery->m_dOverrides )
6528 		pQuery->m_dOverrides[i].m_dValues.Sort ();
6529 
6530 	// fixup query
6531 	pQuery->m_sQuery = pQuery->m_sRawQuery;
6532 
6533 	if ( pQuery->m_eMode==SPH_MATCH_BOOLEAN )
6534 		pQuery->m_eRanker = SPH_RANK_NONE;
6535 
6536 	if ( pQuery->m_eMode==SPH_MATCH_FULLSCAN )
6537 		pQuery->m_sQuery = "";
6538 
6539 	if ( pQuery->m_eMode!=SPH_MATCH_ALL && pQuery->m_eMode!=SPH_MATCH_ANY && pQuery->m_eMode!=SPH_MATCH_PHRASE )
6540 		return;
6541 
6542 	const char * szQuery = pQuery->m_sRawQuery.cstr ();
6543 	int iQueryLen = strlen(szQuery);
6544 
6545 	pQuery->m_sQuery.Reserve ( iQueryLen*2+8 );
6546 	char * szRes = (char*) pQuery->m_sQuery.cstr ();
6547 	char c;
6548 
6549 	if ( pQuery->m_eMode==SPH_MATCH_ANY || pQuery->m_eMode==SPH_MATCH_PHRASE )
6550 		*szRes++ = '\"';
6551 
6552 	if ( iQueryLen )
6553 	{
6554 		while ( ( c = *szQuery++ )!=0 )
6555 		{
6556 			// must be in sync with EscapeString (php api)
6557 			const char sMagics[] = "<\\()|-!@~\"&/^$=";
6558 			for ( const char * s = sMagics; *s; s++ )
6559 				if ( c==*s )
6560 				{
6561 					*szRes++ = '\\';
6562 					break;
6563 				}
6564 			*szRes++ = c;
6565 		}
6566 	}
6567 
6568 	switch ( pQuery->m_eMode )
6569 	{
6570 		case SPH_MATCH_ALL:		pQuery->m_eRanker = SPH_RANK_PROXIMITY; *szRes = '\0'; break;
6571 		case SPH_MATCH_ANY:		pQuery->m_eRanker = SPH_RANK_MATCHANY; strncpy ( szRes, "\"/1", 8 ); break;
6572 		case SPH_MATCH_PHRASE:	pQuery->m_eRanker = SPH_RANK_PROXIMITY; *szRes++ = '\"'; *szRes = '\0'; break;
6573 		default:				return;
6574 	}
6575 
6576 	if ( !pQuery->m_bHasOuter )
6577 	{
6578 		pQuery->m_sOuterOrderBy = "";
6579 		pQuery->m_iOuterOffset = 0;
6580 		pQuery->m_iOuterLimit = 0;
6581 	}
6582 }
6583 
6584 
ParseSearchQuery(InputBuffer_c & tReq,CSphQuery & tQuery,int iVer,int iMasterVer)6585 bool ParseSearchQuery ( InputBuffer_c & tReq, CSphQuery & tQuery, int iVer, int iMasterVer )
6586 {
6587 	// daemon-level defaults
6588 	tQuery.m_iRetryCount = g_iAgentRetryCount;
6589 	tQuery.m_iRetryDelay = g_iAgentRetryDelay;
6590 	tQuery.m_iAgentQueryTimeout = g_iAgentQueryTimeout;
6591 
6592 	// v.1.27+ flags come first
6593 	DWORD uFlags = 0;
6594 	if ( iVer>=0x11B )
6595 		uFlags = tReq.GetDword();
6596 
6597 	// v.1.0. mode, limits, weights, ID/TS ranges
6598 	tQuery.m_iOffset = tReq.GetInt ();
6599 	tQuery.m_iLimit = tReq.GetInt ();
6600 	tQuery.m_eMode = (ESphMatchMode) tReq.GetInt ();
6601 	if ( iVer>=0x110 )
6602 	{
6603 		tQuery.m_eRanker = (ESphRankMode) tReq.GetInt ();
6604 		if ( tQuery.m_eRanker==SPH_RANK_EXPR || tQuery.m_eRanker==SPH_RANK_EXPORT )
6605 			tQuery.m_sRankerExpr = tReq.GetString();
6606 	}
6607 	tQuery.m_eSort = (ESphSortOrder) tReq.GetInt ();
6608 	if ( iVer>=0x102 )
6609 	{
6610 		tQuery.m_sSortBy = tReq.GetString ();
6611 		sphColumnToLowercase ( const_cast<char *>( tQuery.m_sSortBy.cstr() ) );
6612 	}
6613 	tQuery.m_sRawQuery = tReq.GetString ();
6614 	tQuery.m_iWeights = tReq.GetDwords ( (DWORD**)&tQuery.m_pWeights, SPH_MAX_FIELDS, "invalid weight count %d (should be in 0..%d range)" );
6615 	tQuery.m_sIndexes = tReq.GetString ();
6616 
6617 	bool bIdrange64 = false;
6618 	if ( iVer>=0x108 )
6619 		bIdrange64 = ( tReq.GetInt()!=0 );
6620 
6621 	SphDocID_t uMinID = 0;
6622 	SphDocID_t uMaxID = DOCID_MAX;
6623 	if ( bIdrange64 )
6624 	{
6625 		uMinID = (SphDocID_t)tReq.GetUint64 ();
6626 		uMaxID = (SphDocID_t)tReq.GetUint64 ();
6627 		// FIXME? could report clamp here if I'm id32 and client passed id64 range,
6628 		// but frequently this won't affect anything at all
6629 	} else
6630 	{
6631 		uMinID = tReq.GetDword ();
6632 		uMaxID = tReq.GetDword ();
6633 	}
6634 
6635 	if ( iVer<0x108 && uMaxID==0xffffffffUL )
6636 		uMaxID = 0; // fixup older clients which send 32-bit UINT_MAX by default
6637 
6638 	if ( uMaxID==0 )
6639 		uMaxID = DOCID_MAX;
6640 
6641 	// v.1.2
6642 	if ( iVer>=0x102 )
6643 	{
6644 		int iAttrFilters = tReq.GetInt ();
6645 		if ( iAttrFilters>g_iMaxFilters )
6646 		{
6647 			tReq.SendErrorReply ( "too much attribute filters (req=%d, max=%d)", iAttrFilters, g_iMaxFilters );
6648 			return false;
6649 		}
6650 
6651 		const int MAX_ERROR_SET_BUFFER = 128;
6652 		char sSetError[MAX_ERROR_SET_BUFFER];
6653 		tQuery.m_dFilters.Resize ( iAttrFilters );
6654 		ARRAY_FOREACH ( iFilter, tQuery.m_dFilters )
6655 		{
6656 			CSphFilterSettings & tFilter = tQuery.m_dFilters[iFilter];
6657 			tFilter.m_sAttrName = tReq.GetString ();
6658 			sphColumnToLowercase ( const_cast<char *>( tFilter.m_sAttrName.cstr() ) );
6659 
6660 			snprintf ( sSetError, MAX_ERROR_SET_BUFFER
6661 				, "invalid attribute '%s'(%d) set length %s (should be in 0..%s range)", tFilter.m_sAttrName.cstr(), iFilter, "%d", "%d" );
6662 
6663 			if ( iVer>=0x10E )
6664 			{
6665 				// v.1.14+
6666 				tFilter.m_eType = (ESphFilter) tReq.GetDword ();
6667 				switch ( tFilter.m_eType )
6668 				{
6669 					case SPH_FILTER_RANGE:
6670 						tFilter.m_iMinValue = ( iVer>=0x114 ) ? tReq.GetUint64() : tReq.GetDword ();
6671 						tFilter.m_iMaxValue = ( iVer>=0x114 ) ? tReq.GetUint64() : tReq.GetDword ();
6672 						break;
6673 
6674 					case SPH_FILTER_FLOATRANGE:
6675 						tFilter.m_fMinValue = tReq.GetFloat ();
6676 						tFilter.m_fMaxValue = tReq.GetFloat ();
6677 						break;
6678 
6679 					case SPH_FILTER_VALUES:
6680 						{
6681 							bool bRes = ( iVer>=0x114 )
6682 								? tReq.GetQwords ( tFilter.m_dValues, g_iMaxFilterValues, sSetError )
6683 								: tReq.GetDwords ( tFilter.m_dValues, g_iMaxFilterValues, sSetError );
6684 							if ( !bRes )
6685 								return false;
6686 						}
6687 						break;
6688 					case SPH_FILTER_STRING:
6689 						tFilter.m_dStrings.Add ( tReq.GetString() );
6690 						break;
6691 
6692 					case SPH_FILTER_NULL:
6693 						tFilter.m_bHasEqual = tReq.GetByte()!=0;
6694 						break;
6695 
6696 					case SPH_FILTER_USERVAR:
6697 						tFilter.m_dStrings.Add ( tReq.GetString() );
6698 						break;
6699 
6700 					case SPH_FILTER_STRING_LIST:
6701 					{
6702 						int iCount = tReq.GetDword();
6703 						if ( iCount<0 || iCount>g_iMaxFilterValues )
6704 						{
6705 							tReq.SendErrorReply ( sSetError, iCount, g_iMaxFilterValues );
6706 							return false;
6707 						}
6708 						tFilter.m_dStrings.Resize ( iCount );
6709 						ARRAY_FOREACH ( iString, tFilter.m_dStrings )
6710 							tFilter.m_dStrings[iString] = tReq.GetString();
6711 					}
6712 					break;
6713 
6714 					default:
6715 						tReq.SendErrorReply ( "unknown filter type (type-id=%d)", tFilter.m_eType );
6716 						return false;
6717 				}
6718 
6719 			} else
6720 			{
6721 				// pre-1.14
6722 				if ( !tReq.GetDwords ( tFilter.m_dValues, g_iMaxFilterValues, sSetError ) )
6723 					return false;
6724 
6725 				if ( !tFilter.m_dValues.GetLength() )
6726 				{
6727 					// 0 length means this is range, not set
6728 					tFilter.m_iMinValue = tReq.GetDword ();
6729 					tFilter.m_iMaxValue = tReq.GetDword ();
6730 				}
6731 
6732 				tFilter.m_eType = tFilter.m_dValues.GetLength() ? SPH_FILTER_VALUES : SPH_FILTER_RANGE;
6733 			}
6734 
6735 			if ( iVer>=0x106 )
6736 				tFilter.m_bExclude = !!tReq.GetDword ();
6737 
6738 			if ( iMasterVer>=5 )
6739 				tFilter.m_bHasEqual = !!tReq.GetDword();
6740 
6741 			// back-port of master v13 git:edba5ab93a7313684525ed22eb2df7072c333d71
6742 			if ( iMasterVer>=13 )
6743 				tReq.GetDword();
6744 		}
6745 	}
6746 
6747 	// now add id range filter
6748 	if ( uMinID!=0 || uMaxID!=DOCID_MAX )
6749 	{
6750 		CSphFilterSettings & tFilter = tQuery.m_dFilters.Add();
6751 		tFilter.m_sAttrName = "@id";
6752 		tFilter.m_eType = SPH_FILTER_RANGE;
6753 		tFilter.m_iMinValue = uMinID;
6754 		tFilter.m_iMaxValue = uMaxID;
6755 	}
6756 
6757 	// v.1.3
6758 	if ( iVer>=0x103 )
6759 	{
6760 		tQuery.m_eGroupFunc = (ESphGroupBy) tReq.GetDword ();
6761 		tQuery.m_sGroupBy = tReq.GetString ();
6762 		sphColumnToLowercase ( const_cast<char *>( tQuery.m_sGroupBy.cstr() ) );
6763 	}
6764 
6765 	// v.1.4
6766 	tQuery.m_iMaxMatches = DEFAULT_MAX_MATCHES;
6767 	if ( iVer>=0x104 )
6768 		tQuery.m_iMaxMatches = tReq.GetInt ();
6769 
6770 	// v.1.5, v.1.7
6771 	if ( iVer>=0x107 )
6772 	{
6773 		tQuery.m_sGroupSortBy = tReq.GetString ();
6774 	} else if ( iVer>=0x105 )
6775 	{
6776 		bool bSortByGroup = ( tReq.GetInt()!=0 );
6777 		if ( !bSortByGroup )
6778 		{
6779 			char sBuf[256];
6780 			switch ( tQuery.m_eSort )
6781 			{
6782 			case SPH_SORT_RELEVANCE:
6783 				tQuery.m_sGroupSortBy = "@weight desc";
6784 				break;
6785 
6786 			case SPH_SORT_ATTR_DESC:
6787 			case SPH_SORT_ATTR_ASC:
6788 				snprintf ( sBuf, sizeof(sBuf), "%s %s", tQuery.m_sSortBy.cstr(),
6789 					tQuery.m_eSort==SPH_SORT_ATTR_ASC ? "asc" : "desc" );
6790 				tQuery.m_sGroupSortBy = sBuf;
6791 				break;
6792 
6793 			case SPH_SORT_EXTENDED:
6794 				tQuery.m_sGroupSortBy = tQuery.m_sSortBy;
6795 				break;
6796 
6797 			default:
6798 				tReq.SendErrorReply ( "INTERNAL ERROR: unsupported sort mode %d in groupby sort fixup", tQuery.m_eSort );
6799 				return false;
6800 			}
6801 		}
6802 	}
6803 
6804 	// v.1.9
6805 	if ( iVer>=0x109 )
6806 		tQuery.m_iCutoff = tReq.GetInt();
6807 
6808 	// v.1.10
6809 	if ( iVer>=0x10A )
6810 	{
6811 		tQuery.m_iRetryCount = tReq.GetInt ();
6812 		tQuery.m_iRetryDelay = tReq.GetInt ();
6813 	}
6814 
6815 	// v.1.11
6816 	if ( iVer>=0x10B )
6817 	{
6818 		tQuery.m_sGroupDistinct = tReq.GetString ();
6819 		sphColumnToLowercase ( const_cast<char *>( tQuery.m_sGroupDistinct.cstr() ) );
6820 	}
6821 
6822 	// v.1.14
6823 	if ( iVer>=0x10E )
6824 	{
6825 		tQuery.m_bGeoAnchor = ( tReq.GetInt()!=0 );
6826 		if ( tQuery.m_bGeoAnchor )
6827 		{
6828 			tQuery.m_sGeoLatAttr = tReq.GetString ();
6829 			tQuery.m_sGeoLongAttr = tReq.GetString ();
6830 			tQuery.m_fGeoLatitude = tReq.GetFloat ();
6831 			tQuery.m_fGeoLongitude = tReq.GetFloat ();
6832 		}
6833 	}
6834 
6835 	// v.1.15
6836 	if ( iVer>=0x10F )
6837 	{
6838 		tQuery.m_dIndexWeights.Resize ( tReq.GetInt() ); // FIXME! add sanity check
6839 		ARRAY_FOREACH ( i, tQuery.m_dIndexWeights )
6840 		{
6841 			tQuery.m_dIndexWeights[i].m_sName = tReq.GetString ();
6842 			tQuery.m_dIndexWeights[i].m_iValue = tReq.GetInt ();
6843 		}
6844 	}
6845 
6846 	// v.1.17
6847 	if ( iVer>=0x111 )
6848 		tQuery.m_uMaxQueryMsec = tReq.GetDword ();
6849 
6850 	// v.1.18
6851 	if ( iVer>=0x112 )
6852 	{
6853 		tQuery.m_dFieldWeights.Resize ( tReq.GetInt() ); // FIXME! add sanity check
6854 		ARRAY_FOREACH ( i, tQuery.m_dFieldWeights )
6855 		{
6856 			tQuery.m_dFieldWeights[i].m_sName = tReq.GetString ();
6857 			tQuery.m_dFieldWeights[i].m_iValue = tReq.GetInt ();
6858 		}
6859 	}
6860 
6861 	// v.1.19
6862 	if ( iVer>=0x113 )
6863 		tQuery.m_sComment = tReq.GetString ();
6864 
6865 	// v.1.21
6866 	if ( iVer>=0x115 )
6867 	{
6868 		tQuery.m_dOverrides.Resize ( tReq.GetInt() ); // FIXME! add sanity check
6869 		ARRAY_FOREACH ( i, tQuery.m_dOverrides )
6870 		{
6871 			CSphAttrOverride & tOverride = tQuery.m_dOverrides[i];
6872 			tOverride.m_sAttr = tReq.GetString ();
6873 			tOverride.m_eAttrType = (ESphAttr) tReq.GetDword ();
6874 
6875 			tOverride.m_dValues.Resize ( tReq.GetInt() ); // FIXME! add sanity check
6876 			ARRAY_FOREACH ( iVal, tOverride.m_dValues )
6877 			{
6878 				CSphAttrOverride::IdValuePair_t & tEntry = tOverride.m_dValues[iVal];
6879 				tEntry.m_uDocID = (SphDocID_t) tReq.GetUint64 ();
6880 
6881 				if ( tOverride.m_eAttrType==SPH_ATTR_FLOAT )		tEntry.m_fValue = tReq.GetFloat ();
6882 				else if ( tOverride.m_eAttrType==SPH_ATTR_BIGINT )	tEntry.m_uValue = tReq.GetUint64 ();
6883 				else												tEntry.m_uValue = tReq.GetDword ();
6884 			}
6885 		}
6886 	}
6887 
6888 	// v.1.22
6889 	if ( iVer>=0x116 )
6890 	{
6891 		tQuery.m_sSelect = tReq.GetString ();
6892 		tQuery.m_bAgent = ( iMasterVer>0 );
6893 		if ( tQuery.m_sSelect.Begins ( "*,*" ) ) // this is the legacy mark of agent for debug purpose
6894 		{
6895 			tQuery.m_bAgent = true;
6896 			int iSelectLen = tQuery.m_sSelect.Length();
6897 			tQuery.m_sSelect = ( iSelectLen>4 ? tQuery.m_sSelect.SubString ( 4, iSelectLen-4 ) : "*" );
6898 		}
6899 		// fixup select list
6900 		if ( tQuery.m_sSelect.IsEmpty () )
6901 			tQuery.m_sSelect = "*";
6902 
6903 		CSphString sError;
6904 		if ( !tQuery.ParseSelectList ( sError ) )
6905 		{
6906 			tReq.SendErrorReply ( "select: %s", sError.cstr() );
6907 
6908 			// we want to see a parse error in query_log_format=sphinxql mode too
6909 			if ( g_eLogFormat==LOG_FORMAT_SPHINXQL && g_iQueryLogFile>=0 )
6910 			{
6911 				CSphStringBuilder tBuf;
6912 				char sTimeBuf [ SPH_TIME_PID_MAX_SIZE ];
6913 				sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
6914 
6915 				tBuf += "/""* ";
6916 				tBuf += sTimeBuf;
6917 				tBuf.Appendf ( "*""/ %s # error=%s\n", tQuery.m_sSelect.cstr(), sError.cstr() );
6918 
6919 				sphSeek ( g_iQueryLogFile, 0, SEEK_END );
6920 				sphWrite ( g_iQueryLogFile, tBuf.cstr(), tBuf.Length() );
6921 			}
6922 
6923 			return false;
6924 		}
6925 	}
6926 
6927 	// v.1.27
6928 	if ( iVer>=0x11B )
6929 	{
6930 		// parse simple flags
6931 		tQuery.m_bReverseScan = !!( uFlags & QFLAG_REVERSE_SCAN );
6932 		tQuery.m_bSortKbuffer = !!( uFlags & QFLAG_SORT_KBUFFER );
6933 		tQuery.m_bSimplify = !!( uFlags & QFLAG_SIMPLIFY );
6934 		tQuery.m_bPlainIDF = !!( uFlags & QFLAG_PLAIN_IDF );
6935 		tQuery.m_bGlobalIDF = !!( uFlags & QFLAG_GLOBAL_IDF );
6936 		tQuery.m_bLocalDF = !!( uFlags & QFLAG_LOCAL_DF );
6937 
6938 		if ( iMasterVer>0 || iVer==0x11E )
6939 			tQuery.m_bNormalizedTFIDF = !!( uFlags & QFLAG_NORMALIZED_TF );
6940 
6941 		// fetch optional stuff
6942 		if ( uFlags & QFLAG_MAX_PREDICTED_TIME )
6943 			tQuery.m_iMaxPredictedMsec = tReq.GetInt();
6944 	}
6945 
6946 	// v.1.29
6947 	if ( iVer>=0x11D )
6948 	{
6949 		tQuery.m_sOuterOrderBy = tReq.GetString();
6950 		tQuery.m_iOuterOffset = tReq.GetDword();
6951 		tQuery.m_iOuterLimit = tReq.GetDword();
6952 		tQuery.m_bHasOuter = ( tReq.GetInt()!=0 );
6953 	}
6954 
6955 	// extension v.1
6956 	tQuery.m_eCollation = g_eCollation;
6957 	if ( iMasterVer>=1 )
6958 		tQuery.m_eCollation = (ESphCollation)tReq.GetDword();
6959 
6960 	// extension v.2
6961 	if ( iMasterVer>=2 )
6962 	{
6963 		tQuery.m_sOuterOrderBy = tReq.GetString();
6964 		if ( tQuery.m_bHasOuter )
6965 			tQuery.m_iOuterLimit = tReq.GetInt();
6966 	}
6967 
6968 	if ( iMasterVer>=6 )
6969 	{
6970 		tQuery.m_iGroupbyLimit = tReq.GetInt();
6971 	}
6972 
6973 	if ( iMasterVer>=14 )
6974 	{
6975 		tQuery.m_sUDRanker = tReq.GetString();
6976 		tQuery.m_sUDRankerOpts = tReq.GetString();
6977 		tQuery.m_sQueryTokenFilterLib = tReq.GetString();
6978 		tQuery.m_sQueryTokenFilterName = tReq.GetString();
6979 		tQuery.m_sQueryTokenFilterOpts = tReq.GetString();
6980 	}
6981 
6982 	/////////////////////
6983 	// additional checks
6984 	/////////////////////
6985 
6986 	if ( tReq.GetError() )
6987 	{
6988 		tReq.SendErrorReply ( "invalid or truncated request" );
6989 		return false;
6990 	}
6991 
6992 	CSphString sError;
6993 	CheckQuery ( tQuery, sError );
6994 	if ( !sError.IsEmpty() )
6995 	{
6996 		tReq.SendErrorReply ( "%s", sError.cstr() );
6997 		return false;
6998 	}
6999 
7000 	// now prepare it for the engine
7001 	PrepareQueryEmulation ( &tQuery );
7002 
7003 	// all ok
7004 	return true;
7005 }
7006 
7007 //////////////////////////////////////////////////////////////////////////
7008 
LogQueryPlain(const CSphQuery & tQuery,const CSphQueryResult & tRes)7009 void LogQueryPlain ( const CSphQuery & tQuery, const CSphQueryResult & tRes )
7010 {
7011 	assert ( g_eLogFormat==LOG_FORMAT_PLAIN );
7012 	if ( ( !g_bQuerySyslog && g_iQueryLogFile<0 ) || !tRes.m_sError.IsEmpty() )
7013 		return;
7014 
7015 	CSphStringBuilder tBuf;
7016 
7017 	// [time]
7018 #if USE_SYSLOG
7019 	if ( !g_bQuerySyslog )
7020 	{
7021 #endif
7022 
7023 		char sTimeBuf[SPH_TIME_PID_MAX_SIZE];
7024 		sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
7025 		tBuf.Appendf ( "[%s]", sTimeBuf );
7026 
7027 #if USE_SYSLOG
7028 	} else
7029 		tBuf += "[query]";
7030 #endif
7031 
7032 	// querytime sec
7033 	int iQueryTime = Max ( tRes.m_iQueryTime, 0 );
7034 	int iRealTime = Max ( tRes.m_iRealQueryTime, 0 );
7035 	tBuf.Appendf ( " %d.%03d sec", iRealTime/1000, iRealTime%1000 );
7036 	tBuf.Appendf ( " %d.%03d sec", iQueryTime/1000, iQueryTime%1000 );
7037 
7038 	// optional multi-query multiplier
7039 	if ( tRes.m_iMultiplier>1 )
7040 		tBuf.Appendf ( " x%d", tRes.m_iMultiplier );
7041 
7042 	// [matchmode/numfilters/sortmode matches (offset,limit)
7043 	static const char * sModes [ SPH_MATCH_TOTAL ] = { "all", "any", "phr", "bool", "ext", "scan", "ext2" };
7044 	static const char * sSort [ SPH_SORT_TOTAL ] = { "rel", "attr-", "attr+", "tsegs", "ext", "expr" };
7045 	tBuf.Appendf ( " [%s/%d/%s " INT64_FMT " (%d,%d)",
7046 		sModes [ tQuery.m_eMode ], tQuery.m_dFilters.GetLength(), sSort [ tQuery.m_eSort ],
7047 		tRes.m_iTotalMatches, tQuery.m_iOffset, tQuery.m_iLimit );
7048 
7049 	// optional groupby info
7050 	if ( !tQuery.m_sGroupBy.IsEmpty() )
7051 		tBuf.Appendf ( " @%s", tQuery.m_sGroupBy.cstr() );
7052 
7053 	// ] [indexes]
7054 	tBuf.Appendf ( "] [%s]", tQuery.m_sIndexes.cstr() );
7055 
7056 	// optional performance counters
7057 	if ( g_bIOStats || g_bCpuStats )
7058 	{
7059 		const CSphIOStats & IOStats = tRes.m_tIOStats;
7060 
7061 		tBuf += " [";
7062 
7063 		if ( g_bIOStats )
7064 			tBuf.Appendf ( "ios=%d kb=%d.%d ioms=%d.%d",
7065 				IOStats.m_iReadOps, (int)( IOStats.m_iReadBytes/1024 ), (int)( IOStats.m_iReadBytes%1024 )*10/1024,
7066 				(int)( IOStats.m_iReadTime/1000 ), (int)( IOStats.m_iReadTime%1000 )/100 );
7067 
7068 		if ( g_bIOStats && g_bCpuStats )
7069 			tBuf += " ";
7070 
7071 		if ( g_bCpuStats )
7072 			tBuf.Appendf ( "cpums=%d.%d", (int)( tRes.m_iCpuTime/1000 ), (int)( tRes.m_iCpuTime%1000 )/100 );
7073 
7074 		tBuf += "]";
7075 	}
7076 
7077 	// optional query comment
7078 	if ( !tQuery.m_sComment.IsEmpty() )
7079 		tBuf.Appendf ( " [%s]", tQuery.m_sComment.cstr() );
7080 
7081 	// query
7082 	// (m_sRawQuery is empty when using MySQL handler)
7083 	const CSphString & sQuery = tQuery.m_sRawQuery.IsEmpty()
7084 		? tQuery.m_sQuery
7085 		: tQuery.m_sRawQuery;
7086 
7087 	if ( !sQuery.IsEmpty() )
7088 	{
7089 		tBuf += " ";
7090 		tBuf.AppendEscaped ( sQuery.cstr(), false, true );
7091 	}
7092 
7093 #if USE_SYSLOG
7094 	if ( !g_bQuerySyslog )
7095 	{
7096 #endif
7097 
7098 	// line feed
7099 	tBuf += "\n";
7100 
7101 	sphSeek ( g_iQueryLogFile, 0, SEEK_END );
7102 	sphWrite ( g_iQueryLogFile, tBuf.cstr(), tBuf.Length() );
7103 
7104 #if USE_SYSLOG
7105 	} else
7106 	{
7107 		syslog ( LOG_INFO, "%s", tBuf.cstr() );
7108 	}
7109 #endif
7110 }
7111 
7112 class UnBackquote_fn : public ISphNoncopyable
7113 {
7114 	CSphString m_sBuf;
7115 	const char * m_pDst;
7116 
7117 public:
UnBackquote_fn(const char * pSrc)7118 	explicit UnBackquote_fn ( const char * pSrc )
7119 	{
7120 		m_pDst = pSrc;
7121 		int iLen = 0;
7122 		if ( pSrc && *pSrc )
7123 			iLen = strlen ( pSrc );
7124 
7125 		if ( iLen && memchr ( pSrc, '`', iLen ) )
7126 		{
7127 			m_sBuf = pSrc;
7128 			char * pDst = const_cast<char *>( m_sBuf.cstr() );
7129 			const char * pEnd = pSrc + iLen;
7130 
7131 			while ( pSrc<pEnd )
7132 			{
7133 				*pDst = *pSrc++;
7134 				if ( *pDst!='`' )
7135 					pDst++;
7136 			}
7137 			*pDst = '\0';
7138 			m_pDst = m_sBuf.cstr();
7139 		}
7140 	}
7141 
cstr()7142 	const char * cstr() { return m_pDst; }
7143 };
7144 
FormatOrderBy(CSphStringBuilder * pBuf,const char * sPrefix,ESphSortOrder eSort,const CSphString & sSort)7145 static void FormatOrderBy ( CSphStringBuilder * pBuf, const char * sPrefix, ESphSortOrder eSort, const CSphString & sSort )
7146 {
7147 	assert ( pBuf );
7148 	if ( eSort==SPH_SORT_EXTENDED && sSort=="@weight desc" )
7149 		return;
7150 
7151 	const char * sSubst = "@weight";
7152 	if ( sSort!="@relevance" )
7153 			sSubst = sSort.cstr();
7154 
7155 	UnBackquote_fn tUnquoted ( sSubst );
7156 	sSubst = tUnquoted.cstr();
7157 
7158 	switch ( eSort )
7159 	{
7160 	case SPH_SORT_ATTR_DESC:		pBuf->Appendf ( " %s %s DESC", sPrefix, sSubst ); break;
7161 	case SPH_SORT_ATTR_ASC:			pBuf->Appendf ( " %s %s ASC", sPrefix, sSubst ); break;
7162 	case SPH_SORT_TIME_SEGMENTS:	pBuf->Appendf ( " %s TIME_SEGMENT(%s)", sPrefix, sSubst ); break;
7163 	case SPH_SORT_EXTENDED:			pBuf->Appendf ( " %s %s", sPrefix, sSubst ); break;
7164 	case SPH_SORT_EXPR:				pBuf->Appendf ( " %s BUILTIN_EXPR()", sPrefix ); break;
7165 	case SPH_SORT_RELEVANCE:		pBuf->Appendf ( " %s weight() desc%s%s", sPrefix, ( sSubst && *sSubst ? ", " : "" ), ( sSubst && *sSubst ? sSubst : "" ) ); break;
7166 	default:						pBuf->Appendf ( " %s mode-%d", sPrefix, (int)eSort ); break;
7167 	}
7168 }
7169 
FormatFilter(CSphStringBuilder & tBuf,const CSphFilterSettings & f)7170 static void FormatFilter ( CSphStringBuilder & tBuf, const CSphFilterSettings & f )
7171 {
7172 	switch ( f.m_eType )
7173 	{
7174 		case SPH_FILTER_VALUES:
7175 			if ( f.m_dValues.GetLength()==1 )
7176 			{
7177 				if ( f.m_bExclude )
7178 					tBuf.Appendf ( " %s!=" INT64_FMT, f.m_sAttrName.cstr(), (int64_t)f.m_dValues[0] );
7179 				else
7180 					tBuf.Appendf ( " %s=" INT64_FMT, f.m_sAttrName.cstr(), (int64_t)f.m_dValues[0] );
7181 			} else
7182 			{
7183 				if ( f.m_bExclude )
7184 					tBuf.Appendf ( " %s NOT IN (", f.m_sAttrName.cstr() );
7185 				else
7186 					tBuf.Appendf ( " %s IN (", f.m_sAttrName.cstr() );
7187 
7188 				if ( g_bShortenIn && ( SHORTEN_IN_LIMIT+1<f.m_dValues.GetLength() ) )
7189 				{
7190 					// for really long IN-lists optionally format them as N,N,N,N,...N,N,N, with ellipsis inside.
7191 					int iLimit = SHORTEN_IN_LIMIT-3;
7192 					for ( int j=0; j<iLimit; ++j )
7193 					{
7194 						if ( j )
7195 							tBuf.Appendf ( "," INT64_FMT, (int64_t)f.m_dValues[j] );
7196 						else
7197 							tBuf.Appendf ( INT64_FMT, (int64_t)f.m_dValues[j] );
7198 					}
7199 					iLimit = f.m_dValues.GetLength();
7200 					tBuf.Appendf ( "%s", ",..." );
7201 					for ( int j=iLimit-3; j<iLimit; ++j )
7202 					{
7203 						if ( j )
7204 							tBuf.Appendf ( "," INT64_FMT, (int64_t)f.m_dValues[j] );
7205 						else
7206 							tBuf.Appendf ( INT64_FMT, (int64_t)f.m_dValues[j] );
7207 					}
7208 
7209 				} else
7210 					ARRAY_FOREACH ( j, f.m_dValues )
7211 					{
7212 						if ( j )
7213 							tBuf.Appendf ( "," INT64_FMT, (int64_t)f.m_dValues[j] );
7214 						else
7215 							tBuf.Appendf ( INT64_FMT, (int64_t)f.m_dValues[j] );
7216 					}
7217 				tBuf += ")";
7218 			}
7219 			break;
7220 
7221 		case SPH_FILTER_RANGE:
7222 			if ( f.m_iMinValue==int64_t(INT64_MIN) || ( f.m_iMinValue==0 && f.m_sAttrName=="@id" ) )
7223 			{
7224 				// no min, thus (attr<maxval)
7225 				const char * sOps[2][2] = { { "<", "<=" }, { ">=", ">" } };
7226 				tBuf.Appendf ( " %s%s" INT64_FMT, f.m_sAttrName.cstr(),
7227 					sOps [ f.m_bExclude ][ f.m_bHasEqual ], f.m_iMaxValue );
7228 			} else if ( f.m_iMaxValue==INT64_MAX || ( f.m_iMaxValue==-1 && f.m_sAttrName=="@id" ) )
7229 			{
7230 				// mo max, thus (attr>minval)
7231 				const char * sOps[2][2] = { { ">", ">=" }, { "<", "<=" } };
7232 				tBuf.Appendf ( " %s%s" INT64_FMT, f.m_sAttrName.cstr(),
7233 					sOps [ f.m_bExclude ][ f.m_bHasEqual ], f.m_iMinValue );
7234 			} else
7235 			{
7236 				tBuf.Appendf ( " %s%s BETWEEN " INT64_FMT " AND " INT64_FMT,
7237 					f.m_sAttrName.cstr(), f.m_bExclude ? " NOT" : "",
7238 					f.m_iMinValue + !f.m_bHasEqual, f.m_iMaxValue - !f.m_bHasEqual );
7239 			}
7240 			break;
7241 
7242 		case SPH_FILTER_FLOATRANGE:
7243 			if ( f.m_fMinValue==-FLT_MAX )
7244 			{
7245 				// no min, thus (attr<maxval)
7246 				const char * sOps[2][2] = { { "<", "<=" }, { ">=", ">" } };
7247 				tBuf.Appendf ( " %s%s%f", f.m_sAttrName.cstr(),
7248 					sOps [ f.m_bExclude ][ f.m_bHasEqual ], f.m_fMaxValue );
7249 			} else if ( f.m_fMaxValue==FLT_MAX )
7250 			{
7251 				// mo max, thus (attr>minval)
7252 				const char * sOps[2][2] = { { ">", ">=" }, { "<", "<=" } };
7253 				tBuf.Appendf ( " %s%s%f", f.m_sAttrName.cstr(),
7254 					sOps [ f.m_bExclude ][ f.m_bHasEqual ], f.m_fMinValue );
7255 			} else
7256 			{
7257 				// FIXME? need we handle m_bHasEqual here?
7258 				tBuf.Appendf ( " %s%s BETWEEN %f AND %f",
7259 					f.m_sAttrName.cstr(), f.m_bExclude ? " NOT" : "",
7260 					f.m_fMinValue, f.m_fMaxValue );
7261 			}
7262 			break;
7263 
7264 		case SPH_FILTER_USERVAR:
7265 		case SPH_FILTER_STRING:
7266 			tBuf.Appendf ( " %s%s'%s'", f.m_sAttrName.cstr(), ( f.m_bHasEqual ? "=" : "!=" ), ( f.m_dStrings.GetLength()==1 ? f.m_dStrings[0].cstr() : "" ) );
7267 			break;
7268 
7269 		case SPH_FILTER_NULL:
7270 			tBuf.Appendf ( " %s %s", f.m_sAttrName.cstr(), ( f.m_bHasEqual ? "IS NULL" : "IS NOT NULL" ) );
7271 			break;
7272 
7273 		case SPH_FILTER_STRING_LIST:
7274 			tBuf.Appendf ( " %s IN (", f.m_sAttrName.cstr () );
7275 			ARRAY_FOREACH ( iString, f.m_dStrings )
7276 				tBuf.Appendf ( "%s'%s'", ( iString>0 ? "," : "" ), f.m_dStrings[iString].cstr() );
7277 			tBuf.Appendf ( ")" );
7278 			break;
7279 
7280 		default:
7281 			tBuf += " 1 /""* oops, unknown filter type *""/";
7282 			break;
7283 	}
7284 }
7285 
7286 static const CSphQuery g_tDefaultQuery;
7287 
FormatList(const CSphVector<CSphNamedInt> & dValues,CSphStringBuilder & tBuf)7288 static void FormatList ( const CSphVector<CSphNamedInt> & dValues, CSphStringBuilder & tBuf )
7289 {
7290 	ARRAY_FOREACH ( i, dValues )
7291 		tBuf.Appendf ( "%s%s=%d", i==0 ? "" : ", ", dValues[i].m_sName.cstr(), dValues[i].m_iValue );
7292 }
7293 
FormatOption(const CSphQuery & tQuery,CSphStringBuilder & tBuf)7294 static void FormatOption ( const CSphQuery & tQuery, CSphStringBuilder & tBuf )
7295 {
7296 	int iOpts = 0;
7297 
7298 	if ( tQuery.m_iMaxMatches!=DEFAULT_MAX_MATCHES )
7299 	{
7300 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7301 		tBuf.Appendf ( "max_matches=%d", tQuery.m_iMaxMatches );
7302 	}
7303 
7304 	if ( !tQuery.m_sComment.IsEmpty() )
7305 	{
7306 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7307 		tBuf.Appendf ( "comment='%s'", tQuery.m_sComment.cstr() ); // FIXME! escape, replace newlines..
7308 	}
7309 
7310 	if ( tQuery.m_eRanker!=SPH_RANK_DEFAULT )
7311 	{
7312 		const char * sRanker = sphGetRankerName ( tQuery.m_eRanker );
7313 		if ( !sRanker )
7314 			sRanker = sphGetRankerName ( SPH_RANK_DEFAULT );
7315 
7316 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7317 		tBuf.Appendf ( "ranker=%s", sRanker );
7318 
7319 		if ( !tQuery.m_sRankerExpr.IsEmpty() )
7320 			tBuf.Appendf ( "(\'%s\')", tQuery.m_sRankerExpr.scstr() );
7321 	}
7322 
7323 	if ( tQuery.m_iAgentQueryTimeout!=g_iAgentQueryTimeout )
7324 	{
7325 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7326 		tBuf.Appendf ( "agent_query_timeout=%d", tQuery.m_iAgentQueryTimeout );
7327 	}
7328 
7329 	if ( tQuery.m_iCutoff!=g_tDefaultQuery.m_iCutoff )
7330 	{
7331 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7332 		tBuf.Appendf ( "cutoff=%d", tQuery.m_iCutoff );
7333 	}
7334 
7335 	if ( tQuery.m_dFieldWeights.GetLength() )
7336 	{
7337 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7338 		tBuf.Appendf ( "field_weights=(" );
7339 		FormatList ( tQuery.m_dFieldWeights, tBuf );
7340 		tBuf.Appendf ( ")" );
7341 	}
7342 
7343 	if ( tQuery.m_bGlobalIDF!=g_tDefaultQuery.m_bGlobalIDF )
7344 	{
7345 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7346 		tBuf.Appendf ( "global_idf=1" );
7347 	}
7348 
7349 	if ( tQuery.m_bPlainIDF || !tQuery.m_bNormalizedTFIDF )
7350 	{
7351 		const char * sIDF1 = ( tQuery.m_bPlainIDF ? "plain" : "normalized" );
7352 		const char * sIDF2 = ( tQuery.m_bNormalizedTFIDF ? "tfidf_normalized" : "tfidf_unnormalized" );
7353 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7354 		tBuf.Appendf ( "idf='%s,%s'", sIDF1, sIDF2 );
7355 	}
7356 
7357 	if ( tQuery.m_bLocalDF!=g_tDefaultQuery.m_bLocalDF )
7358 	{
7359 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7360 		tBuf.Appendf ( "local_df=1" );
7361 	}
7362 
7363 	if ( tQuery.m_dIndexWeights.GetLength() )
7364 	{
7365 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7366 		tBuf.Appendf ( "index_weights=(" );
7367 		FormatList ( tQuery.m_dIndexWeights, tBuf );
7368 		tBuf.Appendf ( ")" );
7369 	}
7370 
7371 	if ( tQuery.m_uMaxQueryMsec!=g_tDefaultQuery.m_uMaxQueryMsec )
7372 	{
7373 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7374 		tBuf.Appendf ( "max_query_time=%u", tQuery.m_uMaxQueryMsec );
7375 	}
7376 
7377 	if ( tQuery.m_iMaxPredictedMsec!=g_tDefaultQuery.m_iMaxPredictedMsec )
7378 	{
7379 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7380 		tBuf.Appendf ( "max_predicted_time=%d", tQuery.m_iMaxPredictedMsec );
7381 	}
7382 
7383 	if ( tQuery.m_iRetryCount!=g_iAgentRetryCount )
7384 	{
7385 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7386 		tBuf.Appendf ( "retry_count=%d", tQuery.m_iRetryCount );
7387 	}
7388 
7389 	if ( tQuery.m_iRetryDelay!=g_iAgentRetryDelay )
7390 	{
7391 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7392 		tBuf.Appendf ( "retry_delay=%d", tQuery.m_iRetryDelay );
7393 	}
7394 
7395 	if ( tQuery.m_iRandSeed!=g_tDefaultQuery.m_iRandSeed )
7396 	{
7397 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7398 		tBuf.Appendf ( "rand_seed=" INT64_FMT, tQuery.m_iRandSeed );
7399 	}
7400 
7401 	if ( !tQuery.m_sQueryTokenFilterLib.IsEmpty() )
7402 	{
7403 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7404 		if ( tQuery.m_sQueryTokenFilterOpts.IsEmpty() )
7405 			tBuf.Appendf ( "token_filter = '%s:%s'", tQuery.m_sQueryTokenFilterLib.cstr(), tQuery.m_sQueryTokenFilterName.cstr() );
7406 		else
7407 			tBuf.Appendf ( "token_filter = '%s:%s:%s'", tQuery.m_sQueryTokenFilterLib.cstr(), tQuery.m_sQueryTokenFilterName.cstr(), tQuery.m_sQueryTokenFilterOpts.cstr() );
7408 	}
7409 
7410 	if ( tQuery.m_bIgnoreNonexistent )
7411 	{
7412 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7413 		tBuf.Appendf ( "ignore_nonexistent_columns=1" );
7414 	}
7415 
7416 	if ( tQuery.m_bIgnoreNonexistentIndexes )
7417 	{
7418 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7419 		tBuf.Appendf ( "ignore_nonexistent_indexes=1" );
7420 	}
7421 
7422 	if ( tQuery.m_bStrict )
7423 	{
7424 		tBuf.Appendf ( iOpts++ ? ", " : " OPTION " );
7425 		tBuf.Appendf ( "strict=1" );
7426 	}
7427 }
7428 
7429 
LogQuerySphinxql(const CSphQuery & q,const CSphQueryResult & tRes,const CSphVector<int64_t> & dAgentTimes)7430 static void LogQuerySphinxql ( const CSphQuery & q, const CSphQueryResult & tRes, const CSphVector<int64_t> & dAgentTimes )
7431 {
7432 	assert ( g_eLogFormat==LOG_FORMAT_SPHINXQL );
7433 	if ( g_iQueryLogFile<0 )
7434 		return;
7435 
7436 	CSphStringBuilder tBuf;
7437 
7438 	// get connection id
7439 	int iCid = ( g_eWorkers!=MPM_THREADS ) ? g_iConnID : *(int*) sphThreadGet ( g_tConnKey );
7440 
7441 	// time, conn id, wall, found
7442 	int iQueryTime = Max ( tRes.m_iQueryTime, 0 );
7443 	int iRealTime = Max ( tRes.m_iRealQueryTime, 0 );
7444 
7445 	char sTimeBuf[SPH_TIME_PID_MAX_SIZE];
7446 	sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
7447 
7448 	tBuf += "/""* ";
7449 	tBuf += sTimeBuf;
7450 
7451 	if ( tRes.m_iMultiplier>1 )
7452 		tBuf.Appendf ( " conn %d real %d.%03d wall %d.%03d x%d found " INT64_FMT " *""/ ",
7453 			iCid, iRealTime/1000, iRealTime%1000, iQueryTime/1000, iQueryTime%1000, tRes.m_iMultiplier, tRes.m_iTotalMatches );
7454 	else
7455 		tBuf.Appendf ( " conn %d real %d.%03d wall %d.%03d found " INT64_FMT " *""/ ",
7456 			iCid, iRealTime/1000, iRealTime%1000, iQueryTime/1000, iQueryTime%1000, tRes.m_iTotalMatches );
7457 
7458 	///////////////////////////////////
7459 	// format request as SELECT query
7460 	///////////////////////////////////
7461 
7462 	if ( q.m_bHasOuter )
7463 		tBuf += "SELECT * FROM (";
7464 
7465 	UnBackquote_fn tUnquoted ( q.m_sSelect.cstr() );
7466 	tBuf.Appendf ( "SELECT %s FROM %s", tUnquoted.cstr(), q.m_sIndexes.cstr() );
7467 
7468 	// WHERE clause
7469 	// (m_sRawQuery is empty when using MySQL handler)
7470 	const CSphString & sQuery = q.m_sQuery;
7471 	if ( !sQuery.IsEmpty() || q.m_dFilters.GetLength() )
7472 	{
7473 		bool bDeflowered = false;
7474 
7475 		tBuf += " WHERE";
7476 		if ( !sQuery.IsEmpty() )
7477 		{
7478 			tBuf += " MATCH('";
7479 			tBuf.AppendEscaped ( sQuery.cstr() );
7480 			tBuf += "')";
7481 			bDeflowered = true;
7482 		}
7483 
7484 		ARRAY_FOREACH ( i, q.m_dFilters )
7485 		{
7486 			if ( bDeflowered )
7487 				tBuf += " AND";
7488 			bDeflowered = true;
7489 
7490 			const CSphFilterSettings & f = q.m_dFilters[i];
7491 			FormatFilter ( tBuf, f );
7492 		}
7493 	}
7494 
7495 	// ORDER BY and/or GROUP BY clause
7496 	if ( q.m_sGroupBy.IsEmpty() )
7497 	{
7498 		if ( !q.m_sSortBy.IsEmpty() ) // case API SPH_MATCH_EXTENDED2 - SPH_SORT_RELEVANCE
7499 			FormatOrderBy ( &tBuf, " ORDER BY", q.m_eSort, q.m_sSortBy );
7500 	} else
7501 	{
7502 		tBuf.Appendf ( " GROUP BY %s", q.m_sGroupBy.cstr() );
7503 		FormatOrderBy ( &tBuf, "WITHIN GROUP ORDER BY", q.m_eSort, q.m_sSortBy );
7504 		if ( !q.m_tHaving.m_sAttrName.IsEmpty() )
7505 		{
7506 			tBuf += " HAVING";
7507 			FormatFilter ( tBuf, q.m_tHaving );
7508 		}
7509 		if ( q.m_sGroupSortBy!="@group desc" )
7510 			FormatOrderBy ( &tBuf, "ORDER BY", SPH_SORT_EXTENDED, q.m_sGroupSortBy );
7511 	}
7512 
7513 	// LIMIT clause
7514 	if ( q.m_iOffset!=0 || q.m_iLimit!=20 )
7515 		tBuf.Appendf ( " LIMIT %d,%d", q.m_iOffset, q.m_iLimit );
7516 
7517 	// OPTION clause
7518 	FormatOption ( q, tBuf );
7519 
7520 	// outer order by, limit
7521 	if ( q.m_bHasOuter )
7522 	{
7523 		tBuf += ")";
7524 		if ( !q.m_sOuterOrderBy.IsEmpty() )
7525 			tBuf.Appendf ( " ORDER BY %s", q.m_sOuterOrderBy.cstr() );
7526 		if ( q.m_iOuterOffset>0 )
7527 			tBuf.Appendf ( " LIMIT %d, %d", q.m_iOuterOffset, q.m_iOuterLimit );
7528 		else if ( q.m_iOuterLimit>0 )
7529 			tBuf.Appendf ( " LIMIT %d", q.m_iOuterLimit );
7530 	}
7531 
7532 	// finish SQL statement
7533 	tBuf += ";";
7534 
7535 	///////////////
7536 	// query stats
7537 	///////////////
7538 
7539 	if ( !tRes.m_sError.IsEmpty() )
7540 	{
7541 		// all we have is an error
7542 		tBuf.Appendf ( " /""* error=%s */", tRes.m_sError.cstr() );
7543 
7544 	} else if ( g_bIOStats || g_bCpuStats || dAgentTimes.GetLength() || !tRes.m_sWarning.IsEmpty() )
7545 	{
7546 		// got some extra data, add a comment
7547 		tBuf += " /""*";
7548 
7549 		// performance counters
7550 		if ( g_bIOStats || g_bCpuStats )
7551 		{
7552 			const CSphIOStats & IOStats = tRes.m_tIOStats;
7553 
7554 			if ( g_bIOStats )
7555 				tBuf.Appendf ( " ios=%d kb=%d.%d ioms=%d.%d",
7556 				IOStats.m_iReadOps, (int)( IOStats.m_iReadBytes/1024 ), (int)( IOStats.m_iReadBytes%1024 )*10/1024,
7557 				(int)( IOStats.m_iReadTime/1000 ), (int)( IOStats.m_iReadTime%1000 )/100 );
7558 
7559 			if ( g_bCpuStats )
7560 				tBuf.Appendf ( " cpums=%d.%d", (int)( tRes.m_iCpuTime/1000 ), (int)( tRes.m_iCpuTime%1000 )/100 );
7561 		}
7562 
7563 		// per-agent times
7564 		if ( dAgentTimes.GetLength() )
7565 		{
7566 			tBuf += " agents=(";
7567 			ARRAY_FOREACH ( i, dAgentTimes )
7568 				tBuf.Appendf ( i ? ", %d.%03d" : "%d.%03d",
7569 					(int)(dAgentTimes[i]/1000),
7570 					(int)(dAgentTimes[i]%1000) );
7571 
7572 			tBuf += ")";
7573 		}
7574 
7575 		// warning
7576 		if ( !tRes.m_sWarning.IsEmpty() )
7577 			tBuf.Appendf ( " warning=%s", tRes.m_sWarning.cstr() );
7578 
7579 		// close the comment
7580 		tBuf += " */";
7581 	}
7582 
7583 	// line feed
7584 	tBuf += "\n";
7585 
7586 	sphSeek ( g_iQueryLogFile, 0, SEEK_END );
7587 	sphWrite ( g_iQueryLogFile, tBuf.cstr(), tBuf.Length() );
7588 }
7589 
7590 
LogQuery(const CSphQuery & q,const CSphQueryResult & tRes,const CSphVector<int64_t> & dAgentTimes)7591 static void LogQuery ( const CSphQuery & q, const CSphQueryResult & tRes, const CSphVector<int64_t> & dAgentTimes )
7592 {
7593 	if ( g_iQueryLogMinMs && tRes.m_iQueryTime<g_iQueryLogMinMs )
7594 		return;
7595 
7596 	switch ( g_eLogFormat )
7597 	{
7598 		case LOG_FORMAT_PLAIN:		LogQueryPlain ( q, tRes ); break;
7599 		case LOG_FORMAT_SPHINXQL:	LogQuerySphinxql ( q, tRes, dAgentTimes ); break;
7600 	}
7601 }
7602 
7603 
LogSphinxqlError(const char * sStmt,const char * sError)7604 static void LogSphinxqlError ( const char * sStmt, const char * sError )
7605 {
7606 	if ( g_eLogFormat!=LOG_FORMAT_SPHINXQL || g_iQueryLogFile<0 || !sStmt || !sError )
7607 		return;
7608 
7609 	// time, conn id, query, error
7610 	CSphStringBuilder tBuf;
7611 
7612 	int iCid = ( g_eWorkers!=MPM_THREADS ) ? g_iConnID : *(int*) sphThreadGet ( g_tConnKey );
7613 
7614 	char sTimeBuf[SPH_TIME_PID_MAX_SIZE];
7615 	sphFormatCurrentTime ( sTimeBuf, sizeof(sTimeBuf) );
7616 
7617 	tBuf += "/""* ";
7618 	tBuf += sTimeBuf;
7619 	tBuf.Appendf ( " conn %d *""/ %s # error=%s\n", iCid, sStmt, sError );
7620 
7621 	sphSeek ( g_iQueryLogFile, 0, SEEK_END );
7622 	sphWrite ( g_iQueryLogFile, tBuf.cstr(), tBuf.Length() );
7623 }
7624 
7625 //////////////////////////////////////////////////////////////////////////
7626 
7627 // internals attributes are last no need to send them
SendGetAttrCount(const ISphSchema & tSchema,bool bAgentMode=false)7628 static int SendGetAttrCount ( const ISphSchema & tSchema, bool bAgentMode=false )
7629 {
7630 	int iCount = tSchema.GetAttrsCount();
7631 
7632 	if ( bAgentMode )
7633 		return iCount;
7634 
7635 	if ( iCount && sphIsSortStringInternal ( tSchema.GetAttr ( iCount-1 ).m_sName.cstr() ) )
7636 	{
7637 		for ( int i=iCount-1; i>=0 && sphIsSortStringInternal ( tSchema.GetAttr(i).m_sName.cstr() ); i-- )
7638 		{
7639 			iCount = i;
7640 		}
7641 	}
7642 
7643 	return iCount;
7644 }
7645 
7646 class CSphTaggedVector
7647 {
7648 public:
operator [](int iTag) const7649 	const PoolPtrs_t & operator [] ( int iTag ) const
7650 	{
7651 		return m_dPool [ iTag & 0x7FFFFFF ];
7652 	}
operator [](int iTag)7653 	PoolPtrs_t & operator [] ( int iTag )
7654 	{
7655 		return m_dPool [ iTag & 0x7FFFFFF ];
7656 	}
7657 
Resize(int iSize)7658 	void Resize ( int iSize )
7659 	{
7660 		m_dPool.Resize ( iSize );
7661 	}
7662 
7663 private:
7664 	CSphVector<PoolPtrs_t> m_dPool;
7665 };
7666 
7667 static char g_sJsonNull[] = "{}";
7668 
CalcResultLength(int iVer,const CSphQueryResult * pRes,const CSphTaggedVector & dTag2Pools,bool bAgentMode,const CSphQuery & tQuery,int iMasterVer)7669 int CalcResultLength ( int iVer, const CSphQueryResult * pRes, const CSphTaggedVector & dTag2Pools, bool bAgentMode, const CSphQuery & tQuery, int iMasterVer )
7670 {
7671 	int iRespLen = 0;
7672 
7673 	// query status
7674 	if ( iVer>=0x10D )
7675 	{
7676 		// multi-query status
7677 		iRespLen += 4; // status code
7678 
7679 		if ( !pRes->m_sError.IsEmpty() )
7680 			return iRespLen + 4 +strlen ( pRes->m_sError.cstr() );
7681 
7682 		if ( !pRes->m_sWarning.IsEmpty() )
7683 			iRespLen += 4+strlen ( pRes->m_sWarning.cstr() );
7684 
7685 	} else if ( iVer>=0x106 )
7686 	{
7687 		// warning message
7688 		if ( !pRes->m_sWarning.IsEmpty() )
7689 			iRespLen += 4 + strlen ( pRes->m_sWarning.cstr() );
7690 	}
7691 
7692 	// query stats
7693 	iRespLen += 20;
7694 
7695 	int iAttrsCount = SendGetAttrCount ( pRes->m_tSchema, bAgentMode );
7696 
7697 	// schema
7698 	if ( iVer>=0x102 )
7699 	{
7700 		iRespLen += 8; // 4 for field count, 4 for attr count
7701 		ARRAY_FOREACH ( i, pRes->m_tSchema.m_dFields )
7702 			iRespLen += 4 + strlen ( pRes->m_tSchema.m_dFields[i].m_sName.cstr() ); // namelen, name
7703 		for ( int i=0; i<iAttrsCount; i++ )
7704 			iRespLen += 8 + strlen ( pRes->m_tSchema.GetAttr(i).m_sName.cstr() ); // namelen, name, type
7705 	}
7706 
7707 	// matches
7708 	if ( iVer<0x102 )
7709 		iRespLen += 16*pRes->m_iCount; // matches
7710 	else if ( iVer<0x108 )
7711 		iRespLen += ( 8+4*iAttrsCount )*pRes->m_iCount; // matches
7712 	else
7713 		iRespLen += 4 + ( 8+4*USE_64BIT+4*iAttrsCount )*pRes->m_iCount; // id64 tag and matches
7714 
7715 	if ( iVer>=0x114 )
7716 	{
7717 		// 64bit matches
7718 		int iWideAttrs = 0;
7719 		for ( int i=0; i<iAttrsCount; i++ )
7720 			if ( pRes->m_tSchema.GetAttr(i).m_eAttrType==SPH_ATTR_BIGINT )
7721 				iWideAttrs++;
7722 		iRespLen += 4*pRes->m_iCount*iWideAttrs; // extra 4 bytes per attr per match
7723 	}
7724 
7725 	// agents send additional flag from words statistics
7726 	if ( bAgentMode )
7727 		iRespLen += pRes->m_hWordStats.GetLength();
7728 
7729 	pRes->m_hWordStats.IterateStart();
7730 	while ( pRes->m_hWordStats.IterateNext() ) // per-word stats
7731 		iRespLen += 12 + strlen ( pRes->m_hWordStats.IterateGetKey().cstr() ); // wordlen, word, docs, hits
7732 
7733 	bool bSendJson = ( bAgentMode && iMasterVer>=3 );
7734 	bool bSendJsonField = ( bAgentMode && iMasterVer>=4 );
7735 
7736 	// all pooled values
7737 	CSphVector<CSphAttrLocator> dMvaItems;
7738 	CSphVector<CSphAttrLocator> dStringItems;
7739 	CSphVector<CSphAttrLocator> dStringPtrItems;
7740 	CSphVector<CSphAttrLocator> dJsonItems;
7741 	CSphVector<CSphAttrLocator> dJsonFieldsItems;
7742 	CSphVector<CSphAttrLocator> dFactorItems;
7743 	for ( int i=0; i<iAttrsCount; i++ )
7744 	{
7745 		const CSphColumnInfo & tCol = pRes->m_tSchema.GetAttr(i);
7746 		switch ( tCol.m_eAttrType )
7747 		{
7748 		case SPH_ATTR_UINT32SET:
7749 		case SPH_ATTR_INT64SET:
7750 			dMvaItems.Add ( tCol.m_tLocator );
7751 			break;
7752 		case SPH_ATTR_STRING:
7753 			dStringItems.Add ( tCol.m_tLocator );
7754 			break;
7755 		case SPH_ATTR_STRINGPTR:
7756 			dStringPtrItems.Add ( tCol.m_tLocator );
7757 			break;
7758 		case SPH_ATTR_JSON:
7759 			dJsonItems.Add ( tCol.m_tLocator );
7760 			break;
7761 		case SPH_ATTR_JSON_FIELD:
7762 			dJsonFieldsItems.Add ( tCol.m_tLocator );
7763 			break;
7764 		case SPH_ATTR_FACTORS:
7765 		case SPH_ATTR_FACTORS_JSON:
7766 			dFactorItems.Add ( tCol.m_tLocator );
7767 			break;
7768 		default:
7769 			break;
7770 		}
7771 	}
7772 
7773 	if ( iVer>=0x10C && dMvaItems.GetLength() )
7774 	{
7775 		for ( int i=0; i<pRes->m_iCount; i++ )
7776 		{
7777 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7778 			const DWORD * pMvaPool = dTag2Pools [ tMatch.m_iTag ].m_pMva;
7779 			bool bArenaProhibit = dTag2Pools[tMatch.m_iTag].m_bArenaProhibit;
7780 			ARRAY_FOREACH ( j, dMvaItems )
7781 			{
7782 				assert ( tMatch.GetAttr ( dMvaItems[j] )==0 || pMvaPool );
7783 				const DWORD * pMva = tMatch.GetAttrMVA ( dMvaItems[j], pMvaPool, bArenaProhibit );
7784 				if ( pMva )
7785 					iRespLen += pMva[0]*4; // FIXME? maybe add some sanity check here
7786 			}
7787 		}
7788 	}
7789 
7790 	if ( iVer>=0x117 && dStringItems.GetLength() )
7791 	{
7792 		for ( int i=0; i<pRes->m_iCount; i++ )
7793 		{
7794 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7795 			const BYTE * pStrings = dTag2Pools [ tMatch.m_iTag ].m_pStrings;
7796 			ARRAY_FOREACH ( j, dStringItems )
7797 			{
7798 				DWORD uOffset = (DWORD) tMatch.GetAttr ( dStringItems[j] );
7799 				assert ( !uOffset || pStrings );
7800 				if ( uOffset ) // magic zero
7801 					iRespLen += sphUnpackStr ( pStrings+uOffset, NULL );
7802 			}
7803 		}
7804 	}
7805 
7806 	if ( iVer>=0x11A && bAgentMode )
7807 	{
7808 		iRespLen += 1;			// stats mask
7809 		if ( g_bIOStats )
7810 			iRespLen += 40;		// IO Stats
7811 
7812 		if ( g_bCpuStats )
7813 			iRespLen += 8;		// CPU Stats
7814 
7815 		if ( tQuery.m_iMaxPredictedMsec > 0 )
7816 			iRespLen += 8;		// predicted time
7817 	}
7818 	// fetched_docs and fetched_hits from agent to master
7819 	if ( bAgentMode && iMasterVer>=7 )
7820 		iRespLen += 12;
7821 
7822 	if ( iVer>=0x117 && dStringPtrItems.GetLength() )
7823 	{
7824 		for ( int i=0; i<pRes->m_iCount; i++ )
7825 		{
7826 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7827 			ARRAY_FOREACH ( j, dStringPtrItems )
7828 			{
7829 				const char* pStr = (const char*) tMatch.GetAttr ( dStringPtrItems[j] );
7830 				if ( pStr )
7831 					iRespLen += strlen ( pStr );
7832 			}
7833 		}
7834 	}
7835 
7836 	if ( iVer>=0x117 && dJsonItems.GetLength() )
7837 	{
7838 		CSphVector<BYTE> dJson ( 512 );
7839 		// to master pass JSON as raw data
7840 		for ( int i=0; i<pRes->m_iCount; i++ )
7841 		{
7842 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7843 			const BYTE * pPool = dTag2Pools [ tMatch.m_iTag ].m_pStrings;
7844 			ARRAY_FOREACH ( j, dJsonItems )
7845 			{
7846 				DWORD uOffset = (DWORD) tMatch.GetAttr ( dJsonItems[j] );
7847 				assert ( !uOffset || pPool );
7848 				if ( !uOffset ) // magic zero
7849 				{
7850 					if ( !bSendJson ) // for client JSON magic zero is {}
7851 						iRespLen += sizeof(g_sJsonNull)-1;
7852 					continue;
7853 				}
7854 
7855 				const BYTE * pStr = NULL;
7856 				int iRawLen = sphUnpackStr ( pPool + uOffset, &pStr );
7857 
7858 				if ( bSendJson )
7859 				{
7860 					iRespLen += iRawLen;
7861 				} else
7862 				{
7863 					dJson.Resize ( 0 );
7864 					sphJsonFormat ( dJson, pStr );
7865 					iRespLen += dJson.GetLength();
7866 				}
7867 			}
7868 		}
7869 	}
7870 
7871 	if ( iVer>=0x117 && dJsonFieldsItems.GetLength() )
7872 	{
7873 		CSphVector<BYTE> dJson ( 512 );
7874 
7875 		for ( int i=0; i<pRes->m_iCount; i++ )
7876 		{
7877 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7878 			const BYTE * pStrings = dTag2Pools [ tMatch.m_iTag ].m_pStrings;
7879 			ARRAY_FOREACH ( j, dJsonFieldsItems )
7880 			{
7881 				// sizeof(DWORD) count already
7882 				uint64_t uTypeOffset = tMatch.GetAttr ( dJsonFieldsItems[j] );
7883 				assert ( !uTypeOffset || pStrings );
7884 				if ( !uTypeOffset ) // magic zero
7885 				{
7886 					if ( bSendJsonField )
7887 						iRespLen -= 3; // agent sends to master JSON type as BYTE
7888 					continue;
7889 				}
7890 
7891 				ESphJsonType eJson = ESphJsonType ( uTypeOffset>>32 );
7892 				DWORD uOff = (DWORD)uTypeOffset;
7893 				if ( bSendJsonField )
7894 				{
7895 					const BYTE * pData = pStrings+uOff;
7896 					iRespLen -= 3; // JSON type as BYTE
7897 					iRespLen += sphJsonNodeSize ( eJson, pData );
7898 					if ( sphJsonNodeSize ( eJson, NULL )<0 )
7899 						iRespLen += 4;
7900 				} else
7901 				{
7902 					// to client send as string
7903 					dJson.Resize ( 0 );
7904 					sphJsonFieldFormat ( dJson, pStrings+uOff, eJson, false );
7905 					iRespLen += dJson.GetLength();
7906 				}
7907 			}
7908 		}
7909 	}
7910 
7911 	if ( iVer>=0x11C && dFactorItems.GetLength() )
7912 	{
7913 		for ( int i=0; i<pRes->m_iCount; i++ )
7914 		{
7915 			const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
7916 			ARRAY_FOREACH ( j, dFactorItems )
7917 			{
7918 				DWORD * pData = (DWORD *) tMatch.GetAttr ( dFactorItems[j] );
7919 				if ( pData )
7920 					iRespLen += *pData-sizeof(DWORD);
7921 			}
7922 		}
7923 	}
7924 
7925 	return iRespLen;
7926 }
7927 
7928 
SendResult(int iVer,NetOutputBuffer_c & tOut,const CSphQueryResult * pRes,const CSphTaggedVector & dTag2Pools,bool bAgentMode,const CSphQuery & tQuery,int iMasterVer)7929 void SendResult ( int iVer, NetOutputBuffer_c & tOut, const CSphQueryResult * pRes,
7930 					const CSphTaggedVector & dTag2Pools, bool bAgentMode, const CSphQuery & tQuery, int iMasterVer )
7931 {
7932 	// status
7933 	if ( iVer>=0x10D )
7934 	{
7935 		// multi-query status
7936 		bool bError = !pRes->m_sError.IsEmpty();
7937 		bool bWarning = !bError && !pRes->m_sWarning.IsEmpty();
7938 
7939 		if ( bError )
7940 		{
7941 			tOut.SendInt ( SEARCHD_ERROR );
7942 			tOut.SendString ( pRes->m_sError.cstr() );
7943 			if ( g_bOptNoDetach && g_eLogFormat!=LOG_FORMAT_SPHINXQL )
7944 				sphInfo ( "query error: %s", pRes->m_sError.cstr() );
7945 			return;
7946 
7947 		} else if ( bWarning )
7948 		{
7949 			tOut.SendInt ( SEARCHD_WARNING );
7950 			tOut.SendString ( pRes->m_sWarning.cstr() );
7951 			if ( g_bOptNoDetach && g_eLogFormat!=LOG_FORMAT_SPHINXQL )
7952 				sphInfo ( "query warning: %s", pRes->m_sWarning.cstr() );
7953 		} else
7954 		{
7955 			tOut.SendInt ( SEARCHD_OK );
7956 		}
7957 
7958 	} else
7959 	{
7960 		// single-query warning
7961 		if ( iVer>=0x106 && !pRes->m_sWarning.IsEmpty() )
7962 			tOut.SendString ( pRes->m_sWarning.cstr() );
7963 	}
7964 
7965 	int iAttrsCount = SendGetAttrCount ( pRes->m_tSchema, bAgentMode );
7966 
7967 	bool bSendJson = ( bAgentMode && iMasterVer>=3 );
7968 	bool bSendJsonField = ( bAgentMode && iMasterVer>=4 );
7969 
7970 	// send schema
7971 	if ( iVer>=0x102 )
7972 	{
7973 		tOut.SendInt ( pRes->m_tSchema.m_dFields.GetLength() );
7974 		ARRAY_FOREACH ( i, pRes->m_tSchema.m_dFields )
7975 			tOut.SendString ( pRes->m_tSchema.m_dFields[i].m_sName.cstr() );
7976 
7977 		tOut.SendInt ( iAttrsCount );
7978 		for ( int i=0; i<iAttrsCount; i++ )
7979 		{
7980 			const CSphColumnInfo & tCol = pRes->m_tSchema.GetAttr(i);
7981 			tOut.SendString ( tCol.m_sName.cstr() );
7982 
7983 			ESphAttr eCol = tCol.m_eAttrType;
7984 			if ( ( tCol.m_eAttrType==SPH_ATTR_JSON && !bSendJson ) || ( tCol.m_eAttrType==SPH_ATTR_JSON_FIELD && !bSendJsonField )
7985 				 || ( tCol.m_eAttrType==SPH_ATTR_STRINGPTR && !bAgentMode ) )
7986 				eCol = SPH_ATTR_STRING;
7987 			tOut.SendDword ( (DWORD)eCol );
7988 		}
7989 	}
7990 
7991 	// send matches
7992 	CSphAttrLocator iGIDLoc, iTSLoc;
7993 	if ( iVer<=0x101 )
7994 	{
7995 		for ( int i=0; i<pRes->m_tSchema.GetAttrsCount(); i++ )
7996 		{
7997 			const CSphColumnInfo & tAttr = pRes->m_tSchema.GetAttr(i);
7998 
7999 			if ( iTSLoc.m_iBitOffset<0 && tAttr.m_eAttrType==SPH_ATTR_TIMESTAMP )
8000 				iTSLoc = tAttr.m_tLocator;
8001 
8002 			if ( iGIDLoc.m_iBitOffset<0 && tAttr.m_eAttrType==SPH_ATTR_INTEGER )
8003 				iGIDLoc = tAttr.m_tLocator;
8004 		}
8005 	}
8006 
8007 	tOut.SendInt ( pRes->m_iCount );
8008 	if ( iVer>=0x108 )
8009 		tOut.SendInt ( USE_64BIT );
8010 
8011 	CSphVector<BYTE> dJson ( 512 );
8012 
8013 	for ( int i=0; i<pRes->m_iCount; i++ )
8014 	{
8015 		const CSphMatch & tMatch = pRes->m_dMatches [ pRes->m_iOffset+i ];
8016 #if USE_64BIT
8017 		if ( iVer>=0x108 )
8018 			tOut.SendUint64 ( tMatch.m_uDocID );
8019 		else
8020 #endif
8021 			tOut.SendDword ( (DWORD)tMatch.m_uDocID );
8022 
8023 		if ( iVer<=0x101 )
8024 		{
8025 			tOut.SendDword ( iGIDLoc.m_iBitOffset>=0 ? (DWORD) tMatch.GetAttr ( iGIDLoc ) : 1 );
8026 			tOut.SendDword ( iTSLoc.m_iBitOffset>=0 ? (DWORD) tMatch.GetAttr ( iTSLoc ) : 1 );
8027 			tOut.SendInt ( tMatch.m_iWeight );
8028 		} else
8029 		{
8030 			tOut.SendInt ( tMatch.m_iWeight );
8031 
8032 			const DWORD * pMvaPool = dTag2Pools [ tMatch.m_iTag ].m_pMva;
8033 			const BYTE * pStrings = dTag2Pools [ tMatch.m_iTag ].m_pStrings;
8034 			bool bArenaProhibit = dTag2Pools[tMatch.m_iTag].m_bArenaProhibit;
8035 
8036 			assert ( tMatch.m_pStatic || !pRes->m_tSchema.GetStaticSize() );
8037 #if 0
8038 			// not correct any more because of internal attrs (such as string sorting ptrs)
8039 			assert ( tMatch.m_pDynamic || !pRes->m_tSchema.GetDynamicSize() );
8040 			assert ( !tMatch.m_pDynamic || (int)tMatch.m_pDynamic[-1]==pRes->m_tSchema.GetDynamicSize() );
8041 #endif
8042 
8043 			for ( int j=0; j<iAttrsCount; j++ )
8044 			{
8045 				const CSphColumnInfo & tAttr = pRes->m_tSchema.GetAttr(j);
8046 				switch ( tAttr.m_eAttrType )
8047 				{
8048 				case SPH_ATTR_UINT32SET:
8049 				case SPH_ATTR_INT64SET:
8050 					{
8051 						assert ( tMatch.GetAttr ( tAttr.m_tLocator )==0 || pMvaPool );
8052 						const DWORD * pValues = tMatch.GetAttrMVA ( tAttr.m_tLocator, pMvaPool, bArenaProhibit );
8053 						if ( iVer<0x10C || !pValues )
8054 						{
8055 							// for older clients, fixups column value to 0
8056 							// for newer clients, means that there are 0 values
8057 							tOut.SendDword ( 0 );
8058 						} else
8059 						{
8060 							// send MVA values
8061 							int iValues = *pValues++;
8062 							tOut.SendDword ( iValues );
8063 							if ( tAttr.m_eAttrType==SPH_ATTR_INT64SET )
8064 							{
8065 								assert ( ( iValues%2 )==0 );
8066 								while ( iValues )
8067 								{
8068 									uint64_t uVal = (uint64_t)MVA_UPSIZE ( pValues );
8069 									tOut.SendUint64 ( uVal );
8070 									pValues += 2;
8071 									iValues -= 2;
8072 								}
8073 							} else
8074 							{
8075 								while ( iValues-- )
8076 									tOut.SendDword ( *pValues++ );
8077 							}
8078 						}
8079 						break;
8080 					}
8081 				case SPH_ATTR_JSON:
8082 					{
8083 						if ( iVer<0x117 )
8084 						{
8085 							tOut.SendDword ( 0 );
8086 							break;
8087 						}
8088 						if ( !bSendJson )
8089 						{
8090 							// formatted string to client
8091 							DWORD uOffset = (DWORD) tMatch.GetAttr ( tAttr.m_tLocator );
8092 							assert ( !uOffset || pStrings );
8093 							if ( !uOffset ) // magic zero
8094 							{
8095 								tOut.SendDword ( sizeof(g_sJsonNull)-1 );
8096 								tOut.SendBytes ( g_sJsonNull, sizeof(g_sJsonNull)-1 );
8097 							} else
8098 							{
8099 								dJson.Resize ( 0 );
8100 								const BYTE * pStr = NULL;
8101 								sphUnpackStr ( pStrings + uOffset, &pStr );
8102 								sphJsonFormat ( dJson, pStr );
8103 
8104 								tOut.SendDword ( dJson.GetLength() );
8105 								tOut.SendBytes ( dJson.Begin(), dJson.GetLength() );
8106 							}
8107 							break;
8108 						}
8109 						// no break at the end, pass to SPH_ATTR_STRING
8110 					}
8111 				case SPH_ATTR_STRING:
8112 					{
8113 						if ( iVer<0x117 )
8114 						{
8115 							tOut.SendDword ( 0 );
8116 							break;
8117 						}
8118 						// for newer clients, send binary string either STRING or JSON attribute
8119 						DWORD uOffset = (DWORD) tMatch.GetAttr ( tAttr.m_tLocator );
8120 						if ( !uOffset ) // magic zero
8121 						{
8122 							tOut.SendDword ( 0 ); // null string
8123 						} else
8124 						{
8125 							const BYTE * pStr;
8126 							assert ( pStrings );
8127 							int iLen = sphUnpackStr ( pStrings+uOffset, &pStr );
8128 							tOut.SendDword ( iLen );
8129 							tOut.SendBytes ( pStr, iLen );
8130 						}
8131 						break;
8132 					}
8133 				case SPH_ATTR_STRINGPTR:
8134 					{
8135 						if ( iVer<0x117 )
8136 						{
8137 							tOut.SendDword ( 0 );
8138 							break;
8139 						}
8140 						// for newer clients, send binary string
8141 						const char* pString = (const char*) tMatch.GetAttr ( tAttr.m_tLocator );
8142 						if ( !pString ) // magic zero
8143 						{
8144 							tOut.SendDword ( 0 ); // null string
8145 						} else
8146 						{
8147 							int iLen = strlen ( pString );
8148 							tOut.SendDword ( iLen );
8149 							tOut.SendBytes ( pString, iLen );
8150 						}
8151 						break;
8152 					}
8153 				case SPH_ATTR_JSON_FIELD:
8154 					{
8155 						if ( iVer<0x117 )
8156 						{
8157 							tOut.SendDword ( 0 );
8158 							break;
8159 						}
8160 
8161 						uint64_t uTypeOffset = tMatch.GetAttr ( tAttr.m_tLocator );
8162 						ESphJsonType eJson = ESphJsonType ( uTypeOffset>>32 );
8163 						DWORD uOff = (DWORD)uTypeOffset;
8164 						assert ( !uOff || pStrings );
8165 						if ( !uOff )
8166 						{
8167 							// no key found - NULL value
8168 							if ( bSendJsonField )
8169 								tOut.SendByte ( JSON_EOF );
8170 							else
8171 								tOut.SendDword ( 0 );
8172 
8173 						} else if ( bSendJsonField )
8174 						{
8175 							// to master send packed data
8176 							tOut.SendByte ( (BYTE)eJson );
8177 
8178 							const BYTE * pData = pStrings+uOff;
8179 							int iLen = sphJsonNodeSize ( eJson, pData );
8180 							if ( sphJsonNodeSize ( eJson, NULL )<0 )
8181 								tOut.SendDword ( iLen );
8182 							tOut.SendBytes ( pData, iLen );
8183 						} else
8184 						{
8185 							// to client send data as string
8186 							dJson.Resize ( 0 );
8187 							sphJsonFieldFormat ( dJson, pStrings+uOff, eJson, false );
8188 							tOut.SendDword ( dJson.GetLength() );
8189 							tOut.SendBytes ( dJson.Begin(), dJson.GetLength() );
8190 						}
8191 						break;
8192 					}
8193 				case SPH_ATTR_FACTORS:
8194 				case SPH_ATTR_FACTORS_JSON:
8195 					{
8196 						if ( iVer<0x11C )
8197 						{
8198 							tOut.SendDword ( 0 );
8199 							break;
8200 						}
8201 						BYTE * pData = (BYTE*) tMatch.GetAttr ( tAttr.m_tLocator );
8202 						if ( !pData )
8203 							tOut.SendDword ( 0 );
8204 						else
8205 						{
8206 							DWORD uLength = *(DWORD*)pData;
8207 							tOut.SendDword ( uLength );
8208 							tOut.SendBytes ( pData+sizeof(DWORD), uLength-sizeof(DWORD) );
8209 						}
8210 						break;
8211 					}
8212 				case SPH_ATTR_FLOAT:
8213 					tOut.SendFloat ( tMatch.GetAttrFloat ( tAttr.m_tLocator ) );
8214 					break;
8215 				case SPH_ATTR_BIGINT:
8216 					if ( iVer>=0x114 )
8217 					{
8218 						tOut.SendUint64 ( tMatch.GetAttr ( tAttr.m_tLocator ) );
8219 						break;
8220 					}
8221 					// no break here
8222 				default:
8223 					tOut.SendDword ( (DWORD)tMatch.GetAttr ( tAttr.m_tLocator ) );
8224 					break;
8225 				} /// end switch ( tAttr.m_eAttrType )
8226 			} /// end for ( int j=0; j<iAttrsCount; j++ )
8227 		} /// end else if ( iVer<=0x101 )
8228 	} /// end for ( int i=0; i<pRes->m_iCount; i++ )
8229 
8230 	if ( tQuery.m_bAgent && tQuery.m_iLimit )
8231 		tOut.SendInt ( pRes->m_iCount );
8232 	else
8233 		tOut.SendInt ( pRes->m_dMatches.GetLength() );
8234 
8235 	tOut.SendAsDword ( pRes->m_iTotalMatches );
8236 	tOut.SendInt ( Max ( pRes->m_iQueryTime, 0 ) );
8237 
8238 	if ( iVer>=0x11A && bAgentMode )
8239 	{
8240 		bool bNeedPredictedTime = tQuery.m_iMaxPredictedMsec > 0;
8241 
8242 		BYTE uStatMask = ( bNeedPredictedTime ? 4 : 0 ) | ( g_bCpuStats ? 2 : 0 ) | ( g_bIOStats ? 1 : 0 );
8243 		tOut.SendByte ( uStatMask );
8244 
8245 		if ( g_bIOStats )
8246 		{
8247 			CSphIOStats tStats = pRes->m_tIOStats;
8248 			tStats.Add ( pRes->m_tAgentIOStats );
8249 			tOut.SendUint64 ( tStats.m_iReadTime );
8250 			tOut.SendDword ( tStats.m_iReadOps );
8251 			tOut.SendUint64 ( tStats.m_iReadBytes );
8252 			tOut.SendUint64 ( tStats.m_iWriteTime );
8253 			tOut.SendDword ( tStats.m_iWriteOps );
8254 			tOut.SendUint64 ( tStats.m_iWriteBytes );
8255 		}
8256 
8257 		if ( g_bCpuStats )
8258 		{
8259 			int64_t iCpuTime = pRes->m_iCpuTime + pRes->m_iAgentCpuTime;
8260 			tOut.SendUint64 ( iCpuTime );
8261 		}
8262 
8263 		if ( bNeedPredictedTime )
8264 			tOut.SendUint64 ( pRes->m_iPredictedTime+pRes->m_iAgentPredictedTime );
8265 	}
8266 	if ( bAgentMode && iMasterVer>=7 )
8267 	{
8268 		tOut.SendDword ( pRes->m_tStats.m_iFetchedDocs + pRes->m_iAgentFetchedDocs );
8269 		tOut.SendDword ( pRes->m_tStats.m_iFetchedHits + pRes->m_iAgentFetchedHits );
8270 		if ( iMasterVer>=8 )
8271 			tOut.SendDword ( pRes->m_tStats.m_iSkips + pRes->m_iAgentFetchedSkips );
8272 	}
8273 
8274 	tOut.SendInt ( pRes->m_hWordStats.GetLength() );
8275 
8276 	pRes->m_hWordStats.IterateStart();
8277 	while ( pRes->m_hWordStats.IterateNext() )
8278 	{
8279 		const CSphQueryResultMeta::WordStat_t & tStat = pRes->m_hWordStats.IterateGet();
8280 		tOut.SendString ( pRes->m_hWordStats.IterateGetKey().cstr() );
8281 		tOut.SendAsDword ( tStat.m_iDocs );
8282 		tOut.SendAsDword ( tStat.m_iHits );
8283 		if ( bAgentMode )
8284 			tOut.SendByte ( false ); // statistics have no expanded terms for now
8285 	}
8286 }
8287 
8288 /////////////////////////////////////////////////////////////////////////////
8289 
8290 struct AggrResult_t : CSphQueryResult
8291 {
8292 	CSphVector<CSphRsetSchema>		m_dSchemas;			///< aggregated resultsets schemas (for schema minimization)
8293 	CSphVector<int>					m_dMatchCounts;		///< aggregated resultsets lengths (for schema minimization)
8294 	CSphVector<const CSphIndex*>	m_dLockedAttrs;		///< indexes which are hold in the memory untill sending result
8295 	CSphTaggedVector				m_dTag2Pools;		///< tag to MVA and strings storage pools mapping
8296 	CSphString						m_sZeroCountName;
8297 
AggrResult_tAggrResult_t8298 	AggrResult_t()
8299 	{}
8300 
ClampMatchesAggrResult_t8301 	void ClampMatches ( int iLimit, bool bCommonSchema )
8302 	{
8303 		if ( m_dMatches.GetLength()<=iLimit )
8304 			return;
8305 
8306 		if ( bCommonSchema )
8307 		{
8308 			for ( int i = iLimit; i < m_dMatches.GetLength(); i++ )
8309 				m_tSchema.FreeStringPtrs ( &m_dMatches[i] );
8310 		} else
8311 		{
8312 			int nMatches = 0;
8313 			ARRAY_FOREACH ( i, m_dMatchCounts )
8314 			{
8315 				nMatches += m_dMatchCounts[i];
8316 
8317 				if ( iLimit < nMatches )
8318 				{
8319 					int iFrom = Max ( iLimit, nMatches-m_dMatchCounts[i] );
8320 					for ( int j=iFrom; j<nMatches; j++ )
8321 						m_dSchemas[i].FreeStringPtrs ( &m_dMatches[j] );
8322 				}
8323 			}
8324 		}
8325 
8326 		m_dMatches.Resize ( iLimit );
8327 	}
8328 };
8329 
8330 
8331 struct TaggedMatchSorter_fn : public SphAccessor_T<CSphMatch>
8332 {
CopyKeyTaggedMatchSorter_fn8333 	void CopyKey ( CSphMatch * pMed, CSphMatch * pVal ) const
8334 	{
8335 		pMed->m_uDocID = pVal->m_uDocID;
8336 		pMed->m_iTag = pVal->m_iTag;
8337 	}
8338 
IsLessTaggedMatchSorter_fn8339 	bool IsLess ( const CSphMatch & a, const CSphMatch & b ) const
8340 	{
8341 		bool bDistA = ( ( a.m_iTag & 0x80000000 )==0x80000000 );
8342 		bool bDistB = ( ( b.m_iTag & 0x80000000 )==0x80000000 );
8343 		// sort by doc_id, dist_tag, tag
8344 		return ( a.m_uDocID < b.m_uDocID ) ||
8345 			( a.m_uDocID==b.m_uDocID && ( ( !bDistA && bDistB ) || ( ( a.m_iTag & 0x7FFFFFFF )>( b.m_iTag & 0x7FFFFFFF ) ) ) );
8346 	}
8347 
8348 	// inherited swap does not work on gcc
SwapTaggedMatchSorter_fn8349 	void Swap ( CSphMatch * a, CSphMatch * b ) const
8350 	{
8351 		::Swap ( *a, *b );
8352 	}
8353 };
8354 
8355 
RemapResult(const ISphSchema * pTarget,AggrResult_t * pRes)8356 void RemapResult ( const ISphSchema * pTarget, AggrResult_t * pRes )
8357 {
8358 	int iCur = 0;
8359 	CSphVector<int> dMapFrom ( pTarget->GetAttrsCount() );
8360 
8361 	ARRAY_FOREACH ( iSchema, pRes->m_dSchemas )
8362 	{
8363 		dMapFrom.Resize ( 0 );
8364 		CSphRsetSchema & dSchema = pRes->m_dSchemas[iSchema];
8365 		for ( int i=0; i<pTarget->GetAttrsCount(); i++ )
8366 		{
8367 			dMapFrom.Add ( dSchema.GetAttrIndex ( pTarget->GetAttr(i).m_sName.cstr() ) );
8368 			assert ( dMapFrom[i]>=0
8369 				|| pTarget->GetAttr(i).m_tLocator.IsID()
8370 				|| sphIsSortStringInternal ( pTarget->GetAttr(i).m_sName.cstr() )
8371 				|| pTarget->GetAttr(i).m_sName=="@groupbystr"
8372 				);
8373 		}
8374 		int iLimit = Min ( iCur + pRes->m_dMatchCounts[iSchema], pRes->m_dMatches.GetLength() );
8375 		for ( int i=iCur; i<iLimit; i++ )
8376 		{
8377 			CSphMatch & tMatch = pRes->m_dMatches[i];
8378 
8379 			// create new and shiny (and properly sized) match
8380 			CSphMatch tRow;
8381 			tRow.Reset ( pTarget->GetDynamicSize() );
8382 			tRow.m_uDocID = tMatch.m_uDocID;
8383 			tRow.m_iWeight = tMatch.m_iWeight;
8384 			tRow.m_iTag = tMatch.m_iTag;
8385 
8386 			// remap attrs
8387 			for ( int j=0; j<pTarget->GetAttrsCount(); j++ )
8388 			{
8389 				const CSphColumnInfo & tDst = pTarget->GetAttr(j);
8390 				// we could keep some of the rows static
8391 				// and so, avoid the duplication of the data.
8392 				if ( !tDst.m_tLocator.m_bDynamic )
8393 				{
8394 					assert ( dMapFrom[j]<0 || !dSchema.GetAttr ( dMapFrom[j] ).m_tLocator.m_bDynamic );
8395 					tRow.m_pStatic = tMatch.m_pStatic;
8396 				} else if ( dMapFrom[j]>=0 )
8397 				{
8398 					const CSphColumnInfo & tSrc = dSchema.GetAttr ( dMapFrom[j] );
8399 					if ( tDst.m_eAttrType==SPH_ATTR_FLOAT && tSrc.m_eAttrType==SPH_ATTR_BOOL )
8400 					{
8401 						tRow.SetAttrFloat ( tDst.m_tLocator, ( tMatch.GetAttr ( tSrc.m_tLocator )>0 ? 1.0f : 0.0f ) );
8402 					} else
8403 					{
8404 						tRow.SetAttr ( tDst.m_tLocator, tMatch.GetAttr ( tSrc.m_tLocator ) );
8405 					}
8406 				}
8407 			}
8408 			// swap out old (most likely wrong sized) match
8409 			Swap ( tMatch, tRow );
8410 		}
8411 
8412 		iCur = iLimit;
8413 	}
8414 	assert ( iCur==pRes->m_dMatches.GetLength() );
8415 }
8416 
8417 // rebuild the results itemlist expanding stars
ExpandAsterisk(const ISphSchema & tSchema,const CSphVector<CSphQueryItem> & tItems,CSphVector<CSphQueryItem> * pExpanded,bool bNoID)8418 const CSphVector<CSphQueryItem> * ExpandAsterisk ( const ISphSchema & tSchema,
8419 	const CSphVector<CSphQueryItem> & tItems, CSphVector<CSphQueryItem> * pExpanded, bool bNoID )
8420 {
8421 	// the result schema usually is the index schema + calculated items + @-items
8422 	// we need to extract the index schema only - so, look at the items
8423 	// and cutoff from calculated or @.
8424 	int iSchemaBound = tSchema.GetAttrsCount();
8425 	bool bStar = false;
8426 	ARRAY_FOREACH ( i, tItems )
8427 	{
8428 		const CSphQueryItem & tItem = tItems[i];
8429 		if ( tItem.m_sAlias.cstr() )
8430 		{
8431 			int j = tSchema.GetAttrIndex ( tItem.m_sAlias.cstr() );
8432 			if ( j>=0 )
8433 				iSchemaBound = Min ( iSchemaBound, j );
8434 		}
8435 		bStar = bStar || tItem.m_sExpr=="*";
8436 	}
8437 	// no stars? Nothing to do.
8438 	if ( !bStar )
8439 		return & tItems;
8440 
8441 	while ( iSchemaBound && tSchema.GetAttr ( iSchemaBound-1 ).m_sName.cstr()[0]=='@' )
8442 		iSchemaBound--;
8443 	ARRAY_FOREACH ( i, tItems )
8444 	{
8445 		if ( tItems[i].m_sExpr=="*" )
8446 		{ // asterisk expands to 'id' + all the items from the schema
8447 			if ( tSchema.GetAttrIndex ( "id" )<0 && !bNoID )
8448 			{
8449 				CSphQueryItem& tItem = pExpanded->Add();
8450 				tItem.m_sExpr = "id";
8451 			}
8452 			for ( int j=0; j<iSchemaBound; j++ )
8453 			{
8454 				if ( !j && bNoID && tSchema.GetAttr(j).m_sName=="id" )
8455 					continue;
8456 				CSphQueryItem& tItem = pExpanded->Add();
8457 				tItem.m_sExpr = tSchema.GetAttr ( j ).m_sName;
8458 			}
8459 		} else
8460 			pExpanded->Add ( tItems[i] );
8461 	}
8462 	return pExpanded;
8463 }
8464 
8465 
RemapStrings(ISphMatchSorter * pSorter,AggrResult_t & tRes)8466 static void RemapStrings ( ISphMatchSorter * pSorter, AggrResult_t & tRes )
8467 {
8468 	// do match ptr pre-calc if its "order by string" case
8469 	CSphVector<SphStringSorterRemap_t> dRemapAttr;
8470 	if ( pSorter && pSorter->UsesAttrs() && sphSortGetStringRemap ( pSorter->GetSchema(), tRes.m_tSchema, dRemapAttr ) )
8471 	{
8472 		int iCur = 0;
8473 		ARRAY_FOREACH ( iSchema, tRes.m_dSchemas )
8474 		{
8475 			for ( int i=iCur; i<iCur+tRes.m_dMatchCounts[iSchema]; i++ )
8476 			{
8477 				CSphMatch & tMatch = tRes.m_dMatches[i];
8478 				const BYTE * pStringBase = tRes.m_dTag2Pools[tMatch.m_iTag].m_pStrings;
8479 
8480 				ARRAY_FOREACH ( iAttr, dRemapAttr )
8481 				{
8482 					SphAttr_t uOff = tMatch.GetAttr ( dRemapAttr[iAttr].m_tSrc );
8483 					SphAttr_t uPtr = (SphAttr_t)( pStringBase && uOff ? pStringBase + uOff : 0 );
8484 					tMatch.SetAttr ( dRemapAttr[iAttr].m_tDst, uPtr );
8485 				}
8486 			}
8487 			iCur += tRes.m_dMatchCounts[iSchema];
8488 		}
8489 	}
8490 }
8491 
8492 
KillAllDupes(ISphMatchSorter * pSorter,AggrResult_t & tRes)8493 static int KillAllDupes ( ISphMatchSorter * pSorter, AggrResult_t & tRes )
8494 {
8495 	assert ( pSorter );
8496 	int iDupes = 0;
8497 
8498 	if ( pSorter->IsGroupby () )
8499 	{
8500 		// groupby sorter does that automagically
8501 		pSorter->SetMVAPool ( NULL, false ); // because we must be able to group on @groupby anyway
8502 		pSorter->SetStringPool ( NULL );
8503 		int iMC = 0;
8504 		int iBound = 0;
8505 
8506 		ARRAY_FOREACH ( i, tRes.m_dMatches )
8507 		{
8508 			CSphMatch & tMatch = tRes.m_dMatches[i];
8509 			if ( !pSorter->PushGrouped ( tMatch, i==iBound ) )
8510 				iDupes++;
8511 
8512 			if ( i==iBound )
8513 				iBound += tRes.m_dMatchCounts[iMC++];
8514 		}
8515 	} else
8516 	{
8517 		// normal sorter needs massasging
8518 		// sort by docid and then by tag to guarantee the replacement order
8519 		TaggedMatchSorter_fn fnSort;
8520 		sphSort ( tRes.m_dMatches.Begin(), tRes.m_dMatches.GetLength(), fnSort, fnSort );
8521 
8522 		// by default, simply remove dupes (select first by tag)
8523 		ARRAY_FOREACH ( i, tRes.m_dMatches )
8524 		{
8525 			if ( i==0 || tRes.m_dMatches[i].m_uDocID!=tRes.m_dMatches[i-1].m_uDocID )
8526 				pSorter->Push ( tRes.m_dMatches[i] );
8527 			else
8528 				iDupes++;
8529 		}
8530 	}
8531 
8532 	ARRAY_FOREACH ( i, tRes.m_dMatches )
8533 		tRes.m_tSchema.FreeStringPtrs ( &(tRes.m_dMatches[i]) );
8534 
8535 	tRes.m_dMatches.Reset ();
8536 	sphFlattenQueue ( pSorter, &tRes, -1 );
8537 	SafeDelete ( pSorter );
8538 
8539 	return iDupes;
8540 }
8541 
8542 
RecoverAggregateFunctions(const CSphQuery & tQuery,const AggrResult_t & tRes)8543 static void RecoverAggregateFunctions ( const CSphQuery & tQuery, const AggrResult_t & tRes )
8544 {
8545 	ARRAY_FOREACH ( i, tQuery.m_dItems )
8546 	{
8547 		const CSphQueryItem & tItem = tQuery.m_dItems[i];
8548 		if ( tItem.m_eAggrFunc==SPH_AGGR_NONE )
8549 			continue;
8550 
8551 		for ( int j=0; j<tRes.m_tSchema.GetAttrsCount(); j++ )
8552 		{
8553 			CSphColumnInfo & tCol = const_cast<CSphColumnInfo&> ( tRes.m_tSchema.GetAttr(j) );
8554 			if ( tCol.m_sName==tItem.m_sAlias )
8555 			{
8556 				assert ( tCol.m_eAggrFunc==SPH_AGGR_NONE );
8557 				tCol.m_eAggrFunc = tItem.m_eAggrFunc;
8558 			}
8559 		}
8560 	}
8561 }
8562 
8563 
8564 struct GenericMatchSort_fn : public CSphMatchComparatorState
8565 {
IsLessGenericMatchSort_fn8566 	bool IsLess ( const CSphMatch * a, const CSphMatch * b ) const
8567 	{
8568 		for ( int i=0; i<CSphMatchComparatorState::MAX_ATTRS; i++ )
8569 			switch ( m_eKeypart[i] )
8570 		{
8571 			case SPH_KEYPART_ID:
8572 				if ( a->m_uDocID==b->m_uDocID )
8573 					continue;
8574 				return ( ( m_uAttrDesc>>i ) & 1 ) ^ ( a->m_uDocID < b->m_uDocID );
8575 
8576 			case SPH_KEYPART_WEIGHT:
8577 				if ( a->m_iWeight==b->m_iWeight )
8578 					continue;
8579 				return ( ( m_uAttrDesc>>i ) & 1 ) ^ ( a->m_iWeight < b->m_iWeight );
8580 
8581 			case SPH_KEYPART_INT:
8582 			{
8583 				SphAttr_t aa = a->GetAttr ( m_tLocator[i] );
8584 				SphAttr_t bb = b->GetAttr ( m_tLocator[i] );
8585 				if ( aa==bb )
8586 					continue;
8587 				return ( ( m_uAttrDesc>>i ) & 1 ) ^ ( aa < bb );
8588 			}
8589 			case SPH_KEYPART_FLOAT:
8590 			{
8591 				float aa = a->GetAttrFloat ( m_tLocator[i] );
8592 				float bb = b->GetAttrFloat ( m_tLocator[i] );
8593 				if ( aa==bb )
8594 					continue;
8595 				return ( ( m_uAttrDesc>>i ) & 1 ) ^ ( aa < bb );
8596 			}
8597 			case SPH_KEYPART_STRINGPTR:
8598 			case SPH_KEYPART_STRING:
8599 			{
8600 				int iCmp = CmpStrings ( *a, *b, i );
8601 				if ( iCmp!=0 )
8602 					return ( ( m_uAttrDesc>>i ) & 1 ) ^ ( iCmp < 0 );
8603 				break;
8604 			}
8605 		}
8606 		return false;
8607 	}
8608 };
8609 
8610 
8611 /// returns internal magic names for expressions like COUNT(*) that have a corresponding one
8612 /// returns expression itself otherwise
GetMagicSchemaName(const CSphString & s)8613 const char * GetMagicSchemaName ( const CSphString & s )
8614 {
8615 	if ( s=="count(*)" )
8616 		return "@count";
8617 	if ( s=="weight()" )
8618 		return "@weight";
8619 	if ( s=="groupby()" )
8620 		return "@groupby";
8621 	return s.cstr();
8622 }
8623 
8624 
8625 /// a functor to sort columns by (is_aggregate ASC, column_index ASC)
8626 struct AggregateColumnSort_fn
8627 {
IsAggrAggregateColumnSort_fn8628 	bool IsAggr ( const CSphColumnInfo & c ) const
8629 	{
8630 		return c.m_eAggrFunc!=SPH_AGGR_NONE || c.m_sName=="@groupby" || c.m_sName=="@count" || c.m_sName=="@distinct" || c.m_sName=="@groupbystr";
8631 	}
8632 
IsLessAggregateColumnSort_fn8633 	bool IsLess ( const CSphColumnInfo & a, const CSphColumnInfo & b ) const
8634 	{
8635 		bool aa = IsAggr(a);
8636 		bool bb = IsAggr(b);
8637 		if ( aa!=bb )
8638 			return aa < bb;
8639 		return a.m_iIndex < b.m_iIndex;
8640 	}
8641 };
8642 
8643 
ExtractPostlimit(const CSphRsetSchema & tSchema,CSphVector<const CSphColumnInfo * > & dPostlimit)8644 static void ExtractPostlimit ( const CSphRsetSchema & tSchema, CSphVector<const CSphColumnInfo *> & dPostlimit )
8645 {
8646 	for ( int i=0; i<tSchema.GetAttrsCount(); i++ )
8647 	{
8648 		const CSphColumnInfo & tCol = tSchema.GetAttr ( i );
8649 		if ( tCol.m_eStage==SPH_EVAL_POSTLIMIT )
8650 			dPostlimit.Add ( &tCol );
8651 	}
8652 }
8653 
8654 
ProcessPostlimit(const CSphVector<const CSphColumnInfo * > & dPostlimit,int iFrom,int iTo,AggrResult_t & tRes)8655 static void ProcessPostlimit ( const CSphVector<const CSphColumnInfo *> & dPostlimit, int iFrom, int iTo, AggrResult_t & tRes )
8656 {
8657 	if ( !dPostlimit.GetLength() )
8658 		return;
8659 
8660 	for ( int i=iFrom; i<iTo; i++ )
8661 	{
8662 		CSphMatch & tMatch = tRes.m_dMatches[i];
8663 		// remote match (tag highest bit 1) == everything is already computed
8664 		if ( tMatch.m_iTag & 0x80000000 )
8665 			continue;
8666 
8667 		ARRAY_FOREACH ( j, dPostlimit )
8668 		{
8669 			const CSphColumnInfo * pCol = dPostlimit[j];
8670 			assert ( pCol->m_pExpr.Ptr() );
8671 
8672 			// OPTIMIZE? only if the tag did not change?
8673 			pCol->m_pExpr->Command ( SPH_EXPR_SET_MVA_POOL, &tRes.m_dTag2Pools [ tMatch.m_iTag ] );
8674 			pCol->m_pExpr->Command ( SPH_EXPR_SET_STRING_POOL, (void*)tRes.m_dTag2Pools [ tMatch.m_iTag ].m_pStrings );
8675 
8676 			if ( pCol->m_eAttrType==SPH_ATTR_INTEGER )
8677 				tMatch.SetAttr ( pCol->m_tLocator, pCol->m_pExpr->IntEval(tMatch) );
8678 			else if ( pCol->m_eAttrType==SPH_ATTR_BIGINT )
8679 				tMatch.SetAttr ( pCol->m_tLocator, pCol->m_pExpr->Int64Eval(tMatch) );
8680 			else if ( pCol->m_eAttrType==SPH_ATTR_STRINGPTR )
8681 			{
8682 				const BYTE * pStr = NULL;
8683 				pCol->m_pExpr->StringEval ( tMatch, &pStr );
8684 				tMatch.SetAttr ( pCol->m_tLocator, (SphAttr_t) pStr ); // FIXME! a potential leak of *previous* value?
8685 			} else
8686 			{
8687 				tMatch.SetAttrFloat ( pCol->m_tLocator, pCol->m_pExpr->Eval(tMatch) );
8688 			}
8689 		}
8690 	}
8691 }
8692 
8693 
ProcessLocalPostlimit(const CSphQuery & tQuery,AggrResult_t & tRes)8694 static void ProcessLocalPostlimit ( const CSphQuery & tQuery, AggrResult_t & tRes )
8695 {
8696 	bool bGotPostlimit = false;
8697 	for ( int i=0; i<tRes.m_tSchema.GetAttrsCount() && !bGotPostlimit; i++ )
8698 		bGotPostlimit = ( tRes.m_tSchema.GetAttr(i).m_eStage==SPH_EVAL_POSTLIMIT );
8699 
8700 	if ( !bGotPostlimit )
8701 		return;
8702 
8703 	int iSetNext = 0;
8704 	CSphVector<const CSphColumnInfo *> dPostlimit;
8705 	ARRAY_FOREACH ( iSchema, tRes.m_dSchemas )
8706 	{
8707 		int iSetStart = iSetNext;
8708 		int iSetCount = tRes.m_dMatchCounts[iSchema];
8709 		iSetNext += iSetCount;
8710 		assert ( iSetNext<=tRes.m_dMatches.GetLength() );
8711 
8712 		dPostlimit.Resize ( 0 );
8713 		ExtractPostlimit ( tRes.m_dSchemas[iSchema], dPostlimit );
8714 		if ( !dPostlimit.GetLength() )
8715 			continue;
8716 
8717 		int iTo = iSetCount;
8718 		int iOff = Max ( tQuery.m_iOffset, tQuery.m_iOuterOffset );
8719 		int iCount = ( tQuery.m_iOuterLimit ? tQuery.m_iOuterLimit : tQuery.m_iLimit );
8720 		iTo = Max ( Min ( iOff + iCount, iTo ), 0 );
8721 		// we can't estimate limit.offset per result set
8722 		// as matches got merged and sort next step
8723 		int iFrom = 0;
8724 
8725 		iFrom += iSetStart;
8726 		iTo += iSetStart;
8727 
8728 		ProcessPostlimit ( dPostlimit, iFrom, iTo, tRes );
8729 	}
8730 }
8731 
8732 
8733 /// merges multiple result sets, remaps columns, does reorder for outer selects
8734 /// query is only (!) non-const to tweak order vs reorder clauses
MinimizeAggrResult(AggrResult_t & tRes,CSphQuery & tQuery,int iLocals,int,CSphSchema * pExtraSchema,CSphQueryProfile * pProfiler,bool bFromSphinxql,const CSphFilterSettings * pAggrFilter)8735 bool MinimizeAggrResult ( AggrResult_t & tRes, CSphQuery & tQuery, int iLocals, int ,
8736 	CSphSchema * pExtraSchema, CSphQueryProfile * pProfiler, bool bFromSphinxql, const CSphFilterSettings * pAggrFilter )
8737 {
8738 	// sanity check
8739 	// verify that the match counts are consistent
8740 	int iExpected = 0;
8741 	ARRAY_FOREACH ( i, tRes.m_dMatchCounts )
8742 		iExpected += tRes.m_dMatchCounts[i];
8743 	if ( iExpected!=tRes.m_dMatches.GetLength() )
8744 	{
8745 		tRes.m_sError.SetSprintf ( "INTERNAL ERROR: expected %d matches in combined result set, got %d",
8746 			iExpected, tRes.m_dMatches.GetLength() );
8747 		return false;
8748 	}
8749 
8750 	bool bReturnZeroCount = !tRes.m_sZeroCountName.IsEmpty();
8751 
8752 	// 0 matches via SphinxAPI? no fiddling with schemes is necessary
8753 	// (and via SphinxQL, we still need to return the right schema)
8754 	if ( !bFromSphinxql && !tRes.m_dMatches.GetLength() )
8755 		return true;
8756 
8757 	// 0 result set schemes via SphinxQL? just bail
8758 	if ( bFromSphinxql && !tRes.m_dSchemas.GetLength() && !bReturnZeroCount )
8759 		return true;
8760 
8761 	// build a minimal schema over all the (potentially different) schemes
8762 	// that we have in our aggregated result set
8763 	assert ( tRes.m_dSchemas.GetLength() || bReturnZeroCount );
8764 	if ( tRes.m_dSchemas.GetLength() )
8765 		tRes.m_tSchema = tRes.m_dSchemas[0];
8766 	bool bAgent = tQuery.m_bAgent;
8767 	bool bUsualApi = !bAgent && !bFromSphinxql;
8768 	bool bAllEqual = true;
8769 
8770 	// FIXME? add assert ( tRes.m_tSchema==tRes.m_dSchemas[0] );
8771 	for ( int i=1; i<tRes.m_dSchemas.GetLength(); i++ )
8772 		if ( !MinimizeSchema ( tRes.m_tSchema, tRes.m_dSchemas[i] ) )
8773 			bAllEqual = false;
8774 
8775 	// build a list of select items that the query asked for
8776 	CSphVector<CSphQueryItem> tExtItems;
8777 	const CSphVector<CSphQueryItem> * pItems = ExpandAsterisk ( tRes.m_tSchema, tQuery.m_dItems, &tExtItems, !bFromSphinxql );
8778 	const CSphVector<CSphQueryItem> & tItems = *pItems;
8779 
8780 	// api + index without attributes + select * case
8781 	// can not skip aggregate filtering
8782 	if ( !bFromSphinxql && !tItems.GetLength() && !pAggrFilter )
8783 		return true;
8784 
8785 	// build the final schemas!
8786 	// ???
8787 	CSphVector<CSphColumnInfo> dFrontend ( tItems.GetLength() );
8788 
8789 	// no select items => must be nothing in minimized schema, right?
8790 	assert ( !( tItems.GetLength()==0 && tRes.m_tSchema.GetAttrsCount()>0 ) );
8791 
8792 	// track select items that made it into the internal schema
8793 	CSphVector<int> dKnownItems;
8794 
8795 	// we use this vector to reduce amount of work in next nested loop
8796 	// instead of looping through all dItems we FOREACH only unmapped ones
8797 	CSphVector<int> dUnmappedItems;
8798 	ARRAY_FOREACH ( i, tItems )
8799 	{
8800 		int iCol = -1;
8801 		if ( bFromSphinxql && tItems[i].m_sAlias.IsEmpty() )
8802 			iCol = tRes.m_tSchema.GetAttrIndex ( tItems[i].m_sExpr.cstr() );
8803 
8804 		if ( iCol>=0 )
8805 		{
8806 			dFrontend[i].m_sName = tItems[i].m_sExpr;
8807 			dFrontend[i].m_iIndex = iCol;
8808 			dKnownItems.Add(i);
8809 		} else
8810 			dUnmappedItems.Add(i);
8811 	}
8812 
8813 	// ???
8814 	for ( int iCol=0; iCol<tRes.m_tSchema.GetAttrsCount(); iCol++ )
8815 	{
8816 		const CSphColumnInfo & tCol = tRes.m_tSchema.GetAttr(iCol);
8817 
8818 		assert ( !tCol.m_sName.IsEmpty() );
8819 		bool bMagic = ( *tCol.m_sName.cstr()=='@' );
8820 
8821 		if ( !bMagic && tCol.m_pExpr.Ptr() )
8822 		{
8823 			ARRAY_FOREACH ( j, dUnmappedItems )
8824 				if ( tItems[ dUnmappedItems[j] ].m_sAlias==tCol.m_sName )
8825 			{
8826 				int k = dUnmappedItems[j];
8827 				dFrontend[k].m_iIndex = iCol;
8828 				dFrontend[k].m_sName = tItems[k].m_sAlias;
8829 				dKnownItems.Add(k);
8830 				dUnmappedItems.Remove ( j-- ); // do not skip an element next to removed one!
8831 			}
8832 
8833 			// FIXME?
8834 			// really not sure if this is the right thing to do
8835 			// but it fixes a couple queries in test_163 in compaitbility mode
8836 			if ( bAgent && !dFrontend.Contains ( bind ( &CSphColumnInfo::m_sName ), tCol.m_sName ) )
8837 			{
8838 				CSphColumnInfo & t = dFrontend.Add();
8839 				t.m_iIndex = iCol;
8840 				t.m_sName = tCol.m_sName;
8841 			}
8842 		} else if ( bMagic && ( tCol.m_pExpr.Ptr() || bUsualApi ) )
8843 		{
8844 			ARRAY_FOREACH ( j, dUnmappedItems )
8845 				if ( tCol.m_sName==GetMagicSchemaName ( tItems[ dUnmappedItems[j] ].m_sExpr ) )
8846 			{
8847 				int k = dUnmappedItems[j];
8848 				dFrontend[k].m_iIndex = iCol;
8849 				dFrontend[k].m_sName = tItems[k].m_sAlias;
8850 				dKnownItems.Add(k);
8851 				dUnmappedItems.Remove ( j-- ); // do not skip an element next to removed one!
8852 			}
8853 			if ( !dFrontend.Contains ( bind ( &CSphColumnInfo::m_sName ), tCol.m_sName ) )
8854 			{
8855 				CSphColumnInfo & t = dFrontend.Add();
8856 				t.m_iIndex = iCol;
8857 				t.m_sName = tCol.m_sName;
8858 			}
8859 		} else
8860 		{
8861 			bool bAdded = false;
8862 			ARRAY_FOREACH ( j, dUnmappedItems )
8863 			{
8864 				int k = dUnmappedItems[j];
8865 				const CSphQueryItem & t = tItems[k];
8866 				if ( ( tCol.m_sName==GetMagicSchemaName ( t.m_sExpr ) && t.m_eAggrFunc==SPH_AGGR_NONE )
8867 						|| ( t.m_sAlias==tCol.m_sName &&
8868 							( tRes.m_tSchema.GetAttrIndex ( GetMagicSchemaName ( t.m_sExpr ) )==-1 || t.m_eAggrFunc!=SPH_AGGR_NONE ) ) )
8869 				{
8870 					// tricky bit about naming
8871 					//
8872 					// in master mode, we can just use the alias or expression or whatever
8873 					// the data will be fetched using the locator anyway, column name does not matter anymore
8874 					//
8875 					// in agent mode, however, we need to keep the original column names in our response
8876 					// otherwise, queries like SELECT col1 c, count(*) c FROM dist will fail on master
8877 					// because it won't be able to identify the count(*) aggregate by its name
8878 					dFrontend[k].m_iIndex = iCol;
8879 					dFrontend[k].m_sName = bAgent
8880 						? tCol.m_sName
8881 						: ( tItems[k].m_sAlias.IsEmpty()
8882 							? tItems[k].m_sExpr
8883 							: tItems[k].m_sAlias );
8884 					dKnownItems.Add(k);
8885 					bAdded = true;
8886 					dUnmappedItems.Remove ( j-- ); // do not skip an element next to removed one!
8887 				}
8888 			}
8889 
8890 			// column was not found in the select list directly
8891 			// however we might need it anyway because of a non-NULL extra-schema
8892 			// ??? explain extra-schemas here
8893 			// bMagic condition added for @groupbystr in the agent mode
8894 			if ( !bAdded && pExtraSchema && ( pExtraSchema->GetAttrIndex ( tCol.m_sName.cstr() )>=0 || iLocals==0 || bMagic ) )
8895 			{
8896 				CSphColumnInfo & t = dFrontend.Add();
8897 				t.m_iIndex = iCol;
8898 				t.m_sName = tCol.m_sName;
8899 			}
8900 		}
8901 	}
8902 
8903 	// sanity check
8904 	// verify that we actually have all the queried select items
8905 	assert ( !dUnmappedItems.GetLength() || ( dUnmappedItems.GetLength()==1 && tItems [ dUnmappedItems[0] ].m_sExpr=="id" ) );
8906 	dKnownItems.Sort();
8907 	ARRAY_FOREACH ( i, tItems )
8908 		if ( !dKnownItems.BinarySearch(i) && tItems[i].m_sExpr!="id" )
8909 	{
8910 		tRes.m_sError.SetSprintf ( "INTERNAL ERROR: column '%s/%s' not found in result set schema",
8911 			tItems[i].m_sExpr.cstr(), tItems[i].m_sAlias.cstr() );
8912 		return false;
8913 	}
8914 
8915 	// finalize the frontend schema columns
8916 	// we kept indexes into internal schema there, now use them to lookup and copy column data
8917 	ARRAY_FOREACH ( i, dFrontend )
8918 	{
8919 		CSphColumnInfo & d = dFrontend[i];
8920 		if ( d.m_iIndex<0 && tItems[i].m_sExpr=="id" )
8921 		{
8922 			// handle one single exception to select-list-to-minimized-schema mapping loop above
8923 			// "id" is not a part of a schema, so we gotta handle it here
8924 			d.m_tLocator.m_bDynamic = true;
8925 			d.m_sName = tItems[i].m_sAlias.IsEmpty() ? "id" : tItems[i].m_sAlias;
8926 			d.m_eAttrType = USE_64BIT ? SPH_ATTR_BIGINT : SPH_ATTR_INTEGER;
8927 			d.m_tLocator.m_iBitOffset = -8*(int)sizeof(SphDocID_t); // FIXME? move to locator method?
8928 			d.m_tLocator.m_iBitCount = 8*sizeof(SphDocID_t);
8929 		} else
8930 		{
8931 			// everything else MUST have been mapped in the loop just above
8932 			// so use GetAttr(), and let it assert() at will
8933 			const CSphColumnInfo & s = tRes.m_tSchema.GetAttr ( d.m_iIndex );
8934 			d.m_tLocator = s.m_tLocator;
8935 			d.m_eAttrType = s.m_eAttrType;
8936 			d.m_eAggrFunc = s.m_eAggrFunc; // for a sort loop just below
8937 		}
8938 		d.m_iIndex = i; // to make the aggr sort loop just below stable
8939 	}
8940 
8941 	// tricky bit
8942 	// in agents only, push aggregated columns, if any, to the end
8943 	// for that, sort the schema by (is_aggregate ASC, column_index ASC)
8944 	if ( bAgent )
8945 		dFrontend.Sort ( AggregateColumnSort_fn() );
8946 
8947 	// tricky bit
8948 	// in purely distributed case, all schemas are received from the wire, and miss aggregate functions info
8949 	// thus, we need to re-assign that info
8950 	if ( !iLocals )
8951 		RecoverAggregateFunctions ( tQuery, tRes );
8952 
8953 	// if there's more than one result set,
8954 	// we now have to merge and order all the matches
8955 	// this is a good time to apply outer order clause, too
8956 	if ( tRes.m_iSuccesses>1 || pAggrFilter )
8957 	{
8958 		ESphSortOrder eQuerySort = ( tQuery.m_sOuterOrderBy.IsEmpty() ? SPH_SORT_RELEVANCE : SPH_SORT_EXTENDED );
8959 		// got outer order? gotta do a couple things
8960 		if ( tQuery.m_bHasOuter )
8961 		{
8962 			// first, temporarily patch up sorting clause and max_matches (we will restore them later)
8963 			Swap ( tQuery.m_sOuterOrderBy, tQuery.m_sGroupBy.IsEmpty() ? tQuery.m_sSortBy : tQuery.m_sGroupSortBy );
8964 			Swap ( eQuerySort, tQuery.m_eSort );
8965 			tQuery.m_iMaxMatches *= tRes.m_dMatchCounts.GetLength();
8966 			// FIXME? probably not right; 20 shards with by 300 matches might be too much
8967 			// but propagating too small inner max_matches to the outer is not right either
8968 
8969 			// second, apply inner limit now, before (!) reordering
8970 			int iOut = 0;
8971 			int iSetStart = 0;
8972 			ARRAY_FOREACH ( iSet, tRes.m_dMatchCounts )
8973 			{
8974 				assert ( tQuery.m_iLimit>=0 );
8975 				int iOldOut = iOut;
8976 				int iStart = iSetStart;
8977 				int iSetEnd = iSetStart + tRes.m_dMatchCounts[iSet];
8978 				int iEnd = Min ( iStart + tQuery.m_iLimit, iSetEnd );
8979 				iStart = Min ( iStart, iEnd );
8980 				for ( int i=iStart; i<iEnd; i++ )
8981 					Swap ( tRes.m_dMatches[iOut++], tRes.m_dMatches[i] );
8982 				iSetStart = iSetEnd;
8983 				tRes.m_dMatchCounts[iSet] = iOut - iOldOut;
8984 			}
8985 			tRes.ClampMatches ( iOut, bAllEqual ); // false means no common schema; true == use common schema
8986 		}
8987 
8988 		// so we need to bring matches to the schema that the *sorter* wants
8989 		// so we need to create the sorter before conversion
8990 		//
8991 		// create queue
8992 		// at this point, we do not need to compute anything; it all must be here
8993 		SphQueueSettings_t tQueueSettings ( tQuery, tRes.m_tSchema, tRes.m_sError, NULL );
8994 		tQueueSettings.m_bComputeItems = false;
8995 		tQueueSettings.m_pAggrFilter = pAggrFilter;
8996 		ISphMatchSorter * pSorter = sphCreateQueue ( tQueueSettings );
8997 
8998 		// restore outer order related patches, or it screws up the query log
8999 		if ( tQuery.m_bHasOuter )
9000 		{
9001 			Swap ( tQuery.m_sOuterOrderBy, tQuery.m_sGroupBy.IsEmpty() ? tQuery.m_sSortBy : tQuery.m_sGroupSortBy );
9002 			Swap ( eQuerySort, tQuery.m_eSort );
9003 			tQuery.m_iMaxMatches /= tRes.m_dMatchCounts.GetLength();
9004 		}
9005 
9006 		if ( !pSorter )
9007 			return false;
9008 
9009 		// reset bAllEqual flag if sorter makes new attributes
9010 		if ( bAllEqual )
9011 		{
9012 			// at first we count already existed internal attributes
9013 			// then check if sorter makes more
9014 			CSphVector<SphStringSorterRemap_t> dRemapAttr;
9015 			sphSortGetStringRemap ( tRes.m_tSchema, tRes.m_tSchema, dRemapAttr );
9016 			int iRemapCount = dRemapAttr.GetLength();
9017 			sphSortGetStringRemap ( pSorter->GetSchema(), tRes.m_tSchema, dRemapAttr );
9018 
9019 			bAllEqual = ( dRemapAttr.GetLength()<=iRemapCount );
9020 		}
9021 
9022 		// sorter expects this
9023 		tRes.m_tSchema = pSorter->GetSchema();
9024 
9025 		// convert all matches to sorter schema - at least to manage all static to dynamic
9026 		if ( !bAllEqual )
9027 		{
9028 			// post-limit stuff first
9029 			if ( iLocals )
9030 			{
9031 				ESphQueryState eOld = SPH_QSTATE_TOTAL;
9032 				if ( pProfiler )
9033 					eOld = pProfiler->Switch ( SPH_QSTATE_EVAL_POST );
9034 				ProcessLocalPostlimit ( tQuery, tRes );
9035 				if ( pProfiler )
9036 					pProfiler->Switch ( eOld );
9037 			}
9038 
9039 			RemapResult ( &tRes.m_tSchema, &tRes );
9040 		}
9041 		RemapStrings ( pSorter, tRes );
9042 
9043 		// do the sort work!
9044 		tRes.m_iTotalMatches -= KillAllDupes ( pSorter, tRes );
9045 	}
9046 
9047 	// apply outer order clause to single result set
9048 	// (multiple combined sets just got reordered above)
9049 	// apply inner limit first
9050 	if ( tRes.m_iSuccesses==1 && tQuery.m_bHasOuter )
9051 		tRes.ClampMatches ( tQuery.m_iLimit, bAllEqual );
9052 
9053 	if ( tRes.m_iSuccesses==1 && tQuery.m_bHasOuter && !tQuery.m_sOuterOrderBy.IsEmpty() )
9054 	{
9055 		// reorder (aka outer order)
9056 		ESphSortFunc eFunc;
9057 		GenericMatchSort_fn tReorder;
9058 
9059 		ESortClauseParseResult eRes = sphParseSortClause ( &tQuery, tQuery.m_sOuterOrderBy.cstr(),
9060 			tRes.m_tSchema, eFunc, tReorder, tRes.m_sError );
9061 		if ( eRes==SORT_CLAUSE_RANDOM )
9062 			tRes.m_sError = "order by rand() not supported in outer select";
9063 		if ( eRes!=SORT_CLAUSE_OK )
9064 			return false;
9065 
9066 		assert ( eFunc==FUNC_GENERIC2 || eFunc==FUNC_GENERIC3 || eFunc==FUNC_GENERIC4 || eFunc==FUNC_GENERIC5 );
9067 		sphSort ( tRes.m_dMatches.Begin(), tRes.m_dMatches.GetLength(), tReorder, MatchSortAccessor_t() );
9068 	}
9069 
9070 	// compute post-limit stuff
9071 	if ( bAllEqual && iLocals )
9072 	{
9073 		ESphQueryState eOld = SPH_QSTATE_TOTAL;
9074 		if ( pProfiler )
9075 			eOld = pProfiler->Switch ( SPH_QSTATE_EVAL_POST );
9076 
9077 		CSphVector<const CSphColumnInfo *> dPostlimit;
9078 		ExtractPostlimit ( tRes.m_tSchema, dPostlimit );
9079 
9080 		// post compute matches only between offset - limit
9081 		// however at agent we can't estimate limit.offset at master merged result set
9082 		// but master don't provide offset to agents only offset+limit as limit
9083 		// so computing all matches up to iiner.limit \ outer.limit
9084 		int iTo = tRes.m_dMatches.GetLength();
9085 		int iOff = Max ( tQuery.m_iOffset, tQuery.m_iOuterOffset );
9086 		int iCount = ( tQuery.m_iOuterLimit ? tQuery.m_iOuterLimit : tQuery.m_iLimit );
9087 		iTo = Max ( Min ( iOff + iCount, iTo ), 0 );
9088 		int iFrom = Min ( iOff, iTo );
9089 
9090 		ProcessPostlimit ( dPostlimit, iFrom, iTo, tRes );
9091 
9092 		if ( pProfiler )
9093 			pProfiler->Switch ( eOld );
9094 	}
9095 
9096 	// remap groupby() and aliased groupby() to @groupbystr or string attribute
9097 	const CSphColumnInfo * p = tRes.m_tSchema.GetAttr ( "@groupbystr" );
9098 	if ( !p )
9099 	{
9100 		// try string attribute (multiple group-by still displays hashes)
9101 		if ( !tQuery.m_sGroupBy.IsEmpty() )
9102 		{
9103 			p = tRes.m_tSchema.GetAttr ( tQuery.m_sGroupBy.cstr() );
9104 			if ( p && ( p->m_eAttrType!=SPH_ATTR_STRING && p->m_eAttrType!=SPH_ATTR_STRINGPTR ) )
9105 				p = NULL;
9106 		}
9107 	}
9108 	if ( p )
9109 	{
9110 		ARRAY_FOREACH ( i, dFrontend )
9111 		{
9112 			CSphColumnInfo & d = dFrontend[i];
9113 			if ( d.m_sName=="groupby()" )
9114 			{
9115 				d.m_tLocator = p->m_tLocator;
9116 				d.m_eAttrType = p->m_eAttrType;
9117 				d.m_eAggrFunc = p->m_eAggrFunc;
9118 			}
9119 		}
9120 
9121 		// check aliases too
9122 		ARRAY_FOREACH ( j, tQuery.m_dItems )
9123 		{
9124 			const CSphQueryItem & tItem = tQuery.m_dItems[j];
9125 			if ( tItem.m_sExpr=="groupby()" )
9126 			{
9127 				ARRAY_FOREACH ( i, dFrontend )
9128 				{
9129 					CSphColumnInfo & d = dFrontend[i];
9130 					if ( d.m_sName==tItem.m_sAlias )
9131 					{
9132 						d.m_tLocator = p->m_tLocator;
9133 						d.m_eAttrType = p->m_eAttrType;
9134 						d.m_eAggrFunc = p->m_eAggrFunc;
9135 					}
9136 				}
9137 			}
9138 		}
9139 	}
9140 
9141 	// facets
9142 	if ( tQuery.m_bFacet )
9143 	{
9144 		// remap MVA/JSON column to @groupby/@groupbystr in facet queries
9145 		const CSphColumnInfo * p = tRes.m_tSchema.GetAttr ( "@groupbystr" );
9146 		if ( !p )
9147 			p = tRes.m_tSchema.GetAttr ( "@groupby" );
9148 		if ( p )
9149 		{
9150 			ARRAY_FOREACH ( i, dFrontend )
9151 			{
9152 				CSphColumnInfo & d = dFrontend[i];
9153 				if ( tQuery.m_sGroupBy==d.m_sName &&
9154 					( d.m_eAttrType==SPH_ATTR_INT64SET
9155 					|| d.m_eAttrType==SPH_ATTR_UINT32SET
9156 					|| d.m_eAttrType==SPH_ATTR_JSON_FIELD ) )
9157 				{
9158 					d.m_tLocator = p->m_tLocator;
9159 					d.m_eAttrType = p->m_eAttrType;
9160 					d.m_eAggrFunc = p->m_eAggrFunc;
9161 				}
9162 			}
9163 		}
9164 
9165 		// skip all fields after count(*)
9166 		ARRAY_FOREACH ( i, dFrontend )
9167 			if ( dFrontend[i].m_sName=="count(*)" )
9168 			{
9169 				dFrontend.Resize ( i+1 );
9170 				break;
9171 			}
9172 	}
9173 
9174 	// all the merging and sorting is now done
9175 	// replace the minimized matches schema with its subset, the result set schema
9176 	tRes.m_tSchema.SwapAttrs ( dFrontend );
9177 	return true;
9178 }
9179 
9180 /////////////////////////////////////////////////////////////////////////////
9181 
9182 class CSphSchemaMT : public CSphSchema
9183 {
9184 public:
CSphSchemaMT(const char * sName="(nameless)")9185 	explicit				CSphSchemaMT ( const char * sName="(nameless)" ) : CSphSchema ( sName ), m_pLock ( NULL )
9186 	{}
9187 
AwareMT()9188 	void AwareMT()
9189 	{
9190 		if ( m_pLock )
9191 			return;
9192 		m_pLock = new CSphRwlock();
9193 		m_pLock->Init();
9194 	}
9195 
~CSphSchemaMT()9196 	~CSphSchemaMT()
9197 	{
9198 		if ( m_pLock )
9199 			Verify ( m_pLock->Done() );
9200 		SafeDelete ( m_pLock )
9201 	}
9202 
9203 	// get wlocked entry, only if it is not yet touched
GetVirgin()9204 	inline CSphSchemaMT * GetVirgin ()
9205 	{
9206 		if ( !m_pLock )
9207 			return this;
9208 
9209 		if ( m_pLock->WriteLock() )
9210 		{
9211 			if ( m_dAttrs.GetLength()!=0 ) // not already a virgin
9212 			{
9213 				m_pLock->Unlock();
9214 				return NULL;
9215 			}
9216 			return this;
9217 		} else
9218 		{
9219 			sphLogDebug ( "WriteLock %p failed", this );
9220 			assert ( false );
9221 		}
9222 
9223 		return NULL;
9224 	}
9225 
RLock()9226 	inline CSphSchemaMT * RLock()
9227 	{
9228 		if ( !m_pLock )
9229 			return this;
9230 
9231 		if ( !m_pLock->ReadLock() )
9232 		{
9233 			sphLogDebug ( "ReadLock %p failed", this );
9234 			assert ( false );
9235 		}
9236 		return this;
9237 	}
9238 
UnLock() const9239 	inline void UnLock() const
9240 	{
9241 		if ( m_pLock )
9242 			m_pLock->Unlock();
9243 	}
9244 
9245 private:
9246 	mutable CSphRwlock * m_pLock;
9247 };
9248 
9249 class UnlockOnDestroy
9250 {
9251 public:
UnlockOnDestroy(const CSphSchemaMT * lock)9252 	explicit UnlockOnDestroy ( const CSphSchemaMT * lock ) : m_pLock ( lock )
9253 	{}
~UnlockOnDestroy()9254 	inline ~UnlockOnDestroy()
9255 	{
9256 		if ( m_pLock )
9257 			m_pLock->UnLock();
9258 	}
9259 private:
9260 	const CSphSchemaMT * m_pLock;
9261 };
9262 
9263 
StringBinary2Number(const char * sStr,int iLen)9264 static int StringBinary2Number ( const char * sStr, int iLen )
9265 {
9266 	if ( !sStr || !iLen )
9267 		return 0;
9268 
9269 	char sBuf[64];
9270 	if ( (int)(sizeof ( sBuf )-1 )<iLen )
9271 		iLen = sizeof ( sBuf )-1;
9272 	memcpy ( sBuf, sStr, iLen );
9273 	sBuf[iLen] = '\0';
9274 
9275 	return atoi ( sBuf );
9276 }
9277 
SnippetTransformPassageMacros(CSphString & sSrc,CSphString & sPost)9278 static bool SnippetTransformPassageMacros ( CSphString & sSrc, CSphString & sPost )
9279 {
9280 	const char sPassageMacro[] = "%PASSAGE_ID%";
9281 
9282 	const char * sPass = NULL;
9283 	if ( !sSrc.IsEmpty() )
9284 		sPass = strstr ( sSrc.cstr(), sPassageMacro );
9285 
9286 	if ( !sPass )
9287 		return false;
9288 
9289 	int iSrcLen = sSrc.Length();
9290 	int iPassLen = sizeof ( sPassageMacro ) - 1;
9291 	int iTailLen = iSrcLen - iPassLen - ( sPass - sSrc.cstr() );
9292 
9293 	// copy tail
9294 	if ( iTailLen )
9295 		sPost.SetBinary ( sPass+iPassLen, iTailLen );
9296 
9297 	CSphString sPre;
9298 	sPre.SetBinary ( sSrc.cstr(), sPass - sSrc.cstr() );
9299 	sSrc.Swap ( sPre );
9300 
9301 	return true;
9302 }
9303 
9304 
9305 /// suddenly, searchd-level expression function!
9306 struct Expr_Snippet_c : public ISphStringExpr
9307 {
9308 	ISphExpr *					m_pArgs;
9309 	ISphExpr *					m_pText;
9310 	const BYTE *				m_sWords;
9311 	CSphIndex *					m_pIndex;
9312 	SnippetContext_t			m_tCtx;
9313 	mutable ExcerptQuery_t		m_tHighlight;
9314 	CSphQueryProfile *			m_pProfiler;
9315 
Expr_Snippet_cExpr_Snippet_c9316 	explicit Expr_Snippet_c ( ISphExpr * pArglist, CSphIndex * pIndex, CSphQueryProfile * pProfiler, CSphString & sError )
9317 		: m_pArgs ( pArglist )
9318 		, m_pText ( NULL )
9319 		, m_sWords ( NULL )
9320 		, m_pIndex ( pIndex )
9321 		, m_pProfiler ( pProfiler )
9322 	{
9323 		assert ( pArglist->IsArglist() );
9324 		m_pText = pArglist->GetArg(0);
9325 
9326 		CSphMatch tDummy;
9327 		char * pWords;
9328 		assert ( !pArglist->GetArg(1)->IsStringPtr() ); // aware of memleaks potentially caused by StringEval()
9329 		pArglist->GetArg(1)->StringEval ( tDummy, (const BYTE**)&pWords );
9330 		m_tHighlight.m_sWords = pWords;
9331 
9332 		for ( int i = 2; i < pArglist->GetNumArgs(); i++ )
9333 		{
9334 			assert ( !pArglist->GetArg(i)->IsStringPtr() ); // aware of memleaks potentially caused by StringEval()
9335 			int iLen = pArglist->GetArg(i)->StringEval ( tDummy, (const BYTE**)&pWords );
9336 			if ( !pWords || !iLen )
9337 				continue;
9338 
9339 			const char * sEnd = pWords + iLen;
9340 			while ( pWords<sEnd && *pWords && sphIsSpace ( *pWords ) )	pWords++;
9341 			char * szOption = pWords;
9342 			while ( pWords<sEnd && *pWords && sphIsAlpha ( *pWords ) )	pWords++;
9343 			char * szOptEnd = pWords;
9344 			while ( pWords<sEnd && *pWords && sphIsSpace ( *pWords ) )	pWords++;
9345 
9346 			if ( *pWords++!='=' )
9347 			{
9348 				sError.SetSprintf ( "Error parsing SNIPPET options: %s", pWords );
9349 				return;
9350 			}
9351 
9352 			*szOptEnd = '\0';
9353 			while ( pWords<sEnd && *pWords && sphIsSpace ( *pWords ) )	pWords++;
9354 			char * sValue = pWords;
9355 
9356 			if ( !*sValue )
9357 			{
9358 				sError.SetSprintf ( "Error parsing SNIPPET options" );
9359 				return;
9360 			}
9361 
9362 			while ( pWords<sEnd && *pWords ) pWords++;
9363 			int iStrValLen = pWords - sValue;
9364 
9365 			if ( !strcasecmp ( szOption, "before_match" ) )					{ m_tHighlight.m_sBeforeMatch.SetBinary ( sValue, iStrValLen ); }
9366 			else if ( !strcasecmp ( szOption, "after_match" ) )				{ m_tHighlight.m_sAfterMatch.SetBinary ( sValue, iStrValLen ); }
9367 			else if ( !strcasecmp ( szOption, "chunk_separator" ) )			{ m_tHighlight.m_sChunkSeparator.SetBinary ( sValue, iStrValLen ); }
9368 			else if ( !strcasecmp ( szOption, "limit" ) )					{ m_tHighlight.m_iLimit = StringBinary2Number ( sValue, iStrValLen ); }
9369 			else if ( !strcasecmp ( szOption, "around" ) )					{ m_tHighlight.m_iAround = StringBinary2Number ( sValue, iStrValLen ); }
9370 			else if ( !strcasecmp ( szOption, "exact_phrase" ) )			{ m_tHighlight.m_bExactPhrase = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9371 			else if ( !strcasecmp ( szOption, "use_boundaries" ) )			{ m_tHighlight.m_bUseBoundaries = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9372 			else if ( !strcasecmp ( szOption, "weight_order" ) )			{ m_tHighlight.m_bWeightOrder = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9373 			else if ( !strcasecmp ( szOption, "query_mode" ) )				{ m_tHighlight.m_bHighlightQuery = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9374 			else if ( !strcasecmp ( szOption, "force_all_words" ) )			{ m_tHighlight.m_bForceAllWords = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9375 			else if ( !strcasecmp ( szOption, "limit_passages" ) )			{ m_tHighlight.m_iLimitPassages = StringBinary2Number ( sValue, iStrValLen ); }
9376 			else if ( !strcasecmp ( szOption, "limit_words" ) )				{ m_tHighlight.m_iLimitWords = StringBinary2Number ( sValue, iStrValLen ); }
9377 			else if ( !strcasecmp ( szOption, "start_passage_id" ) )		{ m_tHighlight.m_iPassageId = StringBinary2Number ( sValue, iStrValLen ); }
9378 			else if ( !strcasecmp ( szOption, "load_files" ) )				{ m_tHighlight.m_iLoadFiles |= ( StringBinary2Number ( sValue, iStrValLen )!=0 ? 1 : 0 ); }
9379 			else if ( !strcasecmp ( szOption, "load_files_scattered" ) )	{ m_tHighlight.m_iLoadFiles |= ( StringBinary2Number ( sValue, iStrValLen )!=0 ? 2 : 0 ); }
9380 			else if ( !strcasecmp ( szOption, "html_strip_mode" ) )			{ m_tHighlight.m_sStripMode.SetBinary ( sValue, iStrValLen ); }
9381 			else if ( !strcasecmp ( szOption, "allow_empty" ) )				{ m_tHighlight.m_bAllowEmpty= ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9382 			else if ( !strcasecmp ( szOption, "passage_boundary" ) )		{ m_tHighlight.m_sRawPassageBoundary.SetBinary ( sValue, iStrValLen ); }
9383 			else if ( !strcasecmp ( szOption, "emit_zones" ) )				{ m_tHighlight.m_bEmitZones = ( StringBinary2Number ( sValue, iStrValLen )!=0 ); }
9384 			else
9385 			{
9386 				CSphString sBuf;
9387 				sBuf.SetBinary ( sValue, iStrValLen );
9388 				sError.SetSprintf ( "Unknown SNIPPET option: %s=%s", szOption, sBuf.cstr() );
9389 				return;
9390 			}
9391 		}
9392 
9393 		m_tHighlight.m_bHasBeforePassageMacro = SnippetTransformPassageMacros ( m_tHighlight.m_sBeforeMatch, m_tHighlight.m_sBeforeMatchPassage );
9394 		m_tHighlight.m_bHasAfterPassageMacro = SnippetTransformPassageMacros ( m_tHighlight.m_sAfterMatch, m_tHighlight.m_sAfterMatchPassage );
9395 
9396 		m_tCtx.Setup ( m_pIndex, m_tHighlight, sError );
9397 	}
9398 
~Expr_Snippet_cExpr_Snippet_c9399 	~Expr_Snippet_c()
9400 	{
9401 		SafeRelease ( m_pArgs );
9402 	}
9403 
StringEvalExpr_Snippet_c9404 	virtual int StringEval ( const CSphMatch & tMatch, const BYTE ** ppStr ) const
9405 	{
9406 		ESphQueryState eOld = SPH_QSTATE_TOTAL;
9407 		if ( m_pProfiler )
9408 			eOld = m_pProfiler->Switch ( SPH_QSTATE_SNIPPET );
9409 
9410 		*ppStr = NULL;
9411 
9412 		const BYTE * sSource = NULL;
9413 		int iLen = m_pText->StringEval ( tMatch, &sSource );
9414 
9415 		if ( !iLen )
9416 		{
9417 			if ( m_pText->IsStringPtr() )
9418 				SafeDeleteArray ( sSource );
9419 			if ( m_pProfiler )
9420 				m_pProfiler->Switch ( eOld );
9421 			return 0;
9422 		}
9423 
9424 		// for dynamic strings (eg. fetched by UDFs), just take ownership
9425 		// for static ones (eg. attributes), treat as binary (ie. mind that
9426 		// the trailing zero is NOT guaranteed), and copy them
9427 		if ( m_pText->IsStringPtr() )
9428 			m_tHighlight.m_sSource.Adopt ( (char**)&sSource );
9429 		else
9430 			m_tHighlight.m_sSource.SetBinary ( (const char*)sSource, iLen );
9431 
9432 		// FIXME! fill in all the missing options; use consthash?
9433 		CSphString sWarning, sError;
9434 		sphBuildExcerpt ( m_tHighlight, m_pIndex, m_tCtx.m_tStripper.Ptr(), m_tCtx.m_tExtQuery, m_tCtx.m_eExtQuerySPZ,
9435 			sWarning, sError, m_tCtx.m_pDict, m_tCtx.m_tTokenizer.Ptr(), m_tCtx.m_pQueryTokenizer );
9436 
9437 		int iRes = m_tHighlight.m_dRes.GetLength();
9438 		*ppStr = m_tHighlight.m_dRes.LeakData();
9439 		if ( m_pProfiler )
9440 			m_pProfiler->Switch ( eOld );
9441 		return iRes;
9442 	}
9443 
CommandExpr_Snippet_c9444 	virtual void Command ( ESphExprCommand eCmd, void * pArg )
9445 	{
9446 		if ( eCmd!=SPH_EXPR_SET_STRING_POOL )
9447 			return;
9448 
9449 		if ( m_pArgs )
9450 			m_pArgs->Command ( SPH_EXPR_SET_STRING_POOL, pArg );
9451 		if ( m_pText )
9452 			m_pText->Command ( SPH_EXPR_SET_STRING_POOL, pArg );
9453 	}
9454 };
9455 
9456 
9457 /// searchd expression hook
9458 /// needed to implement functions that are builtin for searchd,
9459 /// but can not be builtin in the generic expression engine itself,
9460 /// like SNIPPET() function that must know about indexes, tokenizers, etc
9461 struct ExprHook_t : public ISphExprHook
9462 {
9463 	static const int HOOK_SNIPPET = 1;
9464 	CSphIndex * m_pIndex; /// BLOODY HACK
9465 	CSphQueryProfile * m_pProfiler;
9466 
ExprHook_tExprHook_t9467 	ExprHook_t ()
9468 		: m_pIndex ( NULL )
9469 		, m_pProfiler ( NULL )
9470 	{}
9471 
IsKnownIdentExprHook_t9472 	virtual int IsKnownIdent ( const char * )
9473 	{
9474 		return -1;
9475 	}
9476 
IsKnownFuncExprHook_t9477 	virtual int IsKnownFunc ( const char * sFunc )
9478 	{
9479 		if ( !strcasecmp ( sFunc, "SNIPPET" ) )
9480 			return HOOK_SNIPPET;
9481 		else
9482 			return -1;
9483 	}
9484 
CreateNodeExprHook_t9485 	virtual ISphExpr * CreateNode ( int DEBUGARG(iID), ISphExpr * pLeft, ESphEvalStage * pEvalStage, CSphString & sError )
9486 	{
9487 		assert ( iID==HOOK_SNIPPET );
9488 		if ( pEvalStage )
9489 			*pEvalStage = SPH_EVAL_POSTLIMIT;
9490 
9491 		ISphExpr * pRes = new Expr_Snippet_c ( pLeft, m_pIndex, m_pProfiler, sError );
9492 		if ( sError.Length() )
9493 			SafeDelete ( pRes );
9494 
9495 		return pRes;
9496 	}
9497 
GetIdentTypeExprHook_t9498 	virtual ESphAttr GetIdentType ( int )
9499 	{
9500 		assert ( 0 );
9501 		return SPH_ATTR_NONE;
9502 	}
9503 
GetReturnTypeExprHook_t9504 	virtual ESphAttr GetReturnType ( int DEBUGARG(iID), const CSphVector<ESphAttr> & dArgs, bool, CSphString & sError )
9505 	{
9506 		assert ( iID==HOOK_SNIPPET );
9507 		if ( dArgs.GetLength()<2 )
9508 		{
9509 			sError = "SNIPPET() requires 2 or more arguments";
9510 			return SPH_ATTR_NONE;
9511 		}
9512 		if ( dArgs[0]!=SPH_ATTR_STRINGPTR && dArgs[0]!=SPH_ATTR_STRING )
9513 		{
9514 			sError = "1st argument to SNIPPET() must be a string expression";
9515 			return SPH_ATTR_NONE;
9516 		}
9517 
9518 		for ( int i = 1; i < dArgs.GetLength(); i++ )
9519 			if ( dArgs[i]!=SPH_ATTR_STRING )
9520 			{
9521 				sError.SetSprintf ( "%d argument to SNIPPET() must be a constant string", i );
9522 				return SPH_ATTR_NONE;
9523 			}
9524 
9525 		return SPH_ATTR_STRINGPTR;
9526 	}
9527 
CheckEnterExprHook_t9528 	virtual void CheckEnter ( int ) {}
CheckExitExprHook_t9529 	virtual void CheckExit ( int ) {}
9530 };
9531 
9532 struct LocalIndex_t
9533 {
9534 	CSphString	m_sName;
9535 	int			m_iOrderTag;
9536 	int			m_iWeight;
9537 	int64_t		m_iMass;
LocalIndex_tLocalIndex_t9538 	LocalIndex_t ()
9539 		: m_iOrderTag ( 0 )
9540 		, m_iWeight ( 1 )
9541 	{ }
9542 };
9543 
9544 
9545 class SearchHandler_c
9546 {
9547 	friend void LocalSearchThreadFunc ( void * pArg );
9548 
9549 public:
9550 	explicit						SearchHandler_c ( int iQueries, bool bSphinxql, bool bMaster );
9551 	~SearchHandler_c();
9552 	void							RunQueries ();					///< run all queries, get all results
9553 	void							RunUpdates ( const CSphQuery & tQuery, const CSphString & sIndex, CSphAttrUpdateEx * pUpdates ); ///< run Update command instead of Search
9554 	void							RunDeletes ( const CSphQuery & tQuery, const CSphString & sIndex, CSphString * pErrors, CSphVector<SphDocID_t>* ); ///< run Delete command instead of Search
9555 
9556 public:
9557 	CSphVector<CSphQuery>			m_dQueries;						///< queries which i need to search
9558 	CSphVector<AggrResult_t>		m_dResults;						///< results which i obtained
9559 	CSphVector<SearchFailuresLog_c>	m_dFailuresSet;					///< failure logs for each query
9560 	CSphVector < CSphVector<int64_t> >	m_dAgentTimes;				///< per-agent time stats
9561 	CSphQueryProfile *				m_pProfile;
9562 	CSphVector<DWORD *>				m_dMva2Free;
9563 	CSphVector<BYTE *>				m_dString2Free;
9564 
9565 protected:
9566 	void							RunSubset ( int iStart, int iEnd );	///< run queries against index(es) from first query in the subset
9567 	void							RunLocalSearches ( ISphMatchSorter * pLocalSorter, const char * sDistName, DWORD uFactorFlags );
9568 	void							RunLocalSearchesMT ();
9569 	bool							RunLocalSearch ( int iLocal, ISphMatchSorter ** ppSorters, CSphQueryResult ** pResults, bool * pMulti ) const;
9570 	bool							AllowsMulti ( int iStart, int iEnd ) const;
9571 	void							SetupLocalDF ( int iStart, int iEnd );
9572 
9573 	int								m_iStart;		///< subset start
9574 	int								m_iEnd;			///< subset end
9575 	bool							m_bMultiQueue;	///< whether current subset is subject to multi-queue optimization
9576 	bool							m_bFacetQueue;	///< whether current subset is subject to facet-queue optimization
9577 	CSphVector<LocalIndex_t>		m_dLocal;		///< local indexes for the current subset
9578 	mutable CSphVector<CSphSchemaMT>		m_dExtraSchemas; ///< the extra fields for agents
9579 	bool							m_bSphinxql;	///< if the query get from sphinxql - to avoid applying sphinxql magick for others
9580 	CSphAttrUpdateEx *				m_pUpdates;		///< holder for updates
9581 	CSphVector<SphDocID_t> *		m_pDelete;		///< this query is for deleting
9582 
9583 	mutable CSphProcessSharedMutex	m_tLock;
9584 	mutable SmallStringHash_T<int>	m_hUsed;
9585 
9586 	mutable ExprHook_t				m_tHook;
9587 
9588 	SmallStringHash_T < int64_t >	m_hLocalDocs;
9589 	int64_t							m_iTotalDocs;
9590 	bool							m_bGotLocalDF;
9591 	bool							m_bMaster;
9592 
9593 	const ServedIndex_t *			UseIndex ( int iLocal ) const;
9594 	void							ReleaseIndex ( int iLocal ) const;
9595 
9596 	void							OnRunFinished ();
9597 };
9598 
9599 
SearchHandler_c(int iQueries,bool bSphinxql,bool bMaster)9600 SearchHandler_c::SearchHandler_c ( int iQueries, bool bSphinxql, bool bMaster )
9601 {
9602 	m_iStart = 0;
9603 	m_iEnd = 0;
9604 	m_bMultiQueue = false;
9605 	m_bFacetQueue = false;
9606 
9607 	m_dQueries.Resize ( iQueries );
9608 	m_dResults.Resize ( iQueries );
9609 	m_dFailuresSet.Resize ( iQueries );
9610 	m_dExtraSchemas.Resize ( iQueries );
9611 	m_dAgentTimes.Resize ( iQueries );
9612 	m_bSphinxql = bSphinxql;
9613 	m_pUpdates = NULL;
9614 	m_pDelete = NULL;
9615 
9616 	m_pProfile = NULL;
9617 	m_tHook.m_pProfiler = NULL;
9618 	m_iTotalDocs = 0;
9619 	m_bGotLocalDF = false;
9620 	m_bMaster = bMaster;
9621 
9622 	assert ( !m_tLock.GetError() );
9623 }
9624 
9625 
~SearchHandler_c()9626 SearchHandler_c::~SearchHandler_c ()
9627 {
9628 	m_hUsed.IterateStart();
9629 	while ( m_hUsed.IterateNext() )
9630 	{
9631 		if ( m_hUsed.IterateGet()>0 )
9632 			g_pLocalIndexes->GetUnlockedEntry ( m_hUsed.IterateGetKey() ).Unlock();
9633 	}
9634 
9635 	ARRAY_FOREACH ( i, m_dMva2Free )
9636 		SafeDeleteArray ( m_dMva2Free[i] );
9637 	ARRAY_FOREACH ( i, m_dString2Free )
9638 		SafeDeleteArray ( m_dString2Free[i] );
9639 }
9640 
9641 
UseIndex(int iLocal) const9642 const ServedIndex_t * SearchHandler_c::UseIndex ( int iLocal ) const
9643 {
9644 	assert ( iLocal>=0 && iLocal<m_dLocal.GetLength() );
9645 	const CSphString & sName = m_dLocal[iLocal].m_sName;
9646 
9647 	m_tLock.Lock();
9648 	int * pUseCount = m_hUsed ( sName );
9649 	assert ( ( m_pUpdates && pUseCount && *pUseCount>0 ) || !m_pUpdates );
9650 
9651 	const ServedIndex_t * pServed = NULL;
9652 	if ( pUseCount && *pUseCount>0 )
9653 	{
9654 		pServed = &g_pLocalIndexes->GetUnlockedEntry ( sName );
9655 		*pUseCount += 1;
9656 	} else
9657 	{
9658 		pServed = g_pLocalIndexes->GetRlockedEntry ( sName );
9659 		if ( pServed )
9660 		{
9661 			if ( pUseCount )
9662 				(*pUseCount)++;
9663 			else
9664 				m_hUsed.Add ( 1, sName );
9665 		}
9666 	}
9667 
9668 	m_tLock.Unlock();
9669 	return pServed;
9670 }
9671 
9672 
ReleaseIndex(int iLocal) const9673 void SearchHandler_c::ReleaseIndex ( int iLocal ) const
9674 {
9675 	assert ( iLocal>=0 && iLocal<m_dLocal.GetLength() );
9676 
9677 	const CSphString & sName = m_dLocal[iLocal].m_sName;
9678 	m_tLock.Lock();
9679 
9680 	int * pUseCount = m_hUsed ( sName );
9681 	assert ( pUseCount && *pUseCount>=0 );
9682 	(*pUseCount)--;
9683 
9684 	if ( !*pUseCount )
9685 		g_pLocalIndexes->GetUnlockedEntry ( sName ).Unlock();
9686 
9687 	assert ( ( m_pUpdates && pUseCount && *pUseCount ) || !m_pUpdates );
9688 
9689 	m_tLock.Unlock();
9690 }
9691 
RunUpdates(const CSphQuery & tQuery,const CSphString & sIndex,CSphAttrUpdateEx * pUpdates)9692 void SearchHandler_c::RunUpdates ( const CSphQuery & tQuery, const CSphString & sIndex, CSphAttrUpdateEx * pUpdates )
9693 {
9694 	m_pUpdates = pUpdates;
9695 
9696 	m_dQueries[0] = tQuery;
9697 	m_dQueries[0].m_sIndexes = sIndex;
9698 
9699 	// lets add index to prevent deadlock
9700 	// as index already r-locker or w-locked at this point
9701 	m_dLocal.Add().m_sName = sIndex;
9702 	m_hUsed.Add ( 1, sIndex );
9703 	m_dResults[0].m_dTag2Pools.Resize ( 1 );
9704 
9705 	CheckQuery ( tQuery, *pUpdates->m_pError );
9706 	if ( !pUpdates->m_pError->IsEmpty() )
9707 		return;
9708 
9709 	int64_t tmLocal = -sphMicroTimer();
9710 
9711 	RunLocalSearches ( NULL, NULL, SPH_FACTOR_DISABLE );
9712 	tmLocal += sphMicroTimer();
9713 
9714 	OnRunFinished();
9715 
9716 	CSphQueryResult & tRes = m_dResults[0];
9717 
9718 	tRes.m_iOffset = tQuery.m_iOffset;
9719 	tRes.m_iCount = Max ( Min ( tQuery.m_iLimit, tRes.m_dMatches.GetLength()-tQuery.m_iOffset ), 0 );
9720 
9721 	tRes.m_iQueryTime += (int)(tmLocal/1000);
9722 	tRes.m_iCpuTime += tmLocal;
9723 
9724 	if ( !tRes.m_iSuccesses )
9725 	{
9726 		CSphStringBuilder sFailures;
9727 		m_dFailuresSet[0].BuildReport ( sFailures );
9728 		*pUpdates->m_pError = sFailures.cstr();
9729 
9730 	} else if ( !tRes.m_sError.IsEmpty() )
9731 	{
9732 		CSphStringBuilder sFailures;
9733 		m_dFailuresSet[0].BuildReport ( sFailures );
9734 		tRes.m_sWarning = sFailures.cstr(); // FIXME!!! commit warnings too
9735 	}
9736 
9737 	if ( g_pStats )
9738 	{
9739 		const CSphIOStats & tIO = tRes.m_tIOStats;
9740 
9741 		g_tStatsMutex.Lock();
9742 		g_pStats->m_iQueries += 1;
9743 		g_pStats->m_iQueryTime += tmLocal;
9744 		g_pStats->m_iQueryCpuTime += tmLocal;
9745 		g_pStats->m_iDiskReads += tIO.m_iReadOps;
9746 		g_pStats->m_iDiskReadTime += tIO.m_iReadTime;
9747 		g_pStats->m_iDiskReadBytes += tIO.m_iReadBytes;
9748 		g_tStatsMutex.Unlock();
9749 	}
9750 
9751 	LogQuery ( m_dQueries[0], m_dResults[0], m_dAgentTimes[0] );
9752 }
9753 
RunDeletes(const CSphQuery & tQuery,const CSphString & sIndex,CSphString * pErrors,CSphVector<SphDocID_t> * pValues)9754 void SearchHandler_c::RunDeletes ( const CSphQuery & tQuery, const CSphString & sIndex, CSphString * pErrors, CSphVector<SphDocID_t>* pValues )
9755 {
9756 	m_pDelete = pValues;
9757 	m_dQueries[0] = tQuery;
9758 	m_dQueries[0].m_sIndexes = sIndex;
9759 
9760 	// lets add index to prevent deadlock
9761 	// as index already w-locked at this point
9762 	m_dLocal.Add().m_sName = sIndex;
9763 	m_hUsed.Add ( 1, sIndex );
9764 	m_dResults[0].m_dTag2Pools.Resize ( 1 );
9765 
9766 	CheckQuery ( tQuery, *pErrors );
9767 	if ( !pErrors->IsEmpty() )
9768 		return;
9769 
9770 	int64_t tmLocal = -sphMicroTimer();
9771 
9772 	RunLocalSearches ( NULL, NULL, SPH_FACTOR_DISABLE );
9773 	tmLocal += sphMicroTimer();
9774 
9775 	OnRunFinished();
9776 
9777 	CSphQueryResult & tRes = m_dResults[0];
9778 
9779 	tRes.m_iOffset = tQuery.m_iOffset;
9780 	tRes.m_iCount = Max ( Min ( tQuery.m_iLimit, tRes.m_dMatches.GetLength()-tQuery.m_iOffset ), 0 );
9781 
9782 	tRes.m_iQueryTime += (int)(tmLocal/1000);
9783 	tRes.m_iCpuTime += tmLocal;
9784 
9785 	if ( !tRes.m_iSuccesses )
9786 	{
9787 		CSphStringBuilder sFailures;
9788 		m_dFailuresSet[0].BuildReport ( sFailures );
9789 		*pErrors = sFailures.cstr();
9790 
9791 	} else if ( !tRes.m_sError.IsEmpty() )
9792 	{
9793 		CSphStringBuilder sFailures;
9794 		m_dFailuresSet[0].BuildReport ( sFailures );
9795 		tRes.m_sWarning = sFailures.cstr(); // FIXME!!! commit warnings too
9796 	}
9797 
9798 	if ( g_pStats )
9799 	{
9800 		const CSphIOStats & tIO = tRes.m_tIOStats;
9801 
9802 		g_tStatsMutex.Lock();
9803 		g_pStats->m_iQueries += 1;
9804 		g_pStats->m_iQueryTime += tmLocal;
9805 		g_pStats->m_iQueryCpuTime += tmLocal;
9806 		g_pStats->m_iDiskReads += tIO.m_iReadOps;
9807 		g_pStats->m_iDiskReadTime += tIO.m_iReadTime;
9808 		g_pStats->m_iDiskReadBytes += tIO.m_iReadBytes;
9809 		g_tStatsMutex.Unlock();
9810 	}
9811 
9812 	LogQuery ( m_dQueries[0], m_dResults[0], m_dAgentTimes[0] );
9813 }
9814 
RunQueries()9815 void SearchHandler_c::RunQueries()
9816 {
9817 	// check if all queries are to the same index
9818 	bool bSameIndex = ARRAY_ALL ( bSameIndex, m_dQueries, m_dQueries[_all].m_sIndexes==m_dQueries[0].m_sIndexes );
9819 	if ( bSameIndex )
9820 	{
9821 		// batch queries to same index
9822 		RunSubset ( 0, m_dQueries.GetLength()-1 );
9823 		ARRAY_FOREACH ( i, m_dQueries )
9824 			LogQuery ( m_dQueries[i], m_dResults[i], m_dAgentTimes[i] );
9825 	} else
9826 	{
9827 		// fallback; just work each query separately
9828 		ARRAY_FOREACH ( i, m_dQueries )
9829 		{
9830 			RunSubset ( i, i );
9831 			LogQuery ( m_dQueries[i], m_dResults[i], m_dAgentTimes[i] );
9832 		}
9833 	}
9834 	OnRunFinished();
9835 }
9836 
9837 
9838 // final fixup
OnRunFinished()9839 void SearchHandler_c::OnRunFinished()
9840 {
9841 	ARRAY_FOREACH ( i, m_dResults )
9842 	{
9843 		m_dResults[i].m_iMatches = m_dResults[i].m_dMatches.GetLength();
9844 	}
9845 }
9846 
9847 
9848 /// return cpu time, in microseconds
sphCpuTimer()9849 int64_t sphCpuTimer ()
9850 {
9851 #ifdef HAVE_CLOCK_GETTIME
9852 	if ( !g_bCpuStats )
9853 		return 0;
9854 
9855 #if defined (CLOCK_THREAD_CPUTIME_ID)
9856 // CPU time (user+sys), Linux style, current thread
9857 #define LOC_CLOCK CLOCK_THREAD_CPUTIME_ID
9858 #elif defined(CLOCK_PROCESS_CPUTIME_ID)
9859 // CPU time (user+sys), Linux style
9860 #define LOC_CLOCK CLOCK_PROCESS_CPUTIME_ID
9861 #elif defined(CLOCK_PROF)
9862 // CPU time (user+sys), FreeBSD style
9863 #define LOC_CLOCK CLOCK_PROF
9864 #else
9865 // POSIX fallback (wall time)
9866 #define LOC_CLOCK CLOCK_REALTIME
9867 #endif
9868 
9869 	struct timespec tp;
9870 	if ( clock_gettime ( LOC_CLOCK, &tp ) )
9871 		return 0;
9872 
9873 	return tp.tv_sec*1000000 + tp.tv_nsec/1000;
9874 #else
9875 	return 0;
9876 #endif
9877 }
9878 
9879 
9880 struct LocalSearch_t
9881 {
9882 	int					m_iLocal;
9883 	ISphMatchSorter **	m_ppSorters;
9884 	CSphQueryResult **	m_ppResults;
9885 	bool				m_bResult;
9886 	int64_t				m_iMass;
9887 };
9888 
9889 
9890 struct LocalSearchThreadContext_t
9891 {
9892 	SphThread_t					m_tThd;
9893 	SearchHandler_c *			m_pHandler;
9894 	CrashQuery_t				m_tCrashQuery;
9895 	int							m_iSearches;
9896 	LocalSearch_t *				m_pSearches;
9897 	CSphAtomic<long> *			m_pCurSearch;
9898 
LocalSearchThreadContext_tLocalSearchThreadContext_t9899 	LocalSearchThreadContext_t()
9900 		: m_pHandler ( NULL )
9901 		, m_iSearches ( 0 )
9902 		, m_pSearches ( NULL )
9903 		, m_pCurSearch ( NULL )
9904 	{}
9905 };
9906 
9907 
LocalSearchThreadFunc(void * pArg)9908 void LocalSearchThreadFunc ( void * pArg )
9909 {
9910 	LocalSearchThreadContext_t * pContext = (LocalSearchThreadContext_t*) pArg;
9911 
9912 	SphCrashLogger_c::SetLastQuery ( pContext->m_tCrashQuery );
9913 
9914 	for ( ;; )
9915 	{
9916 		if ( *pContext->m_pCurSearch>=pContext->m_iSearches )
9917 			return;
9918 
9919 		long iCurSearch = pContext->m_pCurSearch->Inc();
9920 		if ( iCurSearch>=pContext->m_iSearches )
9921 			return;
9922 		LocalSearch_t * pCall = pContext->m_pSearches + iCurSearch;
9923 		pCall->m_bResult = pContext->m_pHandler->RunLocalSearch ( pCall->m_iLocal,
9924 			pCall->m_ppSorters, pCall->m_ppResults, &pContext->m_pHandler->m_bMultiQueue );
9925 	}
9926 }
9927 
9928 
MergeWordStats(CSphQueryResultMeta & tDstResult,const SmallStringHash_T<CSphQueryResultMeta::WordStat_t> & hSrc,SearchFailuresLog_c * pLog,const char * sIndex)9929 static void MergeWordStats ( CSphQueryResultMeta & tDstResult,
9930 	const SmallStringHash_T<CSphQueryResultMeta::WordStat_t> & hSrc,
9931 	SearchFailuresLog_c * pLog, const char * sIndex )
9932 {
9933 	assert ( pLog );
9934 
9935 	if ( !tDstResult.m_hWordStats.GetLength() )
9936 	{
9937 		// nothing has been set yet; just copy
9938 		tDstResult.m_hWordStats = hSrc;
9939 		return;
9940 	}
9941 
9942 	CSphString sDiff;
9943 	SphWordStatChecker_t tDiff;
9944 	tDiff.Set ( hSrc );
9945 	tDiff.DumpDiffer ( tDstResult.m_hWordStats, NULL, sDiff );
9946 	if ( !sDiff.IsEmpty() )
9947 		pLog->SubmitEx ( sIndex, "%s", sDiff.cstr() );
9948 
9949 	hSrc.IterateStart();
9950 	while ( hSrc.IterateNext() )
9951 	{
9952 		const CSphQueryResultMeta::WordStat_t & tSrcStat = hSrc.IterateGet();
9953 		tDstResult.AddStat ( hSrc.IterateGetKey(), tSrcStat.m_iDocs, tSrcStat.m_iHits );
9954 	}
9955 }
9956 
9957 
CalcPredictedTimeMsec(const CSphQueryResult & tRes)9958 static int64_t CalcPredictedTimeMsec ( const CSphQueryResult & tRes )
9959 {
9960 	assert ( tRes.m_bHasPrediction );
9961 
9962 	int64_t iNanoResult = int64_t(g_iPredictorCostSkip)*tRes.m_tStats.m_iSkips+
9963 		g_iPredictorCostDoc*tRes.m_tStats.m_iFetchedDocs+
9964 		g_iPredictorCostHit*tRes.m_tStats.m_iFetchedHits+
9965 		g_iPredictorCostMatch*tRes.m_iTotalMatches;
9966 
9967 	return iNanoResult/1000000;
9968 }
9969 
9970 
FlattenToRes(ISphMatchSorter * pSorter,AggrResult_t & tRes,int iTag)9971 static void FlattenToRes ( ISphMatchSorter * pSorter, AggrResult_t & tRes, int iTag )
9972 {
9973 	assert ( pSorter );
9974 
9975 	if ( pSorter->GetLength() )
9976 	{
9977 		tRes.m_dSchemas.Add ( pSorter->GetSchema() );
9978 		PoolPtrs_t & tPoolPtrs = tRes.m_dTag2Pools[iTag];
9979 		assert ( !tPoolPtrs.m_pMva && !tPoolPtrs.m_pStrings );
9980 		tPoolPtrs.m_pMva = tRes.m_pMva;
9981 		tPoolPtrs.m_pStrings = tRes.m_pStrings;
9982 		tPoolPtrs.m_bArenaProhibit = tRes.m_bArenaProhibit;
9983 		int iCopied = sphFlattenQueue ( pSorter, &tRes, iTag );
9984 		tRes.m_dMatchCounts.Add ( iCopied );
9985 
9986 		// clean up for next index search
9987 		tRes.m_pMva = NULL;
9988 		tRes.m_pStrings = NULL;
9989 		tRes.m_bArenaProhibit = false;
9990 	}
9991 }
9992 
9993 
RemoveMissedRows(AggrResult_t & tRes)9994 static void RemoveMissedRows ( AggrResult_t & tRes )
9995 {
9996 	if ( !tRes.m_dMatchCounts.Last() )
9997 		return;
9998 
9999 	CSphMatch * pStart = tRes.m_dMatches.Begin() + tRes.m_dMatches.GetLength() - tRes.m_dMatchCounts.Last();
10000 	CSphMatch * pSrc = pStart;
10001 	CSphMatch * pDst = pStart;
10002 	const CSphMatch * pEnd = tRes.m_dMatches.Begin() + tRes.m_dMatches.GetLength();
10003 
10004 	while ( pSrc<pEnd )
10005 	{
10006 		if ( !pSrc->m_pStatic )
10007 		{
10008 			tRes.m_tSchema.FreeStringPtrs ( pSrc );
10009 			pSrc++;
10010 			continue;
10011 		}
10012 
10013 		Swap ( *pSrc, *pDst );
10014 		pSrc++;
10015 		pDst++;
10016 	}
10017 
10018 	tRes.m_dMatchCounts.Last() = pDst - pStart;
10019 	tRes.m_dMatches.Resize ( pDst - tRes.m_dMatches.Begin() );
10020 }
10021 
10022 
RunLocalSearchesMT()10023 void SearchHandler_c::RunLocalSearchesMT ()
10024 {
10025 	int64_t tmLocal = sphMicroTimer();
10026 
10027 	// setup local searches
10028 	const int iQueries = m_iEnd-m_iStart+1;
10029 	CSphVector<LocalSearch_t> dWorks ( m_dLocal.GetLength() );
10030 	CSphVector<CSphQueryResult> dResults ( m_dLocal.GetLength()*iQueries );
10031 	CSphVector<ISphMatchSorter*> dSorters ( m_dLocal.GetLength()*iQueries );
10032 	CSphVector<CSphQueryResult*> dResultPtrs ( m_dLocal.GetLength()*iQueries );
10033 
10034 	ARRAY_FOREACH ( i, dResultPtrs )
10035 		dResultPtrs[i] = &dResults[i];
10036 
10037 	ARRAY_FOREACH ( i, m_dLocal )
10038 	{
10039 		dWorks[i].m_iLocal = i;
10040 		dWorks[i].m_iMass = -m_dLocal[i].m_iMass; // minus for reverse order
10041 		dWorks[i].m_ppSorters = &dSorters [ i*iQueries ];
10042 		dWorks[i].m_ppResults = &dResultPtrs [ i*iQueries ];
10043 	}
10044 	dWorks.Sort ( bind ( &LocalSearch_t::m_iMass ) );
10045 
10046 	// setup threads
10047 	CSphVector<LocalSearchThreadContext_t> dThreads ( Min ( g_iDistThreads, dWorks.GetLength() ) );
10048 
10049 	// prepare for multithread extra schema processing
10050 	for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10051 		m_dExtraSchemas[iQuery].AwareMT();
10052 
10053 	CrashQuery_t tCrashQuery = SphCrashLogger_c::GetQuery(); // transfer query info for crash logger to new thread
10054 	// fire searcher threads
10055 
10056 	CSphAtomic<long> iaCursor;
10057 	ARRAY_FOREACH ( i, dThreads )
10058 	{
10059 		LocalSearchThreadContext_t & t = dThreads[i];
10060 		t.m_pHandler = this;
10061 		t.m_tCrashQuery = tCrashQuery;
10062 		t.m_pCurSearch = &iaCursor;
10063 		t.m_iSearches = dWorks.GetLength();
10064 		t.m_pSearches = dWorks.Begin();
10065 		SphCrashLogger_c::ThreadCreate ( &dThreads[i].m_tThd, LocalSearchThreadFunc, (void*)&dThreads[i] ); // FIXME! check result
10066 	}
10067 
10068 	// wait for them to complete
10069 	ARRAY_FOREACH ( i, dThreads )
10070 		sphThreadJoin ( &dThreads[i].m_tThd );
10071 
10072 	// now merge the results
10073 	ARRAY_FOREACH ( iLocal, dWorks )
10074 	{
10075 		bool bResult = dWorks[iLocal].m_bResult;
10076 		const char * sLocal = m_dLocal[iLocal].m_sName.cstr();
10077 		int iOrderTag = m_dLocal[iLocal].m_iOrderTag;
10078 
10079 		if ( !bResult )
10080 		{
10081 			// failed
10082 			for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10083 			{
10084 				int iResultIndex = iLocal*iQueries;
10085 				if ( !m_bMultiQueue )
10086 					iResultIndex += iQuery - m_iStart;
10087 				m_dFailuresSet[iQuery].Submit ( sLocal, dResults[iResultIndex].m_sError.cstr() );
10088 			}
10089 			continue;
10090 		}
10091 
10092 		// multi-query succeeded
10093 		for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10094 		{
10095 			// base result set index
10096 			// in multi-queue case, the only (!) result set actually filled with meta info
10097 			// in non-multi-queue case, just a first index, we fix it below
10098 			int iResultIndex = iLocal*iQueries;
10099 
10100 			// current sorter ALWAYS resides at this index, in all cases
10101 			// (current as in sorter for iQuery-th query against iLocal-th index)
10102 			int iSorterIndex = iLocal*iQueries + iQuery - m_iStart;
10103 
10104 			if ( !m_bMultiQueue )
10105 			{
10106 				// non-multi-queue case
10107 				// means that we have mere 1:1 mapping between results and sorters
10108 				// so let's adjust result set index
10109 				iResultIndex = iSorterIndex;
10110 
10111 			} else if ( dResults[iResultIndex].m_iMultiplier==-1 )
10112 			{
10113 				// multi-queue case
10114 				// need to additionally check per-query failures of MultiQueryEx
10115 				// those are reported through multiplier
10116 				// note that iSorterIndex just below is NOT a typo
10117 				// separate errors still go into separate result sets
10118 				// even though regular meta does not
10119 				m_dFailuresSet[iQuery].Submit ( sLocal, dResults[iSorterIndex].m_sError.cstr() );
10120 				continue;
10121 			}
10122 
10123 			// no sorter, no fun
10124 			ISphMatchSorter * pSorter = dSorters [ iSorterIndex ];
10125 			if ( !pSorter )
10126 				continue;
10127 
10128 			// this one seems OK
10129 			AggrResult_t & tRes = m_dResults[iQuery];
10130 			CSphQueryResult & tRaw = dResults[iResultIndex];
10131 
10132 			tRes.m_iSuccesses++;
10133 			tRes.m_iTotalMatches += pSorter->GetTotalCount();
10134 
10135 			tRes.m_pMva = tRaw.m_pMva;
10136 			tRes.m_pStrings = tRaw.m_pStrings;
10137 			tRes.m_bArenaProhibit = tRaw.m_bArenaProhibit;
10138 			MergeWordStats ( tRes, tRaw.m_hWordStats, &m_dFailuresSet[iQuery], sLocal );
10139 
10140 			// move external attributes storage from tRaw to actual result
10141 			tRaw.LeakStorages ( tRes );
10142 
10143 			tRes.m_bHasPrediction |= tRaw.m_bHasPrediction;
10144 			tRes.m_iMultiplier = m_bMultiQueue ? iQueries : 1;
10145 			tRes.m_iCpuTime += tRaw.m_iCpuTime / tRes.m_iMultiplier;
10146 			tRes.m_tIOStats.Add ( tRaw.m_tIOStats );
10147 			if ( tRaw.m_bHasPrediction )
10148 			{
10149 				tRes.m_tStats.Add ( tRaw.m_tStats );
10150 				tRes.m_iPredictedTime = CalcPredictedTimeMsec ( tRes );
10151 			}
10152 			if ( tRaw.m_iBadRows )
10153 				tRes.m_sWarning.SetSprintf ( "query result is inaccurate because of " INT64_FMT " missed documents", tRaw.m_iBadRows );
10154 
10155 			// extract matches from sorter
10156 			FlattenToRes ( pSorter, tRes, iOrderTag+iQuery-m_iStart );
10157 
10158 			if ( tRaw.m_iBadRows )
10159 				RemoveMissedRows ( tRes );
10160 
10161 			// take over the schema from sorter, it doesn't need it anymore
10162 			tRes.m_tSchema = pSorter->GetSchema(); // can SwapOut
10163 
10164 			if ( !tRaw.m_sWarning.IsEmpty() )
10165 				m_dFailuresSet[iQuery].Submit ( sLocal, tRaw.m_sWarning.cstr() );
10166 		}
10167 	}
10168 
10169 	ARRAY_FOREACH ( i, dSorters )
10170 		SafeDelete ( dSorters[i] );
10171 
10172 	// update our wall time for every result set
10173 	tmLocal = sphMicroTimer() - tmLocal;
10174 	for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10175 	{
10176 		m_dResults[iQuery].m_iQueryTime += (int)( tmLocal/1000 );
10177 	}
10178 }
10179 
10180 int64_t sphCpuTimer();
10181 
10182 // invoked from MT searches. So, must be MT-aware!
RunLocalSearch(int iLocal,ISphMatchSorter ** ppSorters,CSphQueryResult ** ppResults,bool * pMulti) const10183 bool SearchHandler_c::RunLocalSearch ( int iLocal, ISphMatchSorter ** ppSorters, CSphQueryResult ** ppResults, bool * pMulti ) const
10184 {
10185 	int64_t iCpuTime = -sphCpuTimer();
10186 
10187 	const int iQueries = m_iEnd-m_iStart+1;
10188 	const ServedIndex_t * pServed = UseIndex ( iLocal );
10189 	if ( !pServed || !pServed->m_bEnabled )
10190 	{
10191 		// FIXME! submit a failure?
10192 		if ( pServed )
10193 			ReleaseIndex ( iLocal );
10194 		return false;
10195 	}
10196 	assert ( pServed->m_pIndex );
10197 	assert ( pServed->m_bEnabled );
10198 	assert ( pMulti );
10199 
10200 	// create sorters
10201 	int iValidSorters = 0;
10202 	DWORD uFactorFlags = SPH_FACTOR_DISABLE;
10203 	for ( int i=0; i<iQueries; i++ )
10204 	{
10205 		CSphString & sError = ppResults[i]->m_sError;
10206 		const CSphQuery & tQuery = m_dQueries[i+m_iStart];
10207 		CSphSchemaMT * pExtraSchemaMT = tQuery.m_bAgent?m_dExtraSchemas[i+m_iStart].GetVirgin():NULL;
10208 		UnlockOnDestroy dSchemaLock ( pExtraSchemaMT );
10209 
10210 		m_tHook.m_pIndex = pServed->m_pIndex;
10211 		SphQueueSettings_t tQueueSettings ( tQuery, pServed->m_pIndex->GetMatchSchema(), sError, m_pProfile );
10212 		tQueueSettings.m_bComputeItems = true;
10213 		tQueueSettings.m_pExtra = pExtraSchemaMT;
10214 		tQueueSettings.m_pUpdate = m_pUpdates;
10215 		tQueueSettings.m_pDeletes = m_pDelete;
10216 		tQueueSettings.m_pHook = &m_tHook;
10217 
10218 		ppSorters[i] = sphCreateQueue ( tQueueSettings );
10219 
10220 		uFactorFlags |= tQueueSettings.m_uPackedFactorFlags;
10221 
10222 		if ( ppSorters[i] )
10223 			iValidSorters++;
10224 
10225 		// can't use multi-query for sorter with string attribute at group by or sort
10226 		if ( ppSorters[i] && *pMulti )
10227 			*pMulti = ppSorters[i]->CanMulti();
10228 	}
10229 	if ( !iValidSorters )
10230 	{
10231 		ReleaseIndex ( iLocal );
10232 		return false;
10233 	}
10234 
10235 	CSphVector<int> dLocked;
10236 
10237 	// setup kill-lists
10238 	KillListVector dKillist;
10239 	for ( int i=iLocal+1; i<m_dLocal.GetLength(); i++ )
10240 	{
10241 		const ServedIndex_t * pKillListIndex = UseIndex(i);
10242 		if ( !pKillListIndex )
10243 			continue;
10244 
10245 		if ( pKillListIndex->m_bEnabled && pKillListIndex->m_pIndex->GetKillListSize() )
10246 		{
10247 			KillListTrait_t & tElem = dKillist.Add ();
10248 			tElem.m_pBegin = pKillListIndex->m_pIndex->GetKillList();
10249 			tElem.m_iLen = pKillListIndex->m_pIndex->GetKillListSize();
10250 			dLocked.Add(i);
10251 		} else
10252 		{
10253 			ReleaseIndex(i);
10254 		}
10255 	}
10256 
10257 	int iIndexWeight = m_dLocal[iLocal].m_iWeight;
10258 
10259 	// do the query
10260 	CSphMultiQueryArgs tMultiArgs ( dKillist, iIndexWeight );
10261 	tMultiArgs.m_uPackedFactorFlags = uFactorFlags;
10262 	if ( m_bGotLocalDF )
10263 	{
10264 		tMultiArgs.m_bLocalDF = true;
10265 		tMultiArgs.m_pLocalDocs = &m_hLocalDocs;
10266 		tMultiArgs.m_iTotalDocs = m_iTotalDocs;
10267 	}
10268 
10269 	bool bResult = false;
10270 	ppResults[0]->m_tIOStats.Start();
10271 	if ( *pMulti )
10272 	{
10273 		bResult = pServed->m_pIndex->MultiQuery ( &m_dQueries[m_iStart], ppResults[0], iQueries, ppSorters, tMultiArgs );
10274 	} else
10275 	{
10276 		bResult = pServed->m_pIndex->MultiQueryEx ( iQueries, &m_dQueries[m_iStart], ppResults, ppSorters, tMultiArgs );
10277 	}
10278 	ppResults[0]->m_tIOStats.Stop();
10279 
10280 	iCpuTime += sphCpuTimer();
10281 	for ( int i=0; i<iQueries; ++i )
10282 		ppResults[i]->m_iCpuTime = iCpuTime;
10283 
10284 	ARRAY_FOREACH ( i, dLocked )
10285 		ReleaseIndex ( dLocked[i] );
10286 
10287 	return bResult;
10288 }
10289 
10290 
RunLocalSearches(ISphMatchSorter * pLocalSorter,const char * sDistName,DWORD uFactorFlags)10291 void SearchHandler_c::RunLocalSearches ( ISphMatchSorter * pLocalSorter, const char * sDistName, DWORD uFactorFlags )
10292 {
10293 	if ( g_iDistThreads>1 && m_dLocal.GetLength()>1 )
10294 	{
10295 		RunLocalSearchesMT();
10296 		return;
10297 	}
10298 
10299 	CSphVector <int> dLocked;
10300 	ARRAY_FOREACH ( iLocal, m_dLocal )
10301 	{
10302 		const char * sLocal = m_dLocal[iLocal].m_sName.cstr();
10303 		int iOrderTag = m_dLocal[iLocal].m_iOrderTag;
10304 		int iIndexWeight = m_dLocal[iLocal].m_iWeight;
10305 
10306 		const ServedIndex_t * pServed = UseIndex ( iLocal );
10307 		if ( !pServed || !pServed->m_bEnabled )
10308 		{
10309 			if ( sDistName )
10310 				for ( int i=m_iStart; i<=m_iEnd; i++ )
10311 					m_dFailuresSet[i].SubmitEx ( sDistName, "local index %s missing", sLocal );
10312 
10313 			if ( pServed )
10314 				ReleaseIndex ( iLocal );
10315 			continue;
10316 		}
10317 
10318 		assert ( pServed->m_pIndex );
10319 		assert ( pServed->m_bEnabled );
10320 
10321 		// create sorters
10322 		CSphVector<ISphMatchSorter*> dSorters ( m_iEnd-m_iStart+1 );
10323 		ARRAY_FOREACH ( i, dSorters )
10324 			dSorters[i] = NULL;
10325 
10326 		DWORD uTotalFactorFlags = uFactorFlags;
10327 		int iValidSorters = 0;
10328 		for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10329 		{
10330 			CSphString sError;
10331 			CSphQuery & tQuery = m_dQueries[iQuery];
10332 			CSphSchemaMT * pExtraSchema = tQuery.m_bAgent?m_dExtraSchemas[iQuery].GetVirgin():NULL;
10333 			UnlockOnDestroy dSchemaLock ( pExtraSchema );
10334 
10335 			// create sorter, if needed
10336 			ISphMatchSorter * pSorter = pLocalSorter;
10337 			if ( !pLocalSorter )
10338 			{
10339 				// create queue
10340 				m_tHook.m_pIndex = pServed->m_pIndex;
10341 				SphQueueSettings_t tQueueSettings ( tQuery, pServed->m_pIndex->GetMatchSchema(), sError, m_pProfile );
10342 				tQueueSettings.m_bComputeItems = true;
10343 				tQueueSettings.m_pExtra = pExtraSchema;
10344 				tQueueSettings.m_pUpdate = m_pUpdates;
10345 				tQueueSettings.m_pDeletes = m_pDelete;
10346 				tQueueSettings.m_pHook = &m_tHook;
10347 
10348 				pSorter = sphCreateQueue ( tQueueSettings );
10349 
10350 				uTotalFactorFlags |= tQueueSettings.m_uPackedFactorFlags;
10351 				tQuery.m_bZSlist = tQueueSettings.m_bZonespanlist;
10352 				if ( !pSorter )
10353 				{
10354 					m_dFailuresSet[iQuery].Submit ( sLocal, sError.cstr() );
10355 					continue;
10356 				}
10357 				if ( m_bMultiQueue )
10358 				{
10359 					// can't use multi-query for sorter with string attribute at group by or sort
10360 					m_bMultiQueue = pSorter->CanMulti();
10361 
10362 					if ( !m_bMultiQueue )
10363 						m_bFacetQueue = false;
10364 				}
10365 				if ( !sError.IsEmpty() )
10366 					m_dFailuresSet[iQuery].Submit ( sLocal, sError.cstr() );
10367 			}
10368 
10369 			dSorters[iQuery-m_iStart] = pSorter;
10370 			iValidSorters++;
10371 		}
10372 		if ( !iValidSorters )
10373 		{
10374 			ReleaseIndex ( iLocal );
10375 			continue;
10376 		}
10377 
10378 		// if sorter schemes have dynamic part, its lengths should be the same for queries to be optimized
10379 		const ISphMatchSorter * pLastMulti = dSorters[0];
10380 		for ( int i=1; i<dSorters.GetLength() && m_bMultiQueue; i++ )
10381 		{
10382 			if ( !dSorters[i] )
10383 				continue;
10384 
10385 			if ( !pLastMulti )
10386 			{
10387 				pLastMulti = dSorters[i];
10388 				continue;
10389 			}
10390 
10391 			assert ( pLastMulti && dSorters[i] );
10392 			m_bMultiQueue = pLastMulti->GetSchema().GetDynamicSize()==dSorters[i]->GetSchema().GetDynamicSize();
10393 		}
10394 
10395 		// facets, sanity check for json fields (can't be multi-queried yet)
10396 		for ( int i=1; i<dSorters.GetLength() && m_bFacetQueue; i++ )
10397 		{
10398 			if ( !dSorters[i] )
10399 				continue;
10400 			for ( int j=0; j<dSorters[i]->GetSchema().GetAttrsCount(); j++ )
10401 				if ( dSorters[i]->GetSchema().GetAttr(j).m_eAttrType==SPH_ATTR_JSON_FIELD )
10402 				{
10403 					m_bMultiQueue = m_bFacetQueue = false;
10404 					break;
10405 				}
10406 		}
10407 
10408 		if ( m_bFacetQueue )
10409 			m_bMultiQueue = true;
10410 
10411 		// me shortcuts
10412 		AggrResult_t tStats;
10413 
10414 		// set kill-list
10415 		KillListVector dKillist;
10416 		for ( int i=iLocal+1; i<m_dLocal.GetLength(); i++ )
10417 		{
10418 			const ServedIndex_t * pKillListIndex = UseIndex(i);
10419 			if ( !pKillListIndex )
10420 				continue;
10421 
10422 			if ( pKillListIndex->m_bEnabled && pKillListIndex->m_pIndex->GetKillListSize() )
10423 			{
10424 				KillListTrait_t & tElem = dKillist.Add ();
10425 				tElem.m_pBegin = pKillListIndex->m_pIndex->GetKillList();
10426 				tElem.m_iLen = pKillListIndex->m_pIndex->GetKillListSize();
10427 				dLocked.Add(i);
10428 			} else
10429 			{
10430 				ReleaseIndex(i);
10431 			}
10432 		}
10433 
10434 		// do the query
10435 		CSphMultiQueryArgs tMultiArgs ( dKillist, iIndexWeight );
10436 		tMultiArgs.m_uPackedFactorFlags = uTotalFactorFlags;
10437 		if ( m_bGotLocalDF )
10438 		{
10439 			tMultiArgs.m_bLocalDF = true;
10440 			tMultiArgs.m_pLocalDocs = &m_hLocalDocs;
10441 			tMultiArgs.m_iTotalDocs = m_iTotalDocs;
10442 		}
10443 
10444 		bool bResult = false;
10445 		if ( m_bMultiQueue )
10446 		{
10447 			tStats.m_tIOStats.Start();
10448 			bResult = pServed->m_pIndex->MultiQuery ( &m_dQueries[m_iStart], &tStats, dSorters.GetLength(), dSorters.Begin(), tMultiArgs );
10449 			tStats.m_tIOStats.Stop();
10450 		} else
10451 		{
10452 			CSphVector<CSphQueryResult*> dResults ( m_dResults.GetLength() );
10453 			ARRAY_FOREACH ( i, m_dResults )
10454 			{
10455 				dResults[i] = &m_dResults[i];
10456 				dResults[i]->m_pMva = NULL;
10457 				dResults[i]->m_pStrings = NULL;
10458 			}
10459 
10460 			dResults[m_iStart]->m_tIOStats.Start();
10461 			bResult = pServed->m_pIndex->MultiQueryEx ( dSorters.GetLength(), &m_dQueries[m_iStart], &dResults[m_iStart], &dSorters[0], tMultiArgs );
10462 			dResults[m_iStart]->m_tIOStats.Stop();
10463 		}
10464 
10465 		// handle results
10466 		if ( !bResult )
10467 		{
10468 			// failed, submit local (if not empty) or global error string
10469 			for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10470 				m_dFailuresSet[iQuery].Submit ( sLocal, tStats.m_sError.IsEmpty()
10471 					? m_dResults [ m_bMultiQueue ? m_iStart : iQuery ].m_sError.cstr()
10472 					: tStats.m_sError.cstr() );
10473 		} else
10474 		{
10475 			// multi-query succeeded
10476 			for ( int iQuery=m_iStart; iQuery<=m_iEnd; iQuery++ )
10477 			{
10478 				// but some of the sorters could had failed at "create sorter" stage
10479 				ISphMatchSorter * pSorter = dSorters [ iQuery-m_iStart ];
10480 				if ( !pSorter )
10481 					continue;
10482 
10483 				// this one seems OK
10484 				AggrResult_t & tRes = m_dResults[iQuery];
10485 
10486 				int64_t iBadRows = m_bMultiQueue ? tStats.m_iBadRows : tRes.m_iBadRows;
10487 				if ( iBadRows )
10488 					tRes.m_sWarning.SetSprintf ( "query result is inaccurate because of " INT64_FMT " missed documents", iBadRows );
10489 
10490 				// multi-queue only returned one result set meta, so we need to replicate it
10491 				if ( m_bMultiQueue )
10492 				{
10493 					// these times will be overridden below, but let's be clean
10494 					tRes.m_iQueryTime += tStats.m_iQueryTime / ( m_iEnd-m_iStart+1 );
10495 					tRes.m_iCpuTime += tStats.m_iCpuTime / ( m_iEnd-m_iStart+1 );
10496 					tRes.m_tIOStats.Add ( tStats.m_tIOStats );
10497 					tRes.m_pMva = tStats.m_pMva;
10498 					tRes.m_pStrings = tStats.m_pStrings;
10499 					tRes.m_bArenaProhibit = tStats.m_bArenaProhibit;
10500 					MergeWordStats ( tRes, tStats.m_hWordStats, &m_dFailuresSet[iQuery], sLocal );
10501 					tRes.m_iMultiplier = m_iEnd-m_iStart+1;
10502 				} else if ( tRes.m_iMultiplier==-1 )
10503 				{
10504 					m_dFailuresSet[iQuery].Submit ( sLocal, tRes.m_sError.cstr() );
10505 					continue;
10506 				}
10507 
10508 				tRes.m_iSuccesses++;
10509 				// lets do this schema copy just once
10510 				tRes.m_tSchema = pSorter->GetSchema();
10511 				tRes.m_iTotalMatches += pSorter->GetTotalCount();
10512 				tRes.m_iPredictedTime = tRes.m_bHasPrediction ? CalcPredictedTimeMsec ( tRes ) : 0;
10513 
10514 				// extract matches from sorter
10515 				FlattenToRes ( pSorter, tRes, iOrderTag+iQuery-m_iStart );
10516 
10517 				if ( iBadRows )
10518 					RemoveMissedRows ( tRes );
10519 
10520 				// move external attributes storage from tStats to actual result
10521 				tStats.LeakStorages ( tRes );
10522 			}
10523 		}
10524 
10525 		ARRAY_FOREACH ( i, dLocked )
10526 			ReleaseIndex ( dLocked[i] );
10527 
10528 		dLocked.Resize ( 0 );
10529 
10530 		// cleanup sorters
10531 		if ( !pLocalSorter )
10532 			ARRAY_FOREACH ( i, dSorters )
10533 				SafeDelete ( dSorters[i] );
10534 	}
10535 }
10536 
10537 
10538 // check expressions into a query to make sure that it's ready for multi query optimization
AllowsMulti(int iStart,int iEnd) const10539 bool SearchHandler_c::AllowsMulti ( int iStart, int iEnd ) const
10540 {
10541 	// in some cases the same select list allows queries to be multi query optimized
10542 	// but we need to check dynamic parts size equality and we do it later in RunLocalSearches()
10543 	const CSphVector<CSphQueryItem> & tFirstQueryItems = m_dQueries [ iStart ].m_dItems;
10544 	bool bItemsSameLen = true;
10545 	for ( int i=iStart+1; i<=iEnd && bItemsSameLen; i++ )
10546 		bItemsSameLen = ( tFirstQueryItems.GetLength()==m_dQueries[i].m_dItems.GetLength() );
10547 	if ( bItemsSameLen )
10548 	{
10549 		bool bSameItems = true;
10550 		ARRAY_FOREACH_COND ( i, tFirstQueryItems, bSameItems )
10551 		{
10552 			const CSphQueryItem & tItem1 = tFirstQueryItems[i];
10553 			for ( int j=iStart+1; j<=iEnd && bSameItems; j++ )
10554 			{
10555 				const CSphQueryItem & tItem2 = m_dQueries[j].m_dItems[i];
10556 				bSameItems = tItem1.m_sExpr==tItem2.m_sExpr && tItem1.m_eAggrFunc==tItem2.m_eAggrFunc;
10557 			}
10558 		}
10559 
10560 		if ( bSameItems )
10561 			return true;
10562 	}
10563 
10564 	// if select lists do not contain any expressions we can optimize queries too
10565 	ARRAY_FOREACH ( i, m_dLocal )
10566 	{
10567 		const ServedIndex_t * pServedIndex = UseIndex ( i );
10568 
10569 		// check that it exists
10570 		if ( !pServedIndex || !pServedIndex->m_bEnabled )
10571 		{
10572 			if ( pServedIndex )
10573 				ReleaseIndex ( i );
10574 			continue;
10575 		}
10576 
10577 		bool bHasExpression = false;
10578 		const CSphSchema & tSchema = pServedIndex->m_pIndex->GetMatchSchema();
10579 		for ( int iCheck=iStart; iCheck<=iEnd && !bHasExpression; iCheck++ )
10580 			bHasExpression = sphHasExpressions ( m_dQueries[iCheck], tSchema );
10581 
10582 		ReleaseIndex ( i );
10583 
10584 		if ( bHasExpression )
10585 			return false;
10586 	}
10587 	return true;
10588 }
10589 
10590 
10591 struct IndexSettings_t
10592 {
10593 	uint64_t	m_uHash;
10594 	int			m_iLocal;
10595 };
10596 
SetupLocalDF(int iStart,int iEnd)10597 void SearchHandler_c::SetupLocalDF ( int iStart, int iEnd )
10598 {
10599 	if ( m_dLocal.GetLength()<2 )
10600 		return;
10601 
10602 	if ( m_pProfile )
10603 		m_pProfile->Switch ( SPH_QSTATE_LOCAL_DF );
10604 
10605 	bool bGlobalIDF = true;
10606 	ARRAY_FOREACH_COND ( i, m_dLocal, bGlobalIDF )
10607 	{
10608 		const ServedIndex_t * pIndex = UseIndex ( i );
10609 		if ( pIndex && pIndex->m_bEnabled )
10610 			bGlobalIDF = !pIndex->m_sGlobalIDFPath.IsEmpty();
10611 
10612 		if ( pIndex )
10613 			ReleaseIndex ( i );
10614 	}
10615 	// bail out on all indexes with global idf set
10616 	if ( bGlobalIDF )
10617 		return;
10618 
10619 	bool bOnlyNoneRanker = true;
10620 	bool bOnlyFullScan = true;
10621 	bool bHasLocalDF = false;
10622 	for ( int iQuery=iStart; iQuery<=iEnd; iQuery++ )
10623 	{
10624 		const CSphQuery & tQuery = m_dQueries[iQuery];
10625 
10626 		bOnlyFullScan &= tQuery.m_sQuery.IsEmpty();
10627 		bHasLocalDF |= tQuery.m_bLocalDF;
10628 		if ( !tQuery.m_sQuery.IsEmpty() && tQuery.m_bLocalDF )
10629 			bOnlyNoneRanker &= ( tQuery.m_eRanker==SPH_RANK_NONE );
10630 	}
10631 	// bail out queries: full-scan, ranker=none, local_idf=0
10632 	if ( bOnlyFullScan || bOnlyNoneRanker || !bHasLocalDF )
10633 		return;
10634 
10635 	CSphVector<char> dQuery ( 512 );
10636 	dQuery.Resize ( 0 );
10637 	for ( int iQuery=iStart; iQuery<=iEnd; iQuery++ )
10638 	{
10639 		const CSphQuery & tQuery = m_dQueries[iQuery];
10640 		if ( tQuery.m_sQuery.IsEmpty() || !tQuery.m_bLocalDF || tQuery.m_eRanker==SPH_RANK_NONE )
10641 			continue;
10642 
10643 		int iLen = tQuery.m_sQuery.Length();
10644 		int iOff = dQuery.GetLength();
10645 		dQuery.Resize ( iOff + iLen + 1 );
10646 		memcpy ( dQuery.Begin()+iOff, tQuery.m_sQuery.cstr(), iLen );
10647 		dQuery[iOff+iLen] = ' '; // queries delimiter
10648 	}
10649 	// bail out on empty queries
10650 	if ( !dQuery.GetLength() )
10651 		return;
10652 
10653 	dQuery.Add ( '\0' );
10654 
10655 	// order indexes by settings
10656 	CSphVector<IndexSettings_t> dLocal ( m_dLocal.GetLength() );
10657 	dLocal.Resize ( 0 );
10658 	ARRAY_FOREACH ( i, m_dLocal )
10659 	{
10660 		const ServedIndex_t * pIndex = UseIndex ( i );
10661 		if ( !pIndex || !pIndex->m_bEnabled )
10662 		{
10663 			if ( pIndex )
10664 				ReleaseIndex ( i );
10665 			continue;
10666 		}
10667 
10668 		dLocal.Add();
10669 		dLocal.Last().m_iLocal = i;
10670 		// TODO: cache settingsFNV on index load
10671 		// FIXME!!! no need to count dictionary hash
10672 		dLocal.Last().m_uHash = pIndex->m_pIndex->GetTokenizer()->GetSettingsFNV() ^ pIndex->m_pIndex->GetDictionary()->GetSettingsFNV();
10673 
10674 		ReleaseIndex ( i );
10675 	}
10676 	dLocal.Sort ( bind ( &IndexSettings_t::m_uHash ) );
10677 
10678 	// gather per-term docs count
10679 	CSphVector < CSphKeywordInfo > dKeywords;
10680 	ARRAY_FOREACH ( i, dLocal )
10681 	{
10682 		int iLocalIndex = dLocal[i].m_iLocal;
10683 		const ServedIndex_t * pIndex = UseIndex ( iLocalIndex );
10684 		if ( !pIndex || !pIndex->m_bEnabled )
10685 		{
10686 			if ( pIndex )
10687 				ReleaseIndex ( iLocalIndex );
10688 			continue;
10689 		}
10690 
10691 		m_iTotalDocs += pIndex->m_pIndex->GetStats().m_iTotalDocuments;
10692 
10693 		if ( i && dLocal[i].m_uHash==dLocal[i-1].m_uHash )
10694 		{
10695 			ARRAY_FOREACH ( kw, dKeywords )
10696 				dKeywords[kw].m_iDocs = 0;
10697 
10698 			// no need to tokenize query just fill docs count
10699 			pIndex->m_pIndex->FillKeywords ( dKeywords );
10700 		} else
10701 		{
10702 			dKeywords.Resize ( 0 );
10703 			pIndex->m_pIndex->GetKeywords ( dKeywords, dQuery.Begin(), true, NULL );
10704 
10705 			// FIXME!!! move duplicate removal to GetKeywords to do less QWord setup and dict searching
10706 			// custom uniq - got rid of word duplicates
10707 			dKeywords.Sort ( bind ( &CSphKeywordInfo::m_sNormalized ) );
10708 			if ( dKeywords.GetLength()>1 )
10709 			{
10710 				int iSrc = 1, iDst = 1;
10711 				while ( iSrc<dKeywords.GetLength() )
10712 				{
10713 					if ( dKeywords[iDst-1].m_sNormalized==dKeywords[iSrc].m_sNormalized )
10714 						iSrc++;
10715 					else
10716 					{
10717 						Swap ( dKeywords[iDst], dKeywords[iSrc] );
10718 						iDst++;
10719 						iSrc++;
10720 					}
10721 				}
10722 				dKeywords.Resize ( iDst );
10723 			}
10724 		}
10725 
10726 		ARRAY_FOREACH ( j, dKeywords )
10727 		{
10728 			const CSphKeywordInfo & tKw = dKeywords[j];
10729 			int64_t * pDocs = m_hLocalDocs ( tKw.m_sNormalized );
10730 			if ( pDocs )
10731 				*pDocs += tKw.m_iDocs;
10732 			else
10733 				m_hLocalDocs.Add ( tKw.m_iDocs, tKw.m_sNormalized );
10734 		}
10735 
10736 		ReleaseIndex ( dLocal[i].m_iLocal );
10737 	}
10738 
10739 	m_bGotLocalDF = true;
10740 }
10741 
10742 
GetIndexWeight(const char * sName,const CSphVector<CSphNamedInt> & dIndexWeights,int iDefaultWeight)10743 static int GetIndexWeight ( const char * sName, const CSphVector<CSphNamedInt> & dIndexWeights, int iDefaultWeight )
10744 {
10745 	ARRAY_FOREACH ( i, dIndexWeights )
10746 		if ( dIndexWeights[i].m_sName==sName )
10747 			return dIndexWeights[i].m_iValue;
10748 
10749 	// distributed index adds {'*', weight} to all agents in case it got custom weight
10750 	if ( dIndexWeights.GetLength() && dIndexWeights.Last().m_sName=="*" )
10751 		return dIndexWeights.Begin()->m_iValue;
10752 
10753 	return iDefaultWeight;
10754 }
10755 
CalculateMass(const CSphIndexStatus & dStats)10756 static uint64_t CalculateMass ( const CSphIndexStatus & dStats )
10757 {
10758 	return dStats.m_iNumChunks * 1000000 + dStats.m_iRamUse + dStats.m_iDiskUse * 10;
10759 }
10760 
GetIndexMass(const char * sName)10761 static uint64_t GetIndexMass ( const char * sName )
10762 {
10763 	ServedIndex_t * pIdx = g_pLocalIndexes->GetUnlockedEntryP ( sName );
10764 	if ( pIdx )
10765 		return pIdx->m_iMass;
10766 	return 0;
10767 }
10768 
10769 struct TaggedLocalSorter_fn
10770 {
IsLessTaggedLocalSorter_fn10771 	bool IsLess ( const LocalIndex_t & a, const LocalIndex_t & b ) const
10772 	{
10773 		return ( a.m_sName < b.m_sName ) || ( a.m_sName==b.m_sName && ( a.m_iOrderTag & 0x7FFFFFFF )>( b.m_iOrderTag & 0x7FFFFFFF ) );
10774 	}
10775 };
10776 
RunSubset(int iStart,int iEnd)10777 void SearchHandler_c::RunSubset ( int iStart, int iEnd )
10778 {
10779 	m_iStart = iStart;
10780 	m_iEnd = iEnd;
10781 	m_dLocal.Reset();
10782 
10783 	// all my stats
10784 	int64_t tmSubset = sphMicroTimer();
10785 	int64_t tmLocal = 0;
10786 	int64_t tmCpu = sphCpuTimer ();
10787 
10788 	// prepare for descent
10789 	CSphQuery & tFirst = m_dQueries[iStart];
10790 
10791 	for ( int iRes=iStart; iRes<=iEnd; iRes++ )
10792 		m_dResults[iRes].m_iSuccesses = 0;
10793 
10794 	if ( iStart==iEnd && m_pProfile )
10795 	{
10796 		m_dResults[iStart].m_pProfile = m_pProfile;
10797 		m_tHook.m_pProfiler = m_pProfile;
10798 	}
10799 
10800 	////////////////////////////////////////////////////////////////
10801 	// check for single-query, multi-queue optimization possibility
10802 	////////////////////////////////////////////////////////////////
10803 
10804 	m_bMultiQueue = ( iStart<iEnd );
10805 	for ( int iCheck=iStart+1; iCheck<=iEnd && m_bMultiQueue; iCheck++ )
10806 	{
10807 		const CSphQuery & qFirst = m_dQueries[iStart];
10808 		const CSphQuery & qCheck = m_dQueries[iCheck];
10809 
10810 		// these parameters must be the same
10811 		if (
10812 			( qCheck.m_sRawQuery!=qFirst.m_sRawQuery ) || // query string
10813 			( qCheck.m_iWeights!=qFirst.m_iWeights ) || // weights count
10814 			( qCheck.m_pWeights && memcmp ( qCheck.m_pWeights, qFirst.m_pWeights, sizeof(int)*qCheck.m_iWeights ) ) || // weights; NOLINT
10815 			( qCheck.m_eMode!=qFirst.m_eMode ) || // search mode
10816 			( qCheck.m_eRanker!=qFirst.m_eRanker ) || // ranking mode
10817 			( qCheck.m_dFilters.GetLength()!=qFirst.m_dFilters.GetLength() ) || // attr filters count
10818 			( qCheck.m_iCutoff!=qFirst.m_iCutoff ) || // cutoff
10819 			( qCheck.m_eSort==SPH_SORT_EXPR && qFirst.m_eSort==SPH_SORT_EXPR && qCheck.m_sSortBy!=qFirst.m_sSortBy ) || // sort expressions
10820 			( qCheck.m_bGeoAnchor!=qFirst.m_bGeoAnchor ) || // geodist expression
10821 			( qCheck.m_bGeoAnchor && qFirst.m_bGeoAnchor
10822 				&& ( qCheck.m_fGeoLatitude!=qFirst.m_fGeoLatitude || qCheck.m_fGeoLongitude!=qFirst.m_fGeoLongitude ) ) ) // some geodist cases
10823 		{
10824 			m_bMultiQueue = false;
10825 			break;
10826 		}
10827 
10828 		// filters must be the same too
10829 		assert ( qCheck.m_dFilters.GetLength()==qFirst.m_dFilters.GetLength() );
10830 		ARRAY_FOREACH ( i, qCheck.m_dFilters )
10831 			if ( qCheck.m_dFilters[i]!=qFirst.m_dFilters[i] )
10832 			{
10833 				m_bMultiQueue = false;
10834 				break;
10835 			}
10836 	}
10837 
10838 	// check for facets
10839 	m_bFacetQueue = iEnd>iStart;
10840 	for ( int iCheck=iStart+1; iCheck<=iEnd && m_bFacetQueue; iCheck++ )
10841 		if ( !m_dQueries[iCheck].m_bFacet )
10842 			m_bFacetQueue = false;
10843 
10844 	if ( m_bFacetQueue )
10845 		m_bMultiQueue = true;
10846 
10847 	////////////////////////////
10848 	// build local indexes list
10849 	////////////////////////////
10850 
10851 	CSphVector<AgentConn_t> dAgents;
10852 	int iDivideLimits = 1;
10853 	int iAgentConnectTimeout = 0, iAgentQueryTimeout = 0;
10854 	int iTagsCount = 0;
10855 	int iTagStep = iEnd - iStart + 1;
10856 
10857 	// they're all local, build the list
10858 	if ( tFirst.m_sIndexes=="*" )
10859 	{
10860 		// search through all local indexes
10861 		for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
10862 			if ( it.Get ().m_bEnabled )
10863 			{
10864 				m_dLocal.Add().m_sName = it.GetKey();
10865 				m_dLocal.Last().m_iOrderTag = iTagsCount;
10866 				m_dLocal.Last().m_iWeight = GetIndexWeight ( it.GetKey().cstr(), tFirst.m_dIndexWeights, 1 );
10867 				m_dLocal.Last().m_iMass = it.Get().m_iMass;
10868 				iTagsCount += iTagStep;
10869 			}
10870 	} else
10871 	{
10872 		CSphVector<CSphString> dLocal;
10873 		// search through specified local indexes
10874 		ParseIndexList ( tFirst.m_sIndexes, dLocal );
10875 
10876 		int iDistCount = 0;
10877 		bool bDevideRemote = false;
10878 
10879 		g_tDistLock.Lock();
10880 		ARRAY_FOREACH ( i, dLocal )
10881 		{
10882 			const CSphString & sIndex = dLocal[i];
10883 			DistributedIndex_t * pDist = g_hDistIndexes ( sIndex );
10884 			if ( !pDist )
10885 			{
10886 				m_dLocal.Add().m_sName = dLocal[i];
10887 				m_dLocal.Last().m_iOrderTag = iTagsCount;
10888 				m_dLocal.Last().m_iWeight = GetIndexWeight ( sIndex.cstr(), tFirst.m_dIndexWeights, 1 );
10889 				m_dLocal.Last().m_iMass = GetIndexMass ( sIndex.cstr() );
10890 				iTagsCount += iTagStep;
10891 
10892 			} else
10893 			{
10894 				iDistCount++;
10895 				iAgentConnectTimeout = iAgentConnectTimeout ? Min ( pDist->m_iAgentConnectTimeout, iAgentConnectTimeout ) : pDist->m_iAgentConnectTimeout;
10896 				iAgentQueryTimeout = iAgentQueryTimeout ? Min ( pDist->m_iAgentQueryTimeout, iAgentQueryTimeout ) : pDist->m_iAgentQueryTimeout;
10897 				int iWeight = GetIndexWeight ( sIndex.cstr(), tFirst.m_dIndexWeights, -1 );
10898 
10899 				dAgents.Reserve ( dAgents.GetLength() + pDist->m_dAgents.GetLength() );
10900 				ARRAY_FOREACH ( j, pDist->m_dAgents )
10901 				{
10902 					dAgents.Add().TakeTraits ( *pDist->m_dAgents[j].GetRRAgent() );
10903 					dAgents.Last().m_iStoreTag = iTagsCount;
10904 					dAgents.Last().m_iWeight = iWeight;
10905 					iTagsCount += iTagStep;
10906 				}
10907 
10908 				m_dLocal.Reserve ( m_dLocal.GetLength() + pDist->m_dLocal.GetLength() );
10909 				ARRAY_FOREACH ( j, pDist->m_dLocal )
10910 				{
10911 					m_dLocal.Add().m_sName = pDist->m_dLocal[j];
10912 					m_dLocal.Last().m_iOrderTag = iTagsCount;
10913 					if ( iWeight!=-1 )
10914 						m_dLocal.Last().m_iWeight = iWeight;
10915 					iTagsCount += iTagStep;
10916 					m_dLocal.Last().m_iMass = GetIndexMass ( pDist->m_dLocal[j].cstr() );
10917 				}
10918 
10919 				bDevideRemote |= pDist->m_bDivideRemoteRanges;
10920 			}
10921 		}
10922 		g_tDistLock.Unlock();
10923 
10924 		// set remote devider
10925 		if ( bDevideRemote )
10926 		{
10927 			if ( iDistCount==1 )
10928 				iDivideLimits = dAgents.GetLength();
10929 			else
10930 			{
10931 				for ( int iRes=iStart; iRes<=iEnd; iRes++ )
10932 					m_dResults[iRes].m_sWarning.SetSprintf ( "distribute multi-index query '%s' doesn't support divide_remote_ranges", tFirst.m_sIndexes.cstr() );
10933 			}
10934 		}
10935 
10936 		// eliminate local dupes that come from distributed indexes
10937 		if ( m_dLocal.GetLength() && dAgents.GetLength() )
10938 		{
10939 			m_dLocal.Sort ( TaggedLocalSorter_fn() );
10940 			int iSrc = 1, iDst = 1;
10941 			while ( iSrc<m_dLocal.GetLength() )
10942 			{
10943 				if ( m_dLocal[iDst-1].m_sName==m_dLocal[iSrc].m_sName )
10944 					iSrc++;
10945 				else
10946 					m_dLocal[iDst++] = m_dLocal[iSrc++];
10947 			}
10948 			m_dLocal.Resize ( iDst );
10949 			m_dLocal.Sort ( bind ( &LocalIndex_t::m_iOrderTag ) ); // keep initial order of locals
10950 		}
10951 
10952 		ARRAY_FOREACH ( i, m_dLocal )
10953 		{
10954 			const ServedIndex_t * pServedIndex = UseIndex ( i );
10955 			bool bEnabled = pServedIndex && pServedIndex->m_bEnabled;
10956 			if ( pServedIndex )
10957 				ReleaseIndex ( i );
10958 
10959 			// check that it exists
10960 			if ( !pServedIndex )
10961 			{
10962 				if ( tFirst.m_bIgnoreNonexistentIndexes )
10963 				{
10964 					m_dLocal.Remove ( i-- );
10965 					continue;
10966 				}
10967 				for ( int iRes=iStart; iRes<=iEnd; iRes++ )
10968 					m_dResults[iRes].m_sError.SetSprintf ( "unknown local index '%s' in search request", m_dLocal[i].m_sName.cstr() );
10969 				return;
10970 			}
10971 
10972 			// if it exists but is not enabled, remove it from the list and force recheck
10973 			if ( !bEnabled )
10974 				m_dLocal.Remove ( i-- );
10975 		}
10976 	}
10977 
10978 	// sanity check
10979 	if ( !dAgents.GetLength() && !m_dLocal.GetLength() )
10980 	{
10981 		const char * sIndexType = ( dAgents.GetLength() ? "" : "local" );
10982 		for ( int iRes=iStart; iRes<=iEnd; iRes++ )
10983 			m_dResults[iRes].m_sError.SetSprintf ( "no enabled %s indexes to search", sIndexType );
10984 		return;
10985 	}
10986 	ARRAY_FOREACH ( i, m_dResults )
10987 		m_dResults[i].m_dTag2Pools.Resize ( iTagsCount );
10988 
10989 	/////////////////////////////////////////////////////
10990 	// optimize single-query, same-schema local searches
10991 	/////////////////////////////////////////////////////
10992 
10993 	DWORD uLocalPFFlags = SPH_FACTOR_DISABLE;
10994 	ISphMatchSorter * pLocalSorter = NULL;
10995 	while ( iStart==iEnd && m_dLocal.GetLength()>1 )
10996 	{
10997 		CSphString sError;
10998 
10999 		// check if all schemes are equal
11000 		bool bAllEqual = true;
11001 
11002 		const ServedIndex_t * pFirstIndex = UseIndex ( 0 );
11003 		if ( !pFirstIndex || !pFirstIndex->m_bEnabled )
11004 		{
11005 			if ( pFirstIndex )
11006 				ReleaseIndex ( 0 );
11007 			break;
11008 		}
11009 
11010 		const CSphSchema & tFirstSchema = pFirstIndex->m_pIndex->GetMatchSchema();
11011 		for ( int i=1; i<m_dLocal.GetLength() && bAllEqual; i++ )
11012 		{
11013 			const ServedIndex_t * pNextIndex = UseIndex ( i );
11014 			if ( !pNextIndex || !pNextIndex->m_bEnabled )
11015 			{
11016 				bAllEqual = false;
11017 				if ( pNextIndex )
11018 					ReleaseIndex ( i );
11019 				break;
11020 			}
11021 
11022 			if ( !tFirstSchema.CompareTo ( pNextIndex->m_pIndex->GetMatchSchema(), sError ) )
11023 				bAllEqual = false;
11024 
11025 			ReleaseIndex ( i );
11026 		}
11027 
11028 		// we can reuse the very same sorter
11029 		if ( bAllEqual )
11030 		{
11031 			CSphSchemaMT * pExtraSchemaMT = m_dQueries[iStart].m_bAgent?m_dExtraSchemas[iStart].GetVirgin():NULL;
11032 			UnlockOnDestroy ExtraLocker ( pExtraSchemaMT );
11033 
11034 			m_tHook.m_pIndex = pFirstIndex->m_pIndex;
11035 			SphQueueSettings_t tQueueSettings ( m_dQueries[iStart], tFirstSchema, sError, m_pProfile );
11036 			tQueueSettings.m_bComputeItems = true;
11037 			tQueueSettings.m_pExtra = pExtraSchemaMT;
11038 			tQueueSettings.m_pHook = &m_tHook;
11039 
11040 			pLocalSorter = sphCreateQueue ( tQueueSettings );
11041 
11042 			uLocalPFFlags = tQueueSettings.m_uPackedFactorFlags;
11043 		}
11044 
11045 		ReleaseIndex ( 0 );
11046 		break;
11047 	}
11048 
11049 	// select lists must have no expressions
11050 	if ( m_bMultiQueue )
11051 		m_bMultiQueue = AllowsMulti ( iStart, iEnd );
11052 
11053 	if ( !m_bMultiQueue )
11054 		m_bFacetQueue = false;
11055 
11056 	// these are mutual exclusive
11057 	assert ( !( m_bMultiQueue && pLocalSorter ) );
11058 
11059 	///////////////////////////////////////////////////////////
11060 	// main query loop (with multiple retries for distributed)
11061 	///////////////////////////////////////////////////////////
11062 
11063 	// connect to remote agents and query them, if required
11064 	CSphScopedPtr<SearchRequestBuilder_t> tReqBuilder ( NULL );
11065 	CSphScopedPtr<CSphRemoteAgentsController> tDistCtrl ( NULL );
11066 	if ( dAgents.GetLength() )
11067 	{
11068 		if ( m_pProfile )
11069 			m_pProfile->Switch ( SPH_QSTATE_DIST_CONNECT );
11070 
11071 		int iRetryCount = Min ( Max ( tFirst.m_iRetryCount, 0 ), MAX_RETRY_COUNT ); // paranoid clamp
11072 
11073 		tReqBuilder = new SearchRequestBuilder_t ( m_dQueries, iStart, iEnd, iDivideLimits );
11074 		tDistCtrl = new CSphRemoteAgentsController ( g_iDistThreads, dAgents,
11075 			*tReqBuilder.Ptr(), iAgentConnectTimeout, iRetryCount, tFirst.m_iRetryDelay );
11076 	}
11077 
11078 	/////////////////////
11079 	// run local queries
11080 	//////////////////////
11081 
11082 	// while the remote queries are running, do local searches
11083 	// FIXME! what if the remote agents finish early, could they timeout?
11084 	if ( m_dLocal.GetLength() )
11085 	{
11086 		SetupLocalDF ( iStart, iEnd );
11087 
11088 		if ( m_pProfile )
11089 			m_pProfile->Switch ( SPH_QSTATE_LOCAL_SEARCH );
11090 
11091 		tmLocal = -sphMicroTimer();
11092 		RunLocalSearches ( pLocalSorter, dAgents.GetLength() ? tFirst.m_sIndexes.cstr() : NULL, uLocalPFFlags );
11093 		tmLocal += sphMicroTimer();
11094 	}
11095 
11096 	///////////////////////
11097 	// poll remote queries
11098 	///////////////////////
11099 
11100 	bool bDistDone = false;
11101 	if ( dAgents.GetLength() )
11102 	{
11103 		if ( m_pProfile )
11104 			m_pProfile->Switch ( SPH_QSTATE_DIST_WAIT );
11105 
11106 		while ( !bDistDone )
11107 		{
11108 			// don't forget to check incoming replies after send was over
11109 			tDistCtrl->WaitAgentsEvent();
11110 			bDistDone = tDistCtrl->IsDone();
11111 			// wait for remote queries to complete
11112 			if ( tDistCtrl->HasReadyAgents() )
11113 			{
11114 				CSphVector<DWORD> dMvaStorage;
11115 				CSphVector<BYTE> dStringStorage;
11116 				dMvaStorage.Add ( 0 );
11117 				dStringStorage.Add ( 0 );
11118 				SearchReplyParser_t tParser ( iStart, iEnd, dMvaStorage, dStringStorage );
11119 				int iMsecLeft = iAgentQueryTimeout - (int)( tmLocal/1000 );
11120 				int iReplys = RemoteWaitForAgents ( dAgents, Max ( iMsecLeft, 0 ), tParser );
11121 				// check if there were valid (though might be 0-matches) replies, and merge them
11122 				if ( iReplys )
11123 				{
11124 					DWORD * pMva = dMvaStorage.Begin();
11125 					BYTE * pString = dStringStorage.Begin();
11126 					ARRAY_FOREACH ( iAgent, dAgents )
11127 					{
11128 						AgentConn_t & tAgent = dAgents[iAgent];
11129 						if ( !tAgent.m_bSuccess )
11130 							continue;
11131 
11132 						int iOrderTag = tAgent.m_iStoreTag;
11133 						// merge this agent's results
11134 						for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11135 						{
11136 							const CSphQueryResult & tRemoteResult = tAgent.m_dResults[iRes-iStart];
11137 
11138 							// copy errors or warnings
11139 							if ( !tRemoteResult.m_sError.IsEmpty() )
11140 								m_dFailuresSet[iRes].SubmitEx ( tFirst.m_sIndexes.cstr(),
11141 									"agent %s: remote query error: %s",
11142 									tAgent.GetName().cstr(), tRemoteResult.m_sError.cstr() );
11143 							if ( !tRemoteResult.m_sWarning.IsEmpty() )
11144 								m_dFailuresSet[iRes].SubmitEx ( tFirst.m_sIndexes.cstr(),
11145 									"agent %s: remote query warning: %s",
11146 									tAgent.GetName().cstr(), tRemoteResult.m_sWarning.cstr() );
11147 
11148 							if ( tRemoteResult.m_iSuccesses<=0 )
11149 								continue;
11150 
11151 							AggrResult_t & tRes = m_dResults[iRes];
11152 							tRes.m_iSuccesses++;
11153 							tRes.m_tSchema = tRemoteResult.m_tSchema;
11154 
11155 							assert ( !tRes.m_dTag2Pools[iOrderTag + iRes - iStart].m_pMva && !tRes.m_dTag2Pools[iOrderTag + iRes - iStart].m_pStrings );
11156 
11157 							tRes.m_dMatches.Reserve ( tRemoteResult.m_dMatches.GetLength() );
11158 							ARRAY_FOREACH ( i, tRemoteResult.m_dMatches )
11159 							{
11160 								tRes.m_dMatches.Add();
11161 								tRemoteResult.m_tSchema.CloneWholeMatch ( &tRes.m_dMatches.Last(), tRemoteResult.m_dMatches[i] );
11162 								tRes.m_dMatches.Last().m_iTag = ( iOrderTag + iRes - iStart ) | 0x80000000;
11163 							}
11164 
11165 							tRes.m_pMva = pMva;
11166 							tRes.m_pStrings = pString;
11167 							tRes.m_dTag2Pools[iOrderTag+iRes-iStart].m_pMva = pMva;
11168 							tRes.m_dTag2Pools[iOrderTag+iRes-iStart].m_pStrings = pString;
11169 							tRes.m_dMatchCounts.Add ( tRemoteResult.m_dMatches.GetLength() );
11170 							tRes.m_dSchemas.Add ( tRemoteResult.m_tSchema );
11171 							// note how we do NOT add per-index weight here
11172 
11173 							// merge this agent's stats
11174 							tRes.m_iTotalMatches += tRemoteResult.m_iTotalMatches;
11175 							tRes.m_iQueryTime += tRemoteResult.m_iQueryTime;
11176 							tRes.m_iAgentCpuTime += tRemoteResult.m_iCpuTime;
11177 							tRes.m_tAgentIOStats.Add ( tRemoteResult.m_tIOStats );
11178 							tRes.m_iAgentPredictedTime += tRemoteResult.m_iPredictedTime;
11179 							tRes.m_iAgentFetchedDocs += tRemoteResult.m_iAgentFetchedDocs;
11180 							tRes.m_iAgentFetchedHits += tRemoteResult.m_iAgentFetchedHits;
11181 							tRes.m_iAgentFetchedSkips += tRemoteResult.m_iAgentFetchedSkips;
11182 							tRes.m_bHasPrediction |= ( m_dQueries[iRes].m_iMaxPredictedMsec>0 );
11183 
11184 							// merge this agent's words
11185 							MergeWordStats ( tRes, tRemoteResult.m_hWordStats, &m_dFailuresSet[iRes], tFirst.m_sIndexes.cstr() );
11186 						}
11187 
11188 						// dismissed
11189 						tAgent.m_dResults.Reset ();
11190 						tAgent.m_bSuccess = false;
11191 						tAgent.m_sFailure = "";
11192 					}
11193 
11194 					m_dMva2Free.Add ( dMvaStorage.LeakData() );
11195 					m_dString2Free.Add ( dStringStorage.LeakData() );
11196 				}
11197 			}
11198 		} // while ( !bDistDone )
11199 	} // if ( bDist && dAgents.GetLength() )
11200 
11201 	// submit failures from failed agents
11202 	// copy timings from all agents
11203 	if ( dAgents.GetLength() )
11204 	{
11205 		ARRAY_FOREACH ( i, dAgents )
11206 		{
11207 			const AgentConn_t & tAgent = dAgents[i];
11208 
11209 			for ( int j=iStart; j<=iEnd; j++ )
11210 			{
11211 				assert ( tAgent.m_iWall>=0 );
11212 				if ( tAgent.m_iWall<0 )
11213 					m_dAgentTimes[j].Add ( ( tAgent.m_iWall + sphMicroTimer() ) / ( 1000 * ( iEnd-iStart+1 ) ) );
11214 				else
11215 					m_dAgentTimes[j].Add ( ( tAgent.m_iWall ) / ( 1000 * ( iEnd-iStart+1 ) ) );
11216 			}
11217 
11218 			if ( !tAgent.m_bSuccess && !tAgent.m_sFailure.IsEmpty() )
11219 				for ( int j=iStart; j<=iEnd; j++ )
11220 					m_dFailuresSet[j].SubmitEx ( tFirst.m_sIndexes.cstr(), tAgent.m_bBlackhole ? "blackhole %s: %s" : "agent %s: %s",
11221 						tAgent.GetName().cstr(), tAgent.m_sFailure.cstr() );
11222 		}
11223 	}
11224 
11225 	// cleanup
11226 	bool bWasLocalSorter = pLocalSorter!=NULL;
11227 	SafeDelete ( pLocalSorter );
11228 
11229 	/////////////////////
11230 	// merge all results
11231 	/////////////////////
11232 
11233 	if ( m_pProfile )
11234 		m_pProfile->Switch ( SPH_QSTATE_AGGREGATE );
11235 
11236 	CSphIOStats tIO;
11237 
11238 	for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11239 	{
11240 		AggrResult_t & tRes = m_dResults[iRes];
11241 		CSphQuery & tQuery = m_dQueries[iRes];
11242 		CSphSchemaMT * pExtraSchema = tQuery.m_bAgent ? m_dExtraSchemas.Begin() + ( bWasLocalSorter ? 0 : iRes ) : NULL;
11243 
11244 		// minimize sorters needs these pointers
11245 		tIO.Add ( tRes.m_tIOStats );
11246 
11247 		// if there were no successful searches at all, this is an error
11248 		if ( !tRes.m_iSuccesses )
11249 		{
11250 			CSphStringBuilder sFailures;
11251 			m_dFailuresSet[iRes].BuildReport ( sFailures );
11252 
11253 			tRes.m_sError = sFailures.cstr();
11254 			continue;
11255 		}
11256 
11257 		// minimize schema and remove dupes
11258 		// assuming here ( tRes.m_tSchema==tRes.m_dSchemas[0] )
11259 		const CSphFilterSettings * pAggrFilter = NULL;
11260 		if ( m_bMaster && !tQuery.m_tHaving.m_sAttrName.IsEmpty() )
11261 			pAggrFilter = &tQuery.m_tHaving;
11262 
11263 		if ( tRes.m_iSuccesses>1 || tQuery.m_dItems.GetLength() || pAggrFilter )
11264 		{
11265 			if ( pExtraSchema )
11266 				pExtraSchema->RLock();
11267 			UnlockOnDestroy SchemaLocker ( pExtraSchema );
11268 
11269 			if ( m_bMaster && tRes.m_iSuccesses && tQuery.m_dItems.GetLength() && tQuery.m_sGroupBy.IsEmpty() && tRes.m_dMatches.GetLength()==0 )
11270 			{
11271 				ARRAY_FOREACH ( i, tQuery.m_dItems )
11272 				{
11273 					if ( tQuery.m_dItems[i].m_sExpr=="count(*)" )
11274 					{
11275 						tRes.m_sZeroCountName = tQuery.m_dItems[i].m_sAlias;
11276 						break;
11277 					}
11278 				}
11279 			}
11280 
11281 			if ( !MinimizeAggrResult ( tRes, tQuery, m_dLocal.GetLength(), dAgents.GetLength(), pExtraSchema, m_pProfile, m_bSphinxql, pAggrFilter ) )
11282 			{
11283 				tRes.m_iSuccesses = 0;
11284 				return; // FIXME? really return, not just continue?
11285 			}
11286 		}
11287 
11288 		if ( !m_dFailuresSet[iRes].IsEmpty() )
11289 		{
11290 			CSphStringBuilder sFailures;
11291 			m_dFailuresSet[iRes].BuildReport ( sFailures );
11292 			tRes.m_sWarning = sFailures.cstr();
11293 		}
11294 
11295 		////////////
11296 		// finalize
11297 		////////////
11298 
11299 		tRes.m_iOffset = Max ( tQuery.m_iOffset, tQuery.m_iOuterOffset );
11300 		tRes.m_iCount = ( tQuery.m_iOuterLimit ? tQuery.m_iOuterLimit : tQuery.m_iLimit );
11301 		tRes.m_iCount = Max ( Min ( tRes.m_iCount, tRes.m_dMatches.GetLength()-tRes.m_iOffset ), 0 );
11302 	}
11303 
11304 	/////////////////////////////////
11305 	// functions on a table argument
11306 	/////////////////////////////////
11307 
11308 	for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11309 	{
11310 		AggrResult_t & tRes = m_dResults[iRes];
11311 		CSphQuery & tQuery = m_dQueries[iRes];
11312 
11313 		// FIXME! log such queries properly?
11314 		if ( tQuery.m_pTableFunc )
11315 		{
11316 			if ( m_pProfile )
11317 				m_pProfile->Switch ( SPH_QSTATE_TABLE_FUNC );
11318 			if ( !tQuery.m_pTableFunc->Process ( &tRes, tRes.m_sError ) )
11319 				tRes.m_iSuccesses = 0;
11320 			// we don't need it anymore
11321 			SafeDelete ( tQuery.m_pTableFunc ); // FIXME? find more obvious place to delete
11322 		}
11323 	}
11324 
11325 	/////////
11326 	// stats
11327 	/////////
11328 
11329 	tmSubset = sphMicroTimer() - tmSubset;
11330 	tmCpu = sphCpuTimer() - tmCpu;
11331 
11332 	// in multi-queue case (1 actual call per N queries), just divide overall query time evenly
11333 	// otherwise (N calls per N queries), divide common query time overheads evenly
11334 	const int iQueries = iEnd-iStart+1;
11335 	if ( m_bMultiQueue )
11336 	{
11337 		for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11338 		{
11339 			m_dResults[iRes].m_iQueryTime = (int)( tmSubset/1000/iQueries );
11340 			m_dResults[iRes].m_iRealQueryTime = (int)( tmSubset/1000/iQueries );
11341 			m_dResults[iRes].m_iCpuTime = tmCpu/iQueries;
11342 		}
11343 	} else
11344 	{
11345 		int64_t tmAccountedWall = 0;
11346 		int64_t tmAccountedCpu = 0;
11347 		for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11348 		{
11349 			tmAccountedWall += m_dResults[iRes].m_iQueryTime*1000;
11350 			assert ( ( m_dResults[iRes].m_iCpuTime==0 && m_dResults[iRes].m_iAgentCpuTime==0 ) || // all work was done in this thread
11351 					( m_dResults[iRes].m_iCpuTime>0 && m_dResults[iRes].m_iAgentCpuTime==0 ) ||	// children threads work
11352 					( m_dResults[iRes].m_iAgentCpuTime>0 && m_dResults[iRes].m_iCpuTime==0 ) );	// agents work
11353 			tmAccountedCpu += m_dResults[iRes].m_iCpuTime;
11354 			tmAccountedCpu += m_dResults[iRes].m_iAgentCpuTime;
11355 		}
11356 		// whether we had work done in children threads (dist_threads>1) or in agents
11357 		bool bExternalWork = tmAccountedCpu!=0;
11358 
11359 		int64_t tmDeltaWall = ( tmSubset - tmAccountedWall ) / iQueries;
11360 
11361 		for ( int iRes=iStart; iRes<=iEnd; iRes++ )
11362 		{
11363 			m_dResults[iRes].m_iQueryTime += (int)(tmDeltaWall/1000);
11364 			m_dResults[iRes].m_iRealQueryTime = (int)( tmSubset/1000/iQueries );
11365 			m_dResults[iRes].m_iCpuTime = tmCpu/iQueries;
11366 			if ( bExternalWork )
11367 				m_dResults[iRes].m_iCpuTime += tmAccountedCpu;
11368 		}
11369 		// don't forget to add this to stats
11370 		if ( bExternalWork )
11371 			tmCpu += tmAccountedCpu;
11372 	}
11373 
11374 	if ( g_pStats )
11375 	{
11376 		g_tStatsMutex.Lock();
11377 		g_pStats->m_iQueries += iQueries;
11378 		g_pStats->m_iQueryTime += tmSubset;
11379 		g_pStats->m_iQueryCpuTime += tmCpu;
11380 		if ( dAgents.GetLength() )
11381 		{
11382 			int64_t tmWait = 0;
11383 			ARRAY_FOREACH ( i, dAgents )
11384 			{
11385 				tmWait += dAgents[i].m_iWaited;
11386 			}
11387 			// do *not* count queries to dist indexes w/o actual remote agents
11388 			g_pStats->m_iDistQueries++;
11389 			g_pStats->m_iDistWallTime += tmSubset;
11390 			g_pStats->m_iDistLocalTime += tmLocal;
11391 			g_pStats->m_iDistWaitTime += tmWait;
11392 		}
11393 		g_pStats->m_iDiskReads += tIO.m_iReadOps;
11394 		g_pStats->m_iDiskReadTime += tIO.m_iReadTime;
11395 		g_pStats->m_iDiskReadBytes += tIO.m_iReadBytes;
11396 		g_tStatsMutex.Unlock();
11397 	}
11398 
11399 	if ( m_pProfile )
11400 		m_pProfile->Switch ( SPH_QSTATE_UNKNOWN );
11401 }
11402 
11403 
CheckCommandVersion(int iVer,int iDaemonVersion,InputBuffer_c & tReq)11404 bool CheckCommandVersion ( int iVer, int iDaemonVersion, InputBuffer_c & tReq )
11405 {
11406 	if ( (iVer>>8)!=(iDaemonVersion>>8) )
11407 	{
11408 		tReq.SendErrorReply ( "major command version mismatch (expected v.%d.x, got v.%d.%d)",
11409 			iDaemonVersion>>8, iVer>>8, iVer&0xff );
11410 		return false;
11411 	}
11412 	if ( iVer>iDaemonVersion )
11413 	{
11414 		tReq.SendErrorReply ( "client version is higher than daemon version (client is v.%d.%d, daemon is v.%d.%d)",
11415 			iVer>>8, iVer&0xff, iDaemonVersion>>8, iDaemonVersion&0xff );
11416 		return false;
11417 	}
11418 	return true;
11419 }
11420 
11421 
SendSearchResponse(SearchHandler_c & tHandler,InputBuffer_c & tReq,int iSock,int iVer,int iMasterVer)11422 void SendSearchResponse ( SearchHandler_c & tHandler, InputBuffer_c & tReq, int iSock, int iVer, int iMasterVer )
11423 {
11424 	// serve the response
11425 	NetOutputBuffer_c tOut ( iSock );
11426 	int iReplyLen = 0;
11427 	bool bAgentMode = ( iMasterVer>0 );
11428 
11429 	if ( iVer<=0x10C )
11430 	{
11431 		assert ( tHandler.m_dQueries.GetLength()==1 );
11432 		assert ( tHandler.m_dResults.GetLength()==1 );
11433 		const AggrResult_t & tRes = tHandler.m_dResults[0];
11434 
11435 		if ( !tRes.m_sError.IsEmpty() )
11436 		{
11437 			tReq.SendErrorReply ( "%s", tRes.m_sError.cstr() );
11438 			return;
11439 		}
11440 
11441 		iReplyLen = CalcResultLength ( iVer, &tRes, tRes.m_dTag2Pools, bAgentMode, tHandler.m_dQueries[0], iMasterVer );
11442 		bool bWarning = ( iVer>=0x106 && !tRes.m_sWarning.IsEmpty() );
11443 
11444 		// send it
11445 		tOut.SendWord ( (WORD)( bWarning ? SEARCHD_WARNING : SEARCHD_OK ) );
11446 		tOut.SendWord ( VER_COMMAND_SEARCH );
11447 		tOut.SendInt ( iReplyLen );
11448 
11449 		SendResult ( iVer, tOut, &tRes, tRes.m_dTag2Pools, bAgentMode, tHandler.m_dQueries[0], iMasterVer );
11450 
11451 	} else
11452 	{
11453 		ARRAY_FOREACH ( i, tHandler.m_dQueries )
11454 			iReplyLen += CalcResultLength ( iVer, &tHandler.m_dResults[i], tHandler.m_dResults[i].m_dTag2Pools, bAgentMode, tHandler.m_dQueries[i], iMasterVer );
11455 
11456 		// send it
11457 		tOut.SendWord ( (WORD)SEARCHD_OK );
11458 		tOut.SendWord ( VER_COMMAND_SEARCH );
11459 		tOut.SendInt ( iReplyLen );
11460 
11461 		ARRAY_FOREACH ( i, tHandler.m_dQueries )
11462 			SendResult ( iVer, tOut, &tHandler.m_dResults[i], tHandler.m_dResults[i].m_dTag2Pools, bAgentMode, tHandler.m_dQueries[i], iMasterVer );
11463 	}
11464 
11465 	tOut.Flush ();
11466 	assert ( tOut.GetError()==true || tOut.GetSentCount()==iReplyLen+8 );
11467 
11468 	// clean up
11469 	ARRAY_FOREACH ( i, tHandler.m_dQueries )
11470 		SafeDeleteArray ( tHandler.m_dQueries[i].m_pWeights );
11471 }
11472 
11473 
HandleCommandSearch(int iSock,int iVer,InputBuffer_c & tReq,ThdDesc_t * pThd)11474 void HandleCommandSearch ( int iSock, int iVer, InputBuffer_c & tReq, ThdDesc_t * pThd )
11475 {
11476 	MEMORY ( MEM_API_SEARCH );
11477 
11478 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_SEARCH, tReq ) )
11479 		return;
11480 
11481 	int iMasterVer = 0;
11482 	if ( iVer>=0x118 )
11483 		iMasterVer = tReq.GetInt();
11484 	if ( iMasterVer<0 || iMasterVer>VER_MASTER )
11485 	{
11486 		tReq.SendErrorReply ( "master-agent version mismatch; update me first, then update master!" );
11487 		return;
11488 	}
11489 
11490 	// parse request
11491 	int iQueries = 1;
11492 	if ( iVer>=0x10D )
11493 		iQueries = tReq.GetDword ();
11494 
11495 	if ( g_iMaxBatchQueries>0 && ( iQueries<=0 || iQueries>g_iMaxBatchQueries ) )
11496 	{
11497 		tReq.SendErrorReply ( "bad multi-query count %d (must be in 1..%d range)", iQueries, g_iMaxBatchQueries );
11498 		return;
11499 	}
11500 
11501 	SearchHandler_c tHandler ( iQueries, false, ( iMasterVer==0 ) );
11502 	ARRAY_FOREACH ( i, tHandler.m_dQueries )
11503 		if ( !ParseSearchQuery ( tReq, tHandler.m_dQueries[i], iVer, iMasterVer ) )
11504 			return;
11505 
11506 	if ( pThd && tHandler.m_dQueries.GetLength() )
11507 	{
11508 		const CSphQuery & q = tHandler.m_dQueries[0];
11509 		pThd->SetThreadInfo ( "api-search query=\"%s\" comment=\"%s\"", q.m_sQuery.scstr(), q.m_sComment.scstr() );
11510 	}
11511 
11512 	// run queries, send response
11513 	tHandler.RunQueries();
11514 	SendSearchResponse ( tHandler, tReq, iSock, iVer, iMasterVer );
11515 
11516 	int64_t iTotalPredictedTime = 0;
11517 	int64_t iTotalAgentPredictedTime = 0;
11518 	ARRAY_FOREACH ( i, tHandler.m_dResults )
11519 	{
11520 		iTotalPredictedTime += tHandler.m_dResults[i].m_iPredictedTime;
11521 		iTotalAgentPredictedTime += tHandler.m_dResults[i].m_iAgentPredictedTime;
11522 	}
11523 
11524 	g_tLastMetaMutex.Lock();
11525 	g_tLastMeta = tHandler.m_dResults[tHandler.m_dResults.GetLength()-1];
11526 	g_tLastMetaMutex.Unlock();
11527 
11528 	g_tStatsMutex.Lock();
11529 	g_pStats->m_iPredictedTime += iTotalPredictedTime;
11530 	g_pStats->m_iAgentPredictedTime += iTotalAgentPredictedTime;
11531 	g_tStatsMutex.Unlock();
11532 }
11533 
11534 //////////////////////////////////////////////////////////////////////////
11535 // TABLE FUNCTIONS
11536 //////////////////////////////////////////////////////////////////////////
11537 
11538 // table functions take an arbitrary result set as their input,
11539 // and return a new, processed, (completely) different one as their output
11540 //
11541 // 1st argument should be the input result set, but a table function
11542 // can optionally take and handle more arguments
11543 //
11544 // table function can completely (!) change the result set
11545 // including (!) the schema
11546 //
11547 // for now, only builtin table functions are supported
11548 // UDFs are planned when the internal call interface is stabilized
11549 
11550 #define LOC_ERROR(_msg) { sError = _msg; return false; }
11551 #define LOC_ERROR1(_msg,_arg1) { sError.SetSprintf ( _msg, _arg1 ); return false; }
11552 
11553 class CSphTableFuncRemoveRepeats : public ISphTableFunc
11554 {
11555 protected:
11556 	CSphString	m_sCol;
11557 	int			m_iOffset;
11558 	int			m_iLimit;
11559 
11560 public:
ValidateArgs(const CSphVector<CSphString> & dArgs,const CSphQuery &,CSphString & sError)11561 	virtual bool ValidateArgs ( const CSphVector<CSphString> & dArgs, const CSphQuery &, CSphString & sError )
11562 	{
11563 		if ( dArgs.GetLength()!=3 )
11564 			LOC_ERROR ( "REMOVE_REPEATS() requires 4 arguments (result_set, column, offset, limit)" );
11565 		if ( !isdigit ( *dArgs[1].cstr() ) )
11566 			LOC_ERROR ( "REMOVE_REPEATS() argument 3 (offset) must be integer" );
11567 		if ( !isdigit ( *dArgs[2].cstr() ) )
11568 			LOC_ERROR ( "REMOVE_REPEATS() argument 4 (limit) must be integer" );
11569 
11570 		m_sCol = dArgs[0];
11571 		m_iOffset = atoi ( dArgs[1].cstr() );
11572 		m_iLimit = atoi ( dArgs[2].cstr() );
11573 
11574 		if ( !m_iLimit )
11575 			LOC_ERROR ( "REMOVE_REPEATS() argument 4 (limit) must be greater than 0" );
11576 		return true;
11577 	}
11578 
11579 
Process(AggrResult_t * pResult,CSphString & sError)11580 	virtual bool Process ( AggrResult_t * pResult, CSphString & sError )
11581 	{
11582 		assert ( pResult );
11583 
11584 		CSphSwapVector<CSphMatch> & dMatches = pResult->m_dMatches;
11585 		if ( !dMatches.GetLength() )
11586 			return true;
11587 
11588 		const CSphColumnInfo * pCol = pResult->m_tSchema.GetAttr ( m_sCol.cstr() );
11589 		if ( !pCol )
11590 			LOC_ERROR1 ( "REMOVE_REPEATS() argument 2 (column %s) not found in result set", m_sCol.cstr() );
11591 
11592 		ESphAttr t = pCol->m_eAttrType;
11593 		if ( t!=SPH_ATTR_INTEGER && t!=SPH_ATTR_BIGINT && t!=SPH_ATTR_TOKENCOUNT && t!=SPH_ATTR_STRINGPTR && t!=SPH_ATTR_STRING )
11594 			LOC_ERROR1 ( "REMOVE_REPEATS() argument 2 (column %s) must be of INTEGER, BIGINT, or STRINGPTR type", m_sCol.cstr() );
11595 
11596 		// we need to initialize the "last seen" value with a key that
11597 		// is guaranteed to be different from the 1st match that we will scan
11598 		// hence (val-1) for scalars, and NULL for strings
11599 		SphAttr_t iLastValue = ( t==SPH_ATTR_STRING || t==SPH_ATTR_STRINGPTR )
11600 			? 0
11601 			: ( dMatches [ pResult->m_iOffset ].GetAttr ( pCol->m_tLocator ) - 1 );
11602 
11603 		// LIMIT N,M clause must be applied before (!) table function
11604 		// so we scan source matches N to N+M-1
11605 		//
11606 		// within those matches, we filter out repeats in a given column,
11607 		// skip first m_iOffset eligible ones, and emit m_iLimit more
11608 		int iOutPos = 0;
11609 		for ( int i=pResult->m_iOffset; i<Min ( dMatches.GetLength(), pResult->m_iOffset+pResult->m_iCount ); i++ )
11610 		{
11611 			// get value, skip repeats
11612 			SphAttr_t iCur = dMatches[i].GetAttr ( pCol->m_tLocator );
11613 			if ( t==SPH_ATTR_STRING && iCur!=0 )
11614 				iCur = (SphAttr_t)( pResult->m_dTag2Pools [ dMatches[i].m_iTag ].m_pStrings + iCur );
11615 
11616 			if ( iCur==iLastValue )
11617 				continue;
11618 			if ( iCur && iLastValue && t==SPH_ATTR_STRINGPTR && strcmp ( (const char*)iCur, (const char*)iLastValue )==0 )
11619 				continue;
11620 			if ( iCur && iLastValue && t==SPH_ATTR_STRING )
11621 			{
11622 				const BYTE * a = (const BYTE*) iCur;
11623 				const BYTE * b = (const BYTE*) iLastValue;
11624 				int iLen1 = sphUnpackStr ( a, &a );
11625 				int iLen2 = sphUnpackStr ( b, &b );
11626 				if ( iLen1==iLen2 && memcmp ( a, b, iLen1 )==0 )
11627 					continue;
11628 			}
11629 
11630 			iLastValue = iCur;
11631 
11632 			// skip eligible rows according to tablefunc offset
11633 			if ( m_iOffset>0 )
11634 			{
11635 				m_iOffset--;
11636 				continue;
11637 			}
11638 
11639 			// emit!
11640 			if ( iOutPos!=i )
11641 				Swap ( dMatches[iOutPos], dMatches[i] );
11642 
11643 			// break if we reached the tablefunc limit
11644 			if ( ++iOutPos==m_iLimit )
11645 				break;
11646 		}
11647 
11648 		// adjust the result set limits
11649 		pResult->ClampMatches ( iOutPos, true );
11650 		pResult->m_iOffset = 0;
11651 		pResult->m_iCount = dMatches.GetLength();
11652 		return true;
11653 	}
11654 };
11655 
11656 #undef LOC_ERROR1
11657 #undef LOC_ERROR
11658 
11659 //////////////////////////////////////////////////////////////////////////
11660 // SQL PARSER
11661 //////////////////////////////////////////////////////////////////////////
11662 
11663 enum SqlStmt_e
11664 {
11665 	STMT_PARSE_ERROR = 0,
11666 	STMT_DUMMY,
11667 
11668 	STMT_SELECT,
11669 	STMT_INSERT,
11670 	STMT_REPLACE,
11671 	STMT_DELETE,
11672 	STMT_SHOW_WARNINGS,
11673 	STMT_SHOW_STATUS,
11674 	STMT_SHOW_META,
11675 	STMT_SET,
11676 	STMT_BEGIN,
11677 	STMT_COMMIT,
11678 	STMT_ROLLBACK,
11679 	STMT_CALL, // check.pl STMT_CALL_SNIPPETS STMT_CALL_KEYWORDS
11680 	STMT_DESCRIBE,
11681 	STMT_SHOW_TABLES,
11682 	STMT_UPDATE,
11683 	STMT_CREATE_FUNCTION,
11684 	STMT_DROP_FUNCTION,
11685 	STMT_ATTACH_INDEX,
11686 	STMT_FLUSH_RTINDEX,
11687 	STMT_FLUSH_RAMCHUNK,
11688 	STMT_SHOW_VARIABLES,
11689 	STMT_TRUNCATE_RTINDEX,
11690 	STMT_SELECT_SYSVAR,
11691 	STMT_SHOW_COLLATION,
11692 	STMT_SHOW_CHARACTER_SET,
11693 	STMT_OPTIMIZE_INDEX,
11694 	STMT_SHOW_AGENT_STATUS,
11695 	STMT_SHOW_INDEX_STATUS,
11696 	STMT_SHOW_PROFILE,
11697 	STMT_ALTER_ADD,
11698 	STMT_ALTER_DROP,
11699 	STMT_SHOW_PLAN,
11700 	STMT_SELECT_DUAL,
11701 	STMT_SHOW_DATABASES,
11702 	STMT_CREATE_PLUGIN,
11703 	STMT_DROP_PLUGIN,
11704 	STMT_SHOW_PLUGINS,
11705 	STMT_SHOW_THREADS,
11706 	STMT_FACET,
11707 	STMT_ALTER_RECONFIGURE,
11708 	STMT_SHOW_INDEX_SETTINGS,
11709 	STMT_FLUSH_INDEX,
11710 
11711 	STMT_TOTAL
11712 };
11713 
11714 
11715 // FIXME? verify or generate these automatically somehow?
11716 static const char * g_dSqlStmts[] =
11717 {
11718 	"parse_error", "dummy", "select", "insert", "replace", "delete", "show_warnings",
11719 	"show_status", "show_meta", "set", "begin", "commit", "rollback", "call",
11720 	"desc", "show_tables", "update", "create_func", "drop_func", "attach_index",
11721 	"flush_rtindex", "flush_ramchunk", "show_variables", "truncate_rtindex", "select_sysvar",
11722 	"show_collation", "show_character_set", "optimize_index", "show_agent_status",
11723 	"show_index_status", "show_profile", "alter_add", "alter_drop", "show_plan",
11724 	"select_dual", "show_databases", "create_plugin", "drop_plugin", "show_plugins", "show_threads",
11725 	"facet", "alter_reconfigure", "show_index_settings", "flush_attributes"
11726 };
11727 
11728 STATIC_ASSERT ( sizeof(g_dSqlStmts)/sizeof(g_dSqlStmts[0])==STMT_TOTAL, STMT_DESC_SHOULD_BE_SAME_AS_STMT_TOTAL );
11729 
11730 
11731 /// refcounted vector
11732 template < typename T >
11733 class RefcountedVector_c : public CSphVector<T>, public ISphRefcounted
11734 {
11735 };
11736 
11737 typedef CSphRefcountedPtr < RefcountedVector_c<SphAttr_t> > AttrValues_p;
11738 
11739 /// insert value
11740 struct SqlInsert_t
11741 {
11742 	int						m_iType;
11743 	CSphString				m_sVal;		// OPTIMIZE? use char* and point to node?
11744 	int64_t					m_iVal;
11745 	float					m_fVal;
11746 	AttrValues_p			m_pVals;
11747 
SqlInsert_tSqlInsert_t11748 	SqlInsert_t ()
11749 		: m_pVals ( NULL )
11750 	{}
11751 };
11752 
11753 
11754 /// parser view on a generic node
11755 /// CAUTION, nodes get copied in the parser all the time, must keep assignment slim
11756 struct SqlNode_t
11757 {
11758 	int						m_iStart;	///< first byte relative to m_pBuf, inclusive
11759 	int						m_iEnd;		///< last byte relative to m_pBuf, exclusive! thus length = end - start
11760 	int64_t					m_iValue;
11761 	int						m_iType;	///< TOK_xxx type for insert values; SPHINXQL_TOK_xxx code for special idents
11762 	float					m_fValue;
11763 	AttrValues_p			m_pValues;	///< filter values vector (FIXME? replace with numeric handles into parser state?)
11764 
SqlNode_tSqlNode_t11765 	SqlNode_t()
11766 		: m_iValue ( 0 )
11767 		, m_iType ( 0 )
11768 		, m_pValues ( NULL )
11769 	{}
11770 };
11771 #define YYSTYPE SqlNode_t
11772 
11773 
11774 enum SqlSet_e
11775 {
11776 	SET_LOCAL,
11777 	SET_GLOBAL_UVAR,
11778 	SET_GLOBAL_SVAR,
11779 	SET_INDEX_UVAR
11780 };
11781 
11782 /// parsing result
11783 /// one day, we will start subclassing this
11784 struct SqlStmt_t
11785 {
11786 	SqlStmt_e				m_eStmt;
11787 	int						m_iRowsAffected;
11788 	const char *			m_sStmt; // for error reporting
11789 
11790 	// SELECT specific
11791 	CSphQuery				m_tQuery;
11792 
11793 	CSphString				m_sTableFunc;
11794 	CSphVector<CSphString>	m_dTableFuncArgs;
11795 
11796 	// used by INSERT, DELETE, CALL, DESC, ATTACH, ALTER
11797 	CSphString				m_sIndex;
11798 
11799 	// INSERT (and CALL) specific
11800 	CSphVector<SqlInsert_t>	m_dInsertValues; // reused by CALL
11801 	CSphVector<CSphString>	m_dInsertSchema;
11802 	int						m_iSchemaSz;
11803 
11804 	// SET specific
11805 	CSphString				m_sSetName;		// reused by ATTACH
11806 	SqlSet_e				m_eSet;
11807 	int						m_iSetValue;
11808 	CSphString				m_sSetValue;
11809 	CSphVector<SphAttr_t>	m_dSetValues;
11810 	bool					m_bSetNull;
11811 
11812 	// CALL specific
11813 	CSphString				m_sCallProc;
11814 	CSphVector<CSphString>	m_dCallOptNames;
11815 	CSphVector<SqlInsert_t>	m_dCallOptValues;
11816 	CSphVector<CSphString>	m_dCallStrings;
11817 
11818 	// UPDATE specific
11819 	CSphAttrUpdate			m_tUpdate;
11820 	int						m_iListStart; // < the position of start and end of index's definition in original query.
11821 	int						m_iListEnd;
11822 
11823 	// CREATE/DROP FUNCTION, INSTALL PLUGIN specific
11824 	CSphString				m_sUdfName; // FIXME! move to arg1?
11825 	CSphString				m_sUdfLib;
11826 	ESphAttr				m_eUdfType;
11827 
11828 	// ALTER specific
11829 	CSphString				m_sAlterAttr;
11830 	ESphAttr				m_eAlterColType;
11831 
11832 	// SHOW THREADS specific
11833 	int						m_iThreadsCols;
11834 
11835 	// generic parameter, different meanings in different statements
11836 	// filter pattern in DESCRIBE, SHOW TABLES / META / VARIABLES
11837 	// target index name in ATTACH
11838 	// token filter options in INSERT
11839 	// plugin type in INSTALL PLUGIN
11840 	CSphString				m_sStringParam;
11841 
11842 	// generic integer parameter, used in SHOW SETTINGS, default value -1
11843 	int						m_iIntParam;
11844 
SqlStmt_tSqlStmt_t11845 	SqlStmt_t ()
11846 		: m_eStmt ( STMT_PARSE_ERROR )
11847 		, m_iRowsAffected ( 0 )
11848 		, m_sStmt ( NULL )
11849 		, m_iSchemaSz ( 0 )
11850 		, m_eSet ( SET_LOCAL )
11851 		, m_iSetValue ( 0 )
11852 		, m_bSetNull ( false )
11853 		, m_iListStart ( -1 )
11854 		, m_iListEnd ( -1 )
11855 		, m_eUdfType ( SPH_ATTR_NONE )
11856 		, m_iThreadsCols ( 0 )
11857 		, m_iIntParam ( -1 )
11858 	{
11859 		m_tQuery.m_eMode = SPH_MATCH_EXTENDED2; // only new and shiny matching and sorting
11860 		m_tQuery.m_eSort = SPH_SORT_EXTENDED;
11861 		m_tQuery.m_sSortBy = "@weight desc"; // default order
11862 		m_tQuery.m_sOrderBy = "@weight desc";
11863 		m_tQuery.m_iAgentQueryTimeout = g_iAgentQueryTimeout;
11864 		m_tQuery.m_iRetryCount = g_iAgentRetryCount;
11865 		m_tQuery.m_iRetryDelay = g_iAgentRetryDelay;
11866 	}
11867 
AddSchemaItemSqlStmt_t11868 	bool AddSchemaItem ( const char * psName )
11869 	{
11870 		m_dInsertSchema.Add ( psName );
11871 		m_dInsertSchema.Last().ToLower();
11872 		m_iSchemaSz = m_dInsertSchema.GetLength();
11873 		return true; // stub; check if the given field actually exists in the schema
11874 	}
11875 
11876 	// check if the number of fields which would be inserted is in accordance to the given schema
CheckInsertIntegritySqlStmt_t11877 	bool CheckInsertIntegrity()
11878 	{
11879 		// cheat: if no schema assigned, assume the size of schema as the size of the first row.
11880 		// (if it is wrong, it will be revealed later)
11881 		if ( !m_iSchemaSz )
11882 			m_iSchemaSz = m_dInsertValues.GetLength();
11883 
11884 		m_iRowsAffected++;
11885 		return m_dInsertValues.GetLength()==m_iRowsAffected*m_iSchemaSz;
11886 	}
11887 };
11888 
11889 
11890 /// magic codes passed via SqlNode_t::m_iStart to handle certain special tokens
11891 /// for instance, to fixup "count(*)" as "@count" easily
11892 enum
11893 {
11894 	SPHINXQL_TOK_COUNT		= -1,
11895 	SPHINXQL_TOK_GROUPBY	= -2,
11896 	SPHINXQL_TOK_WEIGHT		= -3,
11897 	SPHINXQL_TOK_ID			= -4
11898 };
11899 
11900 
11901 struct SqlParser_c : ISphNoncopyable
11902 {
11903 public:
11904 	void *			m_pScanner;
11905 	const char *	m_pBuf;
11906 	const char *	m_pLastTokenStart;
11907 	CSphString *	m_pParseError;
11908 	CSphQuery *		m_pQuery;
11909 	bool			m_bGotQuery;
11910 	SqlStmt_t *		m_pStmt;
11911 	CSphVector<SqlStmt_t> & m_dStmt;
11912 	ESphCollation	m_eCollation;
11913 	BYTE			m_uSyntaxFlags;
11914 
11915 public:
11916 	explicit		SqlParser_c ( CSphVector<SqlStmt_t> & dStmt, ESphCollation eCollation );
11917 
11918 	void			PushQuery ();
11919 
11920 	bool			AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue );
11921 	bool			AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue, const SqlNode_t & sArg );
11922 	bool			AddOption ( const SqlNode_t & tIdent, CSphVector<CSphNamedInt> & dNamed );
11923 	bool			AddInsertOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue );
11924 	void			AddItem ( SqlNode_t * pExpr, ESphAggrFunc eFunc=SPH_AGGR_NONE, SqlNode_t * pStart=NULL, SqlNode_t * pEnd=NULL );
11925 	bool			AddItem ( const char * pToken, SqlNode_t * pStart=NULL, SqlNode_t * pEnd=NULL );
11926 	bool			AddCount ();
11927 	void			AliasLastItem ( SqlNode_t * pAlias );
11928 
11929 	/// called on transition from an outer select to inner select
ResetSelectSqlParser_c11930 	void ResetSelect()
11931 	{
11932 		if ( m_pQuery )
11933 			m_pQuery->m_iSQLSelectStart = m_pQuery->m_iSQLSelectEnd = -1;
11934 	}
11935 
11936 	/// called every time we capture a select list item
11937 	/// (i think there should be a simpler way to track these though)
SetSelectSqlParser_c11938 	void SetSelect ( SqlNode_t * pStart, SqlNode_t * pEnd=NULL )
11939 	{
11940 		if ( m_pQuery )
11941 		{
11942 			if ( pStart && ( m_pQuery->m_iSQLSelectStart<0 || m_pQuery->m_iSQLSelectStart>pStart->m_iStart ) )
11943 				m_pQuery->m_iSQLSelectStart = pStart->m_iStart;
11944 			if ( !pEnd )
11945 				pEnd = pStart;
11946 			if ( pEnd && ( m_pQuery->m_iSQLSelectEnd<0 || m_pQuery->m_iSQLSelectEnd<pEnd->m_iEnd ) )
11947 				m_pQuery->m_iSQLSelectEnd = pEnd->m_iEnd;
11948 		}
11949 	}
11950 
ToStringSqlParser_c11951 	inline CSphString & ToString ( CSphString & sRes, const SqlNode_t & tNode ) const
11952 	{
11953 		if ( tNode.m_iType>=0 )
11954 			sRes.SetBinary ( m_pBuf + tNode.m_iStart, tNode.m_iEnd - tNode.m_iStart );
11955 		else switch ( tNode.m_iType )
11956 		{
11957 			case SPHINXQL_TOK_COUNT:	sRes = "@count"; break;
11958 			case SPHINXQL_TOK_GROUPBY:	sRes = "@groupby"; break;
11959 			case SPHINXQL_TOK_WEIGHT:	sRes = "@weight"; break;
11960 			case SPHINXQL_TOK_ID:		sRes = "@id"; break;
11961 			default:					assert ( 0 && "INTERNAL ERROR: unknown parser ident code" );
11962 		}
11963 		return sRes;
11964 	}
11965 
ToStringUnescapeSqlParser_c11966 	inline void ToStringUnescape ( CSphString & sRes, const SqlNode_t & tNode ) const
11967 	{
11968 		assert ( tNode.m_iType>=0 );
11969 		SqlUnescape ( sRes, m_pBuf + tNode.m_iStart, tNode.m_iEnd - tNode.m_iStart );
11970 	}
11971 
11972 	bool			AddSchemaItem ( SqlNode_t * pNode );
11973 	bool			SetMatch ( const SqlNode_t & tValue );
11974 	void			AddConst ( int iList, const SqlNode_t& tValue );
11975 	void			SetStatement ( const SqlNode_t & tName, SqlSet_e eSet );
11976 	bool			AddFloatRangeFilter ( const SqlNode_t & tAttr, float fMin, float fMax, bool bHasEqual, bool bExclude=false );
11977 	bool			AddIntRangeFilter ( const SqlNode_t & tAttr, int64_t iMin, int64_t iMax );
11978 	bool			AddIntFilterGreater ( const SqlNode_t & tAttr, int64_t iVal, bool bHasEqual );
11979 	bool			AddIntFilterLesser ( const SqlNode_t & tAttr, int64_t iVal, bool bHasEqual );
11980 	bool			AddUservarFilter ( const SqlNode_t & tCol, const SqlNode_t & tVar, bool bExclude );
11981 	void			AddGroupBy ( const SqlNode_t & tGroupBy );
11982 	bool			AddDistinct ( SqlNode_t * pNewExpr, SqlNode_t * pStart, SqlNode_t * pEnd );
11983 	CSphFilterSettings *	AddFilter ( const SqlNode_t & tCol, ESphFilter eType );
11984 	bool					AddStringFilter ( const SqlNode_t & tCol, const SqlNode_t & tVal, bool bExclude );
AddValuesFilterSqlParser_c11985 	CSphFilterSettings *	AddValuesFilter ( const SqlNode_t & tCol ) { return AddFilter ( tCol, SPH_FILTER_VALUES ); }
11986 	bool					AddStringListFilter ( const SqlNode_t & tCol, SqlNode_t & tVal, bool bExclude );
11987 	bool					AddNullFilter ( const SqlNode_t & tCol, bool bEqualsNull );
11988 	void			AddHaving ();
11989 
SetOldSyntaxSqlParser_c11990 	inline bool		SetOldSyntax()
11991 	{
11992 		m_uSyntaxFlags |= 1;
11993 		return IsGoodSyntax ();
11994 	}
11995 
SetNewSyntaxSqlParser_c11996 	inline bool		SetNewSyntax()
11997 	{
11998 		m_uSyntaxFlags |= 2;
11999 		return IsGoodSyntax ();
12000 	}
12001 	bool IsGoodSyntax ();
IsDeprecatedSyntaxSqlParser_c12002 	inline bool IsDeprecatedSyntax () const
12003 	{
12004 		return m_uSyntaxFlags & 1;
12005 	}
12006 
12007 	int							AllocNamedVec ();
12008 	CSphVector<CSphNamedInt> &	GetNamedVec ( int iIndex );
12009 	void						FreeNamedVec ( int iIndex );
12010 	bool						UpdateStatement ( SqlNode_t * pNode );
12011 	bool						DeleteStatement ( SqlNode_t * pNode );
12012 
12013 	void						AddUpdatedAttr ( const SqlNode_t & tName, ESphAttr eType ) const;
12014 	void						UpdateMVAAttr ( const SqlNode_t & tName, const SqlNode_t& dValues );
12015 	void						SetGroupbyLimit ( int iLimit );
12016 
12017 private:
12018 	void						AutoAlias ( CSphQueryItem & tItem, SqlNode_t * pStart, SqlNode_t * pEnd );
12019 	void						GenericStatement ( SqlNode_t * pNode, SqlStmt_e iStmt );
12020 
12021 protected:
12022 	bool						m_bNamedVecBusy;
12023 	CSphVector<CSphNamedInt>	m_dNamedVec;
12024 };
12025 
12026 //////////////////////////////////////////////////////////////////////////
12027 
12028 // unused parameter, simply to avoid type clash between all my yylex() functions
12029 #define YY_DECL static int my_lex ( YYSTYPE * lvalp, void * yyscanner, SqlParser_c * pParser )
12030 
12031 #if USE_WINDOWS
12032 #define YY_NO_UNISTD_H 1
12033 #endif
12034 
12035 #include "llsphinxql.c"
12036 
12037 
yyerror(SqlParser_c * pParser,const char * sMessage)12038 void yyerror ( SqlParser_c * pParser, const char * sMessage )
12039 {
12040 	// flex put a zero at last token boundary; make it undo that
12041 	yylex_unhold ( pParser->m_pScanner );
12042 
12043 	// create our error message
12044 	pParser->m_pParseError->SetSprintf ( "sphinxql: %s near '%s'", sMessage,
12045 		pParser->m_pLastTokenStart ? pParser->m_pLastTokenStart : "(null)" );
12046 
12047 	// fixup TOK_xxx thingies
12048 	char * s = const_cast<char*> ( pParser->m_pParseError->cstr() );
12049 	char * d = s;
12050 	while ( *s )
12051 	{
12052 		if ( strncmp ( s, "TOK_", 4 )==0 )
12053 			s += 4;
12054 		else
12055 			*d++ = *s++;
12056 	}
12057 	*d = '\0';
12058 }
12059 
12060 
12061 #ifndef NDEBUG
12062 // using a proxy to be possible to debug inside yylex
yylex(YYSTYPE * lvalp,SqlParser_c * pParser)12063 static int yylex ( YYSTYPE * lvalp, SqlParser_c * pParser )
12064 {
12065 	int res = my_lex ( lvalp, pParser->m_pScanner, pParser );
12066 	return res;
12067 }
12068 #else
yylex(YYSTYPE * lvalp,SqlParser_c * pParser)12069 static int yylex ( YYSTYPE * lvalp, SqlParser_c * pParser )
12070 {
12071 	return my_lex ( lvalp, pParser->m_pScanner, pParser );
12072 }
12073 #endif
12074 
12075 #ifdef CMAKE_GENERATED_GRAMMAR
12076 	#include "bissphinxql.c"
12077 #else
12078 	#include "yysphinxql.c"
12079 #endif
12080 
12081 //////////////////////////////////////////////////////////////////////////
12082 
12083 class CSphMatchVariant : public CSphMatch
12084 {
12085 public:
ToInt(const SqlInsert_t & tVal)12086 	inline static SphAttr_t ToInt ( const SqlInsert_t & tVal )
12087 	{
12088 		switch ( tVal.m_iType )
12089 		{
12090 			case TOK_QUOTED_STRING :	return strtoul ( tVal.m_sVal.cstr(), NULL, 10 ); // FIXME? report conversion error?
12091 			case TOK_CONST_INT:			return int(tVal.m_iVal);
12092 			case TOK_CONST_FLOAT:		return int(tVal.m_fVal); // FIXME? report conversion error
12093 		}
12094 		return 0;
12095 	}
ToBigInt(const SqlInsert_t & tVal)12096 	inline static SphAttr_t ToBigInt ( const SqlInsert_t & tVal )
12097 	{
12098 		switch ( tVal.m_iType )
12099 		{
12100 			case TOK_QUOTED_STRING :	return strtoll ( tVal.m_sVal.cstr(), NULL, 10 ); // FIXME? report conversion error?
12101 			case TOK_CONST_INT:			return tVal.m_iVal;
12102 			case TOK_CONST_FLOAT:		return int(tVal.m_fVal); // FIXME? report conversion error?
12103 		}
12104 		return 0;
12105 	}
12106 #if USE_64BIT
12107 #define ToDocid ToBigInt
12108 #else
12109 #define ToDocid ToInt
12110 #endif // USE_64BIT
12111 
SetAttr(const CSphAttrLocator & tLoc,const SqlInsert_t & tVal,ESphAttr eTargetType)12112 	bool SetAttr ( const CSphAttrLocator & tLoc, const SqlInsert_t & tVal, ESphAttr eTargetType )
12113 	{
12114 		switch ( eTargetType )
12115 		{
12116 			case SPH_ATTR_INTEGER:
12117 			case SPH_ATTR_TIMESTAMP:
12118 			case SPH_ATTR_BOOL:
12119 			case SPH_ATTR_TOKENCOUNT:
12120 				CSphMatch::SetAttr ( tLoc, ToInt(tVal) );
12121 				break;
12122 			case SPH_ATTR_BIGINT:
12123 				CSphMatch::SetAttr ( tLoc, ToBigInt(tVal) );
12124 				break;
12125 			case SPH_ATTR_FLOAT:
12126 				if ( tVal.m_iType==TOK_QUOTED_STRING )
12127 					SetAttrFloat ( tLoc, (float)strtod ( tVal.m_sVal.cstr(), NULL ) ); // FIXME? report conversion error?
12128 				else if ( tVal.m_iType==TOK_CONST_INT )
12129 					SetAttrFloat ( tLoc, float(tVal.m_iVal) ); // FIXME? report conversion error?
12130 				else if ( tVal.m_iType==TOK_CONST_FLOAT )
12131 					SetAttrFloat ( tLoc, tVal.m_fVal );
12132 				break;
12133 			case SPH_ATTR_STRING:
12134 			case SPH_ATTR_UINT32SET:
12135 			case SPH_ATTR_INT64SET:
12136 			case SPH_ATTR_JSON:
12137 				CSphMatch::SetAttr ( tLoc, 0 );
12138 				break;
12139 			default:
12140 				return false;
12141 		};
12142 		return true;
12143 	}
12144 
SetDefaultAttr(const CSphAttrLocator & tLoc,ESphAttr eTargetType)12145 	inline bool SetDefaultAttr ( const CSphAttrLocator & tLoc, ESphAttr eTargetType )
12146 	{
12147 		SqlInsert_t tVal;
12148 		tVal.m_iType = TOK_CONST_INT;
12149 		tVal.m_iVal = 0;
12150 		return SetAttr ( tLoc, tVal, eTargetType );
12151 	}
12152 };
12153 
SqlParser_c(CSphVector<SqlStmt_t> & dStmt,ESphCollation eCollation)12154 SqlParser_c::SqlParser_c ( CSphVector<SqlStmt_t> & dStmt, ESphCollation eCollation )
12155 	: m_pQuery ( NULL )
12156 	, m_pStmt ( NULL )
12157 	, m_dStmt ( dStmt )
12158 	, m_eCollation ( eCollation )
12159 	, m_uSyntaxFlags ( 0 )
12160 	, m_bNamedVecBusy ( false )
12161 {
12162 	assert ( !m_dStmt.GetLength() );
12163 	PushQuery ();
12164 }
12165 
PushQuery()12166 void SqlParser_c::PushQuery ()
12167 {
12168 	assert ( m_dStmt.GetLength() || ( !m_pQuery && !m_pStmt ) );
12169 
12170 	// post set proper result-set order
12171 	if ( m_dStmt.GetLength() )
12172 	{
12173 		if ( m_pQuery->m_sGroupBy.IsEmpty() )
12174 			m_pQuery->m_sSortBy = m_pQuery->m_sOrderBy;
12175 		else
12176 			m_pQuery->m_sGroupSortBy = m_pQuery->m_sOrderBy;
12177 	}
12178 
12179 	// add new
12180 	m_dStmt.Add ( SqlStmt_t() );
12181 	m_pStmt = &m_dStmt.Last();
12182 	m_pQuery = &m_pStmt->m_tQuery;
12183 	m_pQuery->m_eCollation = m_eCollation;
12184 
12185 	m_bGotQuery = false;
12186 }
12187 
12188 
AddOption(const SqlNode_t & tIdent,const SqlNode_t & tValue)12189 bool SqlParser_c::AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue )
12190 {
12191 	CSphString sOpt, sVal;
12192 	ToString ( sOpt, tIdent ).ToLower();
12193 	ToString ( sVal, tValue ).ToLower().Unquote();
12194 
12195 	// OPTIMIZE? hash possible sOpt choices?
12196 	if ( sOpt=="ranker" )
12197 	{
12198 		m_pQuery->m_eRanker = SPH_RANK_TOTAL;
12199 		for ( int iRanker = SPH_RANK_PROXIMITY_BM25; iRanker<=SPH_RANK_SPH04; iRanker++ )
12200 			if ( sVal==sphGetRankerName ( ESphRankMode ( iRanker ) ) )
12201 			{
12202 				m_pQuery->m_eRanker = ESphRankMode ( iRanker );
12203 				break;
12204 			}
12205 
12206 		if ( m_pQuery->m_eRanker==SPH_RANK_TOTAL )
12207 		{
12208 			if ( sVal==sphGetRankerName ( SPH_RANK_EXPR ) || sVal==sphGetRankerName ( SPH_RANK_EXPORT ) )
12209 			{
12210 				m_pParseError->SetSprintf ( "missing ranker expression (use OPTION ranker=expr('1+2') for example)" );
12211 				return false;
12212 			} else if ( sphPluginExists ( PLUGIN_RANKER, sVal.cstr() ) )
12213 			{
12214 				m_pQuery->m_eRanker = SPH_RANK_PLUGIN;
12215 				m_pQuery->m_sUDRanker = sVal;
12216 			}
12217 			m_pParseError->SetSprintf ( "unknown ranker '%s'", sVal.cstr() );
12218 			return false;
12219 		}
12220 	} else if ( sOpt=="token_filter" )	// tokfilter = hello.dll:hello:some_opts
12221 	{
12222 		CSphVector<CSphString> dParams;
12223 		if ( !sphPluginParseSpec ( sVal, dParams, *m_pParseError ) )
12224 			return false;
12225 
12226 		if ( !dParams.GetLength() )
12227 		{
12228 			m_pParseError->SetSprintf ( "missing token filter spec string" );
12229 			return false;
12230 		}
12231 
12232 		m_pQuery->m_sQueryTokenFilterLib = dParams[0];
12233 		m_pQuery->m_sQueryTokenFilterName = dParams[1];
12234 		m_pQuery->m_sQueryTokenFilterOpts = dParams[2];
12235 	} else if ( sOpt=="max_matches" )
12236 	{
12237 		m_pQuery->m_iMaxMatches = (int)tValue.m_iValue;
12238 
12239 	} else if ( sOpt=="cutoff" )
12240 	{
12241 		m_pQuery->m_iCutoff = (int)tValue.m_iValue;
12242 
12243 	} else if ( sOpt=="max_query_time" )
12244 	{
12245 		m_pQuery->m_uMaxQueryMsec = (int)tValue.m_iValue;
12246 
12247 	} else if ( sOpt=="retry_count" )
12248 	{
12249 		m_pQuery->m_iRetryCount = (int)tValue.m_iValue;
12250 
12251 	} else if ( sOpt=="retry_delay" )
12252 	{
12253 		m_pQuery->m_iRetryDelay = (int)tValue.m_iValue;
12254 
12255 	} else if ( sOpt=="reverse_scan" )
12256 	{
12257 		m_pQuery->m_bReverseScan = ( tValue.m_iValue!=0 );
12258 
12259 	} else if ( sOpt=="ignore_nonexistent_columns" )
12260 	{
12261 		m_pQuery->m_bIgnoreNonexistent = ( tValue.m_iValue!=0 );
12262 
12263 	} else if ( sOpt=="comment" )
12264 	{
12265 		ToStringUnescape ( m_pQuery->m_sComment, tValue );
12266 
12267 	} else if ( sOpt=="sort_method" )
12268 	{
12269 		if ( sVal=="pq" )			m_pQuery->m_bSortKbuffer = false;
12270 		else if ( sVal=="kbuffer" )	m_pQuery->m_bSortKbuffer = true;
12271 		else
12272 		{
12273 			m_pParseError->SetSprintf ( "unknown sort_method=%s (known values are pq, kbuffer)", sVal.cstr() );
12274 			return false;
12275 		}
12276 
12277 	} else if ( sOpt=="agent_query_timeout" )
12278 	{
12279 		m_pQuery->m_iAgentQueryTimeout = (int)tValue.m_iValue;
12280 
12281 	} else if ( sOpt=="max_predicted_time" )
12282 	{
12283 		m_pQuery->m_iMaxPredictedMsec = int ( tValue.m_iValue > INT_MAX ? INT_MAX : tValue.m_iValue );
12284 
12285 	} else if ( sOpt=="boolean_simplify" )
12286 	{
12287 		m_pQuery->m_bSimplify = true;
12288 
12289 	} else if ( sOpt=="idf" )
12290 	{
12291 		CSphVector<CSphString> dOpts;
12292 		sphSplit ( dOpts, sVal.cstr() );
12293 
12294 		ARRAY_FOREACH ( i, dOpts )
12295 		{
12296 			if ( dOpts[i]=="normalized" )
12297 				m_pQuery->m_bPlainIDF = false;
12298 			else if ( dOpts[i]=="plain" )
12299 				m_pQuery->m_bPlainIDF = true;
12300 			else if ( dOpts[i]=="tfidf_normalized" )
12301 				m_pQuery->m_bNormalizedTFIDF = true;
12302 			else if ( dOpts[i]=="tfidf_unnormalized" )
12303 				m_pQuery->m_bNormalizedTFIDF = false;
12304 			else
12305 			{
12306 				m_pParseError->SetSprintf ( "unknown flag %s in idf=%s (known values are plain, normalized, tfidf_normalized, tfidf_unnormalized)",
12307 					dOpts[i].cstr(), sVal.cstr() );
12308 				return false;
12309 			}
12310 		}
12311 	} else if ( sOpt=="global_idf" )
12312 	{
12313 		m_pQuery->m_bGlobalIDF = ( tValue.m_iValue!=0 );
12314 
12315 	} else if ( sOpt=="local_df" )
12316 	{
12317 		m_pQuery->m_bLocalDF = ( tValue.m_iValue!=0 );
12318 
12319 	} else if ( sOpt=="ignore_nonexistent_indexes" )
12320 	{
12321 		m_pQuery->m_bIgnoreNonexistentIndexes = ( tValue.m_iValue!=0 );
12322 
12323 	} else if ( sOpt=="strict" )
12324 	{
12325 		m_pQuery->m_bStrict = ( tValue.m_iValue!=0 );
12326 
12327 	} else if ( sOpt=="columns" ) // for SHOW THREADS
12328 	{
12329 		m_pStmt->m_iThreadsCols = Max ( (int)tValue.m_iValue, 0 );
12330 
12331 	} else if ( sOpt=="rand_seed" )
12332 	{
12333 		m_pStmt->m_tQuery.m_iRandSeed = int64_t(DWORD(tValue.m_iValue));
12334 
12335 	} else
12336 	{
12337 		m_pParseError->SetSprintf ( "unknown option '%s' (or bad argument type)", sOpt.cstr() );
12338 		return false;
12339 	}
12340 
12341 	return true;
12342 }
12343 
12344 
AddOption(const SqlNode_t & tIdent,const SqlNode_t & tValue,const SqlNode_t & tArg)12345 bool SqlParser_c::AddOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue, const SqlNode_t & tArg )
12346 {
12347 	CSphString sOpt, sVal;
12348 	ToString ( sOpt, tIdent ).ToLower();
12349 	ToString ( sVal, tValue ).ToLower().Unquote();
12350 
12351 	if ( sOpt=="ranker" )
12352 	{
12353 		bool bUDR = sphPluginExists ( PLUGIN_RANKER, sVal.cstr() );
12354 		if ( sVal=="expr" || sVal=="export" || bUDR )
12355 		{
12356 			if ( bUDR )
12357 			{
12358 				m_pQuery->m_eRanker = SPH_RANK_PLUGIN;
12359 				m_pQuery->m_sUDRanker = sVal;
12360 				ToStringUnescape ( m_pQuery->m_sUDRankerOpts, tArg );
12361 			} else
12362 			{
12363 				m_pQuery->m_eRanker = sVal=="expr" ? SPH_RANK_EXPR : SPH_RANK_EXPORT;
12364 				ToStringUnescape ( m_pQuery->m_sRankerExpr, tArg );
12365 			}
12366 
12367 			return true;
12368 		}
12369 	}
12370 
12371 	m_pParseError->SetSprintf ( "unknown option or extra argument to '%s=%s'", sOpt.cstr(), sVal.cstr() );
12372 	return false;
12373 }
12374 
12375 
AddOption(const SqlNode_t & tIdent,CSphVector<CSphNamedInt> & dNamed)12376 bool SqlParser_c::AddOption ( const SqlNode_t & tIdent, CSphVector<CSphNamedInt> & dNamed )
12377 {
12378 	CSphString sOpt;
12379 	ToString ( sOpt, tIdent ).ToLower ();
12380 
12381 	if ( sOpt=="field_weights" )
12382 	{
12383 		m_pQuery->m_dFieldWeights.SwapData ( dNamed );
12384 
12385 	} else if ( sOpt=="index_weights" )
12386 	{
12387 		m_pQuery->m_dIndexWeights.SwapData ( dNamed );
12388 
12389 	} else
12390 	{
12391 		m_pParseError->SetSprintf ( "unknown option '%s' (or bad argument type)", sOpt.cstr() );
12392 		return false;
12393 	}
12394 
12395 	return true;
12396 }
12397 
12398 
AddInsertOption(const SqlNode_t & tIdent,const SqlNode_t & tValue)12399 bool SqlParser_c::AddInsertOption ( const SqlNode_t & tIdent, const SqlNode_t & tValue )
12400 {
12401 	CSphString sOpt, sVal;
12402 	ToString ( sOpt, tIdent ).ToLower();
12403 	ToString ( sVal, tValue ).Unquote();
12404 
12405 	if ( sOpt=="token_filter_options" )
12406 	{
12407 		m_pStmt->m_sStringParam = sVal;
12408 	} else
12409 	{
12410 		m_pParseError->SetSprintf ( "unknown option '%s' (or bad argument type)", sOpt.cstr() );
12411 		return false;
12412 	}
12413 	return true;
12414 }
12415 
12416 
AliasLastItem(SqlNode_t * pAlias)12417 void SqlParser_c::AliasLastItem ( SqlNode_t * pAlias )
12418 {
12419 	if ( pAlias )
12420 	{
12421 		CSphQueryItem & tItem = m_pQuery->m_dItems.Last();
12422 		tItem.m_sAlias.SetBinary ( m_pBuf + pAlias->m_iStart, pAlias->m_iEnd - pAlias->m_iStart );
12423 		tItem.m_sAlias.ToLower();
12424 		SetSelect ( pAlias );
12425 	}
12426 }
12427 
AutoAlias(CSphQueryItem & tItem,SqlNode_t * pStart,SqlNode_t * pEnd)12428 void SqlParser_c::AutoAlias ( CSphQueryItem & tItem, SqlNode_t * pStart, SqlNode_t * pEnd )
12429 {
12430 	if ( pStart && pEnd )
12431 	{
12432 		tItem.m_sAlias.SetBinary ( m_pBuf + pStart->m_iStart, pEnd->m_iEnd - pStart->m_iStart );
12433 		sphColumnToLowercase ( const_cast<char *>( tItem.m_sAlias.cstr() ) );
12434 	} else
12435 	{
12436 		tItem.m_sAlias = tItem.m_sExpr;
12437 	}
12438 	SetSelect ( pStart, pEnd );
12439 }
12440 
AddItem(SqlNode_t * pExpr,ESphAggrFunc eAggrFunc,SqlNode_t * pStart,SqlNode_t * pEnd)12441 void SqlParser_c::AddItem ( SqlNode_t * pExpr, ESphAggrFunc eAggrFunc, SqlNode_t * pStart, SqlNode_t * pEnd )
12442 {
12443 	CSphQueryItem & tItem = m_pQuery->m_dItems.Add();
12444 	tItem.m_sExpr.SetBinary ( m_pBuf + pExpr->m_iStart, pExpr->m_iEnd - pExpr->m_iStart );
12445 	sphColumnToLowercase ( const_cast<char *>( tItem.m_sExpr.cstr() ) );
12446 	tItem.m_eAggrFunc = eAggrFunc;
12447 	AutoAlias ( tItem, pStart?pStart:pExpr, pEnd?pEnd:pExpr );
12448 }
12449 
AddItem(const char * pToken,SqlNode_t * pStart,SqlNode_t * pEnd)12450 bool SqlParser_c::AddItem ( const char * pToken, SqlNode_t * pStart, SqlNode_t * pEnd )
12451 {
12452 	CSphQueryItem & tItem = m_pQuery->m_dItems.Add();
12453 	tItem.m_sExpr = pToken;
12454 	tItem.m_eAggrFunc = SPH_AGGR_NONE;
12455 	sphColumnToLowercase ( const_cast<char *>( tItem.m_sExpr.cstr() ) );
12456 	AutoAlias ( tItem, pStart, pEnd );
12457 	return SetNewSyntax();
12458 }
12459 
AddCount()12460 bool SqlParser_c::AddCount ()
12461 {
12462 	CSphQueryItem & tItem = m_pQuery->m_dItems.Add();
12463 	tItem.m_sExpr = tItem.m_sAlias = "count(*)";
12464 	tItem.m_eAggrFunc = SPH_AGGR_NONE;
12465 	return SetNewSyntax();
12466 }
12467 
AddGroupBy(const SqlNode_t & tGroupBy)12468 void SqlParser_c::AddGroupBy ( const SqlNode_t & tGroupBy )
12469 {
12470 	if ( m_pQuery->m_sGroupBy.IsEmpty() )
12471 	{
12472 		m_pQuery->m_eGroupFunc = SPH_GROUPBY_ATTR;
12473 		m_pQuery->m_sGroupBy.SetBinary ( m_pBuf + tGroupBy.m_iStart, tGroupBy.m_iEnd - tGroupBy.m_iStart );
12474 		sphColumnToLowercase ( const_cast<char *>( m_pQuery->m_sGroupBy.cstr() ) );
12475 	} else
12476 	{
12477 		m_pQuery->m_eGroupFunc = SPH_GROUPBY_MULTIPLE;
12478 		CSphString sTmp;
12479 		sTmp.SetBinary ( m_pBuf + tGroupBy.m_iStart, tGroupBy.m_iEnd - tGroupBy.m_iStart );
12480 		sphColumnToLowercase ( const_cast<char *>( sTmp.cstr() ) );
12481 		m_pQuery->m_sGroupBy.SetSprintf ( "%s, %s", m_pQuery->m_sGroupBy.cstr(), sTmp.cstr() );
12482 	}
12483 }
12484 
SetGroupbyLimit(int iLimit)12485 void SqlParser_c::SetGroupbyLimit ( int iLimit )
12486 {
12487 	m_pQuery->m_iGroupbyLimit = iLimit;
12488 }
12489 
AddDistinct(SqlNode_t * pNewExpr,SqlNode_t * pStart,SqlNode_t * pEnd)12490 bool SqlParser_c::AddDistinct ( SqlNode_t * pNewExpr, SqlNode_t * pStart, SqlNode_t * pEnd )
12491 {
12492 	if ( !m_pQuery->m_sGroupDistinct.IsEmpty() )
12493 	{
12494 		yyerror ( this, "too many COUNT(DISTINCT) clauses" );
12495 		return false;
12496 	}
12497 
12498 	ToString ( m_pQuery->m_sGroupDistinct, *pNewExpr );
12499 	return AddItem ( "@distinct", pStart, pEnd );
12500 }
12501 
AddSchemaItem(YYSTYPE * pNode)12502 bool SqlParser_c::AddSchemaItem ( YYSTYPE * pNode )
12503 {
12504 	assert ( m_pStmt );
12505 	CSphString sItem;
12506 	sItem.SetBinary ( m_pBuf + pNode->m_iStart, pNode->m_iEnd - pNode->m_iStart );
12507 	return m_pStmt->AddSchemaItem ( sItem.cstr() );
12508 }
12509 
SetMatch(const YYSTYPE & tValue)12510 bool SqlParser_c::SetMatch ( const YYSTYPE& tValue )
12511 {
12512 	if ( m_bGotQuery )
12513 	{
12514 		yyerror ( this, "too many MATCH() clauses" );
12515 		return false;
12516 	};
12517 
12518 	ToStringUnescape ( m_pQuery->m_sQuery, tValue );
12519 	m_pQuery->m_sRawQuery = m_pQuery->m_sQuery;
12520 	return m_bGotQuery = true;
12521 }
12522 
AddConst(int iList,const YYSTYPE & tValue)12523 void SqlParser_c::AddConst ( int iList, const YYSTYPE& tValue )
12524 {
12525 	CSphVector<CSphNamedInt> & dVec = GetNamedVec ( iList );
12526 
12527 	dVec.Add();
12528 	ToString ( dVec.Last().m_sName, tValue ).ToLower();
12529 	dVec.Last().m_iValue = (int) tValue.m_iValue;
12530 }
12531 
SetStatement(const YYSTYPE & tName,SqlSet_e eSet)12532 void SqlParser_c::SetStatement ( const YYSTYPE & tName, SqlSet_e eSet )
12533 {
12534 	m_pStmt->m_eStmt = STMT_SET;
12535 	m_pStmt->m_eSet = eSet;
12536 	ToString ( m_pStmt->m_sSetName, tName );
12537 }
12538 
GenericStatement(SqlNode_t * pNode,SqlStmt_e iStmt)12539 void SqlParser_c::GenericStatement ( SqlNode_t * pNode, SqlStmt_e iStmt )
12540 {
12541 	m_pStmt->m_eStmt = iStmt;
12542 	m_pStmt->m_iListStart = pNode->m_iStart;
12543 	m_pStmt->m_iListEnd = pNode->m_iEnd;
12544 	ToString ( m_pStmt->m_sIndex, *pNode );
12545 }
12546 
UpdateStatement(SqlNode_t * pNode)12547 bool SqlParser_c::UpdateStatement ( SqlNode_t * pNode )
12548 {
12549 	GenericStatement ( pNode, STMT_UPDATE );
12550 	m_pStmt->m_tUpdate.m_dRowOffset.Add ( 0 );
12551 	return true;
12552 }
12553 
DeleteStatement(SqlNode_t * pNode)12554 bool SqlParser_c::DeleteStatement ( SqlNode_t * pNode )
12555 {
12556 	GenericStatement ( pNode, STMT_DELETE );
12557 	return true;
12558 }
12559 
AddUpdatedAttr(const SqlNode_t & tName,ESphAttr eType) const12560 void SqlParser_c::AddUpdatedAttr ( const SqlNode_t & tName, ESphAttr eType ) const
12561 {
12562 	CSphAttrUpdate & tUpd = m_pStmt->m_tUpdate;
12563 	CSphString sAttr;
12564 	tUpd.m_dAttrs.Add ( ToString ( sAttr, tName ).ToLower().Leak() );
12565 	tUpd.m_dTypes.Add ( eType ); // sorry, ints only for now, riding on legacy shit!
12566 }
12567 
12568 
UpdateMVAAttr(const SqlNode_t & tName,const SqlNode_t & dValues)12569 void SqlParser_c::UpdateMVAAttr ( const SqlNode_t & tName, const SqlNode_t & dValues )
12570 {
12571 	CSphAttrUpdate & tUpd = m_pStmt->m_tUpdate;
12572 	ESphAttr eType = SPH_ATTR_UINT32SET;
12573 
12574 	if ( dValues.m_pValues.Ptr() && dValues.m_pValues->GetLength()>0 )
12575 	{
12576 		// got MVA values, let's process them
12577 		dValues.m_pValues->Uniq(); // don't need dupes within MVA
12578 		tUpd.m_dPool.Add ( dValues.m_pValues->GetLength()*2 );
12579 		SphAttr_t * pVal = dValues.m_pValues.Ptr()->Begin();
12580 		SphAttr_t * pValMax = pVal + dValues.m_pValues->GetLength();
12581 		for ( ;pVal<pValMax; pVal++ )
12582 		{
12583 			SphAttr_t uVal = *pVal;
12584 			if ( uVal>UINT_MAX )
12585 			{
12586 				eType = SPH_ATTR_INT64SET;
12587 			}
12588 			tUpd.m_dPool.Add ( (DWORD)uVal );
12589 			tUpd.m_dPool.Add ( (DWORD)( uVal>>32 ) );
12590 		}
12591 	} else
12592 	{
12593 		// no values, means we should delete the attribute
12594 		// we signal that to the update code by putting a single zero
12595 		// to the values pool (meaning a zero-length MVA values list)
12596 		tUpd.m_dPool.Add ( 0 );
12597 	}
12598 
12599 	AddUpdatedAttr ( tName, eType );
12600 }
12601 
AddFilter(const SqlNode_t & tCol,ESphFilter eType)12602 CSphFilterSettings * SqlParser_c::AddFilter ( const SqlNode_t & tCol, ESphFilter eType )
12603 {
12604 	CSphString sCol;
12605 	ToString ( sCol, tCol ); // do NOT lowercase just yet, might have to retain case for JSON cols
12606 
12607 	CSphFilterSettings * pFilter = &m_pQuery->m_dFilters.Add();
12608 	pFilter->m_sAttrName = ( !strcasecmp ( sCol.cstr(), "id" ) ) ? "@id" : sCol;
12609 	pFilter->m_eType = eType;
12610 	sphColumnToLowercase ( const_cast<char *>( pFilter->m_sAttrName.cstr() ) );
12611 	return pFilter;
12612 }
12613 
AddFloatRangeFilter(const SqlNode_t & sAttr,float fMin,float fMax,bool bHasEqual,bool bExclude)12614 bool SqlParser_c::AddFloatRangeFilter ( const SqlNode_t & sAttr, float fMin, float fMax, bool bHasEqual, bool bExclude )
12615 {
12616 	CSphFilterSettings * pFilter = AddFilter ( sAttr, SPH_FILTER_FLOATRANGE );
12617 	if ( !pFilter )
12618 		return false;
12619 	pFilter->m_fMinValue = fMin;
12620 	pFilter->m_fMaxValue = fMax;
12621 	pFilter->m_bHasEqual = bHasEqual;
12622 	pFilter->m_bExclude = bExclude;
12623 	return true;
12624 }
12625 
AddIntRangeFilter(const SqlNode_t & sAttr,int64_t iMin,int64_t iMax)12626 bool SqlParser_c::AddIntRangeFilter ( const SqlNode_t & sAttr, int64_t iMin, int64_t iMax )
12627 {
12628 	CSphFilterSettings * pFilter = AddFilter ( sAttr, SPH_FILTER_RANGE );
12629 	if ( !pFilter )
12630 		return false;
12631 	pFilter->m_iMinValue = iMin;
12632 	pFilter->m_iMaxValue = iMax;
12633 	return true;
12634 }
12635 
AddIntFilterGreater(const SqlNode_t & tAttr,int64_t iVal,bool bHasEqual)12636 bool SqlParser_c::AddIntFilterGreater ( const SqlNode_t & tAttr, int64_t iVal, bool bHasEqual )
12637 {
12638 	CSphFilterSettings * pFilter = AddFilter ( tAttr, SPH_FILTER_RANGE );
12639 	if ( !pFilter )
12640 		return false;
12641 	bool bId = ( pFilter->m_sAttrName=="@id" ) || ( pFilter->m_sAttrName=="id" );
12642 	pFilter->m_iMaxValue = bId ? (SphAttr_t)ULLONG_MAX : LLONG_MAX;
12643 	if ( iVal==LLONG_MAX )
12644 	{
12645 		pFilter->m_iMinValue = iVal;
12646 		pFilter->m_bHasEqual = bHasEqual;
12647 	} else
12648 	{
12649 		pFilter->m_iMinValue = bHasEqual ? iVal : iVal+1;
12650 		pFilter->m_bHasEqual = true;
12651 	}
12652 	return true;
12653 }
12654 
AddIntFilterLesser(const SqlNode_t & tAttr,int64_t iVal,bool bHasEqual)12655 bool SqlParser_c::AddIntFilterLesser ( const SqlNode_t & tAttr, int64_t iVal, bool bHasEqual )
12656 {
12657 	CSphFilterSettings * pFilter = AddFilter ( tAttr, SPH_FILTER_RANGE );
12658 	if ( !pFilter )
12659 		return false;
12660 	bool bId = ( pFilter->m_sAttrName=="@id" ) || ( pFilter->m_sAttrName=="id" );
12661 	pFilter->m_iMinValue = bId ? 0 : LLONG_MIN;
12662 	if ( iVal==LLONG_MIN )
12663 	{
12664 		pFilter->m_iMaxValue = iVal;
12665 		pFilter->m_bHasEqual = bHasEqual;
12666 	} else
12667 	{
12668 		pFilter->m_iMaxValue = bHasEqual ? iVal : iVal-1;
12669 		pFilter->m_bHasEqual = true;
12670 	}
12671 	return true;
12672 }
12673 
AddUservarFilter(const SqlNode_t & tCol,const SqlNode_t & tVar,bool bExclude)12674 bool SqlParser_c::AddUservarFilter ( const SqlNode_t & tCol, const SqlNode_t & tVar, bool bExclude )
12675 {
12676 	CSphFilterSettings * pFilter = AddFilter ( tCol, SPH_FILTER_USERVAR );
12677 	if ( !pFilter )
12678 		return false;
12679 	CSphString & sUserVar = pFilter->m_dStrings.Add();
12680 	ToString ( sUserVar, tVar ).ToLower();
12681 	pFilter->m_bExclude = bExclude;
12682 	return true;
12683 }
12684 
12685 
AddStringFilter(const SqlNode_t & tCol,const SqlNode_t & tVal,bool bExclude)12686 bool SqlParser_c::AddStringFilter ( const SqlNode_t & tCol, const SqlNode_t & tVal, bool bExclude )
12687 {
12688 	CSphFilterSettings * pFilter = AddFilter ( tCol, SPH_FILTER_STRING );
12689 	if ( !pFilter )
12690 		return false;
12691 	CSphString & sFilterString = pFilter->m_dStrings.Add();
12692 	ToStringUnescape ( sFilterString, tVal );
12693 	pFilter->m_bExclude = bExclude;
12694 	return true;
12695 }
12696 
12697 
AddStringListFilter(const SqlNode_t & tCol,SqlNode_t & tVal,bool bExclude)12698 bool SqlParser_c::AddStringListFilter ( const SqlNode_t & tCol, SqlNode_t & tVal, bool bExclude )
12699 {
12700 	CSphFilterSettings * pFilter = AddFilter ( tCol, SPH_FILTER_STRING_LIST );
12701 	if ( !pFilter || !tVal.m_pValues.Ptr() )
12702 		return false;
12703 
12704 	pFilter->m_dStrings.Resize ( tVal.m_pValues->GetLength() );
12705 	ARRAY_FOREACH ( i, ( *tVal.m_pValues.Ptr() ) )
12706 	{
12707 		uint64_t uVal = ( *tVal.m_pValues.Ptr() )[i];
12708 		int iOff = ( uVal>>32 );
12709 		int iLen = ( uVal & 0xffffffff );
12710 		pFilter->m_dStrings[i].SetBinary ( m_pBuf + iOff, iLen );
12711 	}
12712 	tVal.m_pValues = NULL;
12713 	pFilter->m_bExclude = bExclude;
12714 	return true;
12715 }
12716 
12717 
AddNullFilter(const SqlNode_t & tCol,bool bEqualsNull)12718 bool SqlParser_c::AddNullFilter ( const SqlNode_t & tCol, bool bEqualsNull )
12719 {
12720 	CSphFilterSettings * pFilter = AddFilter ( tCol, SPH_FILTER_NULL );
12721 	if ( !pFilter )
12722 		return false;
12723 	pFilter->m_bHasEqual = bEqualsNull;
12724 	return true;
12725 }
12726 
AddHaving()12727 void SqlParser_c::AddHaving ()
12728 {
12729 	assert ( m_pQuery->m_dFilters.GetLength() );
12730 	m_pQuery->m_tHaving = m_pQuery->m_dFilters.Pop();
12731 }
12732 
12733 
IsGoodSyntax()12734 bool SqlParser_c::IsGoodSyntax ()
12735 {
12736 	if ( ( m_uSyntaxFlags & 3 )!=3 )
12737 		return true;
12738 	yyerror ( this, "Mixing the old-fashion internal vars (@id, @count, @weight) with new acronyms like count(*), weight() is prohibited" );
12739 	return false;
12740 }
12741 
12742 
AllocNamedVec()12743 int SqlParser_c::AllocNamedVec ()
12744 {
12745 	// we only allow one such vector at a time, right now
12746 	assert ( !m_bNamedVecBusy );
12747 	m_bNamedVecBusy = true;
12748 	m_dNamedVec.Resize ( 0 );
12749 	return 0;
12750 }
12751 
12752 #ifndef NDEBUG
GetNamedVec(int iIndex)12753 CSphVector<CSphNamedInt> & SqlParser_c::GetNamedVec ( int iIndex )
12754 #else
12755 CSphVector<CSphNamedInt> & SqlParser_c::GetNamedVec ( int )
12756 #endif
12757 {
12758 	assert ( m_bNamedVecBusy && iIndex==0 );
12759 	return m_dNamedVec;
12760 }
12761 
12762 #ifndef NDEBUG
FreeNamedVec(int iIndex)12763 void SqlParser_c::FreeNamedVec ( int iIndex )
12764 #else
12765 void SqlParser_c::FreeNamedVec ( int )
12766 #endif
12767 {
12768 	assert ( m_bNamedVecBusy && iIndex==0 );
12769 	m_bNamedVecBusy = false;
12770 	m_dNamedVec.Resize ( 0 );
12771 }
12772 
ParseSqlQuery(const char * sQuery,int iLen,CSphVector<SqlStmt_t> & dStmt,CSphString & sError,ESphCollation eCollation)12773 bool ParseSqlQuery ( const char * sQuery, int iLen, CSphVector<SqlStmt_t> & dStmt, CSphString & sError, ESphCollation eCollation )
12774 {
12775 	assert ( sQuery );
12776 
12777 	SqlParser_c tParser ( dStmt, eCollation );
12778 	tParser.m_pBuf = sQuery;
12779 	tParser.m_pLastTokenStart = NULL;
12780 	tParser.m_pParseError = &sError;
12781 	tParser.m_eCollation = eCollation;
12782 
12783 	char * sEnd = const_cast<char *>( sQuery ) + iLen;
12784 	sEnd[0] = 0; // prepare for yy_scan_buffer
12785 	sEnd[1] = 0; // this is ok because string allocates a small gap
12786 
12787 	yylex_init ( &tParser.m_pScanner );
12788 	YY_BUFFER_STATE tLexerBuffer = yy_scan_buffer ( const_cast<char *>( sQuery ), iLen+2, tParser.m_pScanner );
12789 	if ( !tLexerBuffer )
12790 	{
12791 		sError = "internal error: yy_scan_buffer() failed";
12792 		return false;
12793 	}
12794 
12795 	int iRes = yyparse ( &tParser );
12796 	yy_delete_buffer ( tLexerBuffer, tParser.m_pScanner );
12797 	yylex_destroy ( tParser.m_pScanner );
12798 
12799 	dStmt.Pop(); // last query is always dummy
12800 
12801 	ARRAY_FOREACH ( i, dStmt )
12802 	{
12803 		// select expressions will be reparsed again, by an expression parser,
12804 		// when we have an index to actually bind variables, and create a tree
12805 		//
12806 		// so at SQL parse stage, we only do quick validation, and at this point,
12807 		// we just store the select list for later use by the expression parser
12808 		CSphQuery & tQuery = dStmt[i].m_tQuery;
12809 		if ( tQuery.m_iSQLSelectStart>=0 )
12810 		{
12811 			if ( tQuery.m_iSQLSelectStart-1>=0 && tParser.m_pBuf[tQuery.m_iSQLSelectStart-1]=='`' )
12812 				tQuery.m_iSQLSelectStart--;
12813 			if ( tQuery.m_iSQLSelectEnd<iLen && tParser.m_pBuf[tQuery.m_iSQLSelectEnd]=='`' )
12814 				tQuery.m_iSQLSelectEnd++;
12815 
12816 			tQuery.m_sSelect.SetBinary ( tParser.m_pBuf + tQuery.m_iSQLSelectStart,
12817 				tQuery.m_iSQLSelectEnd - tQuery.m_iSQLSelectStart );
12818 		}
12819 
12820 		// validate tablefuncs
12821 		// tablefuncs are searchd-level builtins rather than common expression-level functions
12822 		// so validation happens here, expression parser does not know tablefuncs (ignorance is bliss)
12823 		if ( dStmt[i].m_eStmt==STMT_SELECT && !dStmt[i].m_sTableFunc.IsEmpty() )
12824 		{
12825 			CSphString & sFunc = dStmt[i].m_sTableFunc;
12826 			sFunc.ToUpper();
12827 
12828 			ISphTableFunc * pFunc = NULL;
12829 			if ( sFunc=="REMOVE_REPEATS" )
12830 				pFunc = new CSphTableFuncRemoveRepeats();
12831 
12832 			if ( !pFunc )
12833 			{
12834 				sError.SetSprintf ( "unknown table function %s()", sFunc.cstr() );
12835 				return false;
12836 			}
12837 			if ( !pFunc->ValidateArgs ( dStmt[i].m_dTableFuncArgs, tQuery, sError ) )
12838 			{
12839 				SafeDelete ( pFunc );
12840 				return false;
12841 			}
12842 			tQuery.m_pTableFunc = pFunc;
12843 		}
12844 
12845 		// validate filters
12846 		ARRAY_FOREACH ( i, tQuery.m_dFilters )
12847 		{
12848 			const CSphString & sCol = tQuery.m_dFilters[i].m_sAttrName;
12849 			if ( !strcasecmp ( sCol.cstr(), "@count" ) || !strcasecmp ( sCol.cstr(), "count(*)" ) )
12850 			{
12851 				sError.SetSprintf ( "sphinxql: Aggregates in 'where' clause prohibited, use 'having'" );
12852 				return false;
12853 			}
12854 		}
12855 	}
12856 
12857 	if ( iRes!=0 || !dStmt.GetLength() )
12858 		return false;
12859 
12860 	if ( tParser.IsDeprecatedSyntax() )
12861 	{
12862 		sError = "Using the old-fashion @variables (@count, @weight, etc.) is deprecated";
12863 		return false;
12864 	}
12865 
12866 	// facets
12867 	ARRAY_FOREACH ( i, dStmt )
12868 	{
12869 		CSphQuery & tQuery = dStmt[i].m_tQuery;
12870 		if ( dStmt[i].m_eStmt==STMT_SELECT )
12871 		{
12872 			while ( i+1<dStmt.GetLength() )
12873 			{
12874 				SqlStmt_t & tStmt = dStmt[i+1];
12875 				if ( tStmt.m_eStmt!=STMT_FACET )
12876 					break;
12877 
12878 				tStmt.m_tQuery.m_bFacet = true;
12879 
12880 				tStmt.m_eStmt = STMT_SELECT;
12881 				tStmt.m_tQuery.m_sIndexes = tQuery.m_sIndexes;
12882 				tStmt.m_tQuery.m_sSelect = tStmt.m_tQuery.m_sFacetBy;
12883 				tStmt.m_tQuery.m_sQuery = tQuery.m_sQuery;
12884 				tStmt.m_tQuery.m_iMaxMatches = tQuery.m_iMaxMatches;
12885 
12886 				// append top-level expressions to a facet schema (for filtering)
12887 				ARRAY_FOREACH ( k, tQuery.m_dItems )
12888 					if ( tQuery.m_dItems[k].m_sAlias!=tQuery.m_dItems[k].m_sExpr )
12889 						tStmt.m_tQuery.m_dItems.Add ( tQuery.m_dItems[k] );
12890 
12891 				// append filters
12892 				ARRAY_FOREACH ( k, tQuery.m_dFilters )
12893 					tStmt.m_tQuery.m_dFilters.Add ( tQuery.m_dFilters[k] );
12894 
12895 				i++;
12896 			}
12897 		}
12898 	}
12899 
12900 	return true;
12901 }
12902 
12903 
12904 /////////////////////////////////////////////////////////////////////////////
12905 
sphGetPassageBoundary(const CSphString & sPassageBoundaryMode)12906 ESphSpz sphGetPassageBoundary ( const CSphString & sPassageBoundaryMode )
12907 {
12908 	if ( sPassageBoundaryMode.IsEmpty() )
12909 		return SPH_SPZ_NONE;
12910 
12911 	ESphSpz eSPZ = SPH_SPZ_NONE;
12912 	if ( sPassageBoundaryMode=="sentence" )
12913 		eSPZ = SPH_SPZ_SENTENCE;
12914 	else if ( sPassageBoundaryMode=="paragraph" )
12915 		eSPZ = SPH_SPZ_PARAGRAPH;
12916 	else if ( sPassageBoundaryMode=="zone" )
12917 		eSPZ = SPH_SPZ_ZONE;
12918 
12919 	return eSPZ;
12920 }
12921 
sphCheckOptionsSPZ(const ExcerptQuery_t & q,const CSphString & sPassageBoundaryMode,CSphString & sError)12922 bool sphCheckOptionsSPZ ( const ExcerptQuery_t & q, const CSphString & sPassageBoundaryMode, CSphString & sError )
12923 {
12924 	if ( q.m_ePassageSPZ )
12925 	{
12926 		if ( q.m_iAround==0 )
12927 		{
12928 			sError.SetSprintf ( "invalid combination of passage_boundary=%s and around=%d", sPassageBoundaryMode.cstr(), q.m_iAround );
12929 			return false;
12930 		} else if ( q.m_bUseBoundaries )
12931 		{
12932 			sError.SetSprintf ( "invalid combination of passage_boundary=%s and use_boundaries", sPassageBoundaryMode.cstr() );
12933 			return false;
12934 		}
12935 	}
12936 
12937 	if ( q.m_bEmitZones )
12938 	{
12939 		if ( q.m_ePassageSPZ!=SPH_SPZ_ZONE )
12940 		{
12941 			sError.SetSprintf ( "invalid combination of passage_boundary=%s and emit_zones", sPassageBoundaryMode.cstr() );
12942 			return false;
12943 		}
12944 		if ( !( q.m_sStripMode=="strip" || q.m_sStripMode=="index" ) )
12945 		{
12946 			sError.SetSprintf ( "invalid combination of strip=%s and emit_zones", q.m_sStripMode.cstr() );
12947 			return false;
12948 		}
12949 	}
12950 
12951 	return true;
12952 }
12953 
12954 /////////////////////////////////////////////////////////////////////////////
12955 // EXCERPTS HANDLER
12956 /////////////////////////////////////////////////////////////////////////////
12957 
12958 enum eExcerpt_Flags
12959 {
12960 	EXCERPT_FLAG_REMOVESPACES		= 1,
12961 	EXCERPT_FLAG_EXACTPHRASE		= 2,
12962 	EXCERPT_FLAG_SINGLEPASSAGE		= 4,
12963 	EXCERPT_FLAG_USEBOUNDARIES		= 8,
12964 	EXCERPT_FLAG_WEIGHTORDER		= 16,
12965 	EXCERPT_FLAG_QUERY				= 32,
12966 	EXCERPT_FLAG_FORCE_ALL_WORDS	= 64,
12967 	EXCERPT_FLAG_LOAD_FILES			= 128,
12968 	EXCERPT_FLAG_ALLOW_EMPTY		= 256,
12969 	EXCERPT_FLAG_EMIT_ZONES			= 512,
12970 	EXCERPT_FLAG_FILES_SCATTERED	= 1024
12971 };
12972 
12973 enum
12974 {
12975 	PROCESSED_ITEM					= -2,
12976 	EOF_ITEM						= -1
12977 };
12978 struct SnippetWorker_t
12979 {
12980 	int64_t						m_iTotal;
12981 	int							m_iHead;
12982 
SnippetWorker_tSnippetWorker_t12983 	SnippetWorker_t()
12984 		: m_iTotal ( 0 )
12985 		, m_iHead ( EOF_ITEM )
12986 	{}
12987 };
12988 
12989 struct SnippetsRemote_t : ISphNoncopyable
12990 {
12991 	CSphVector<AgentConn_t>			m_dAgents;
12992 	CSphVector<SnippetWorker_t>		m_dWorkers;
12993 	CSphVector<ExcerptQuery_t> &	m_dQueries;
12994 	int								m_iAgentConnectTimeout;
12995 	int								m_iAgentQueryTimeout;
12996 
SnippetsRemote_tSnippetsRemote_t12997 	explicit SnippetsRemote_t ( CSphVector<ExcerptQuery_t> & dQueries )
12998 		: m_dQueries ( dQueries )
12999 		, m_iAgentConnectTimeout ( 0 )
13000 		, m_iAgentQueryTimeout ( 0 )
13001 	{}
13002 };
13003 
13004 struct SnippetThread_t
13005 {
13006 	SphThread_t					m_tThd;
13007 	CSphMutex *					m_pLock;
13008 	int							m_iQueries;
13009 	ExcerptQuery_t *			m_pQueries;
13010 	volatile int *				m_pCurQuery;
13011 	CSphIndex *					m_pIndex;
13012 	CrashQuery_t				m_tCrashQuery;
13013 
SnippetThread_tSnippetThread_t13014 	SnippetThread_t()
13015 		: m_pLock ( NULL )
13016 		, m_iQueries ( 0 )
13017 		, m_pQueries ( NULL )
13018 		, m_pCurQuery ( NULL )
13019 		, m_pIndex ( NULL )
13020 	{}
13021 };
13022 
13023 
13024 struct SnippetRequestBuilder_t : public IRequestBuilder_t
13025 {
SnippetRequestBuilder_tSnippetRequestBuilder_t13026 	explicit SnippetRequestBuilder_t ( const SnippetsRemote_t * pWorker )
13027 		: m_pWorker ( pWorker )
13028 		, m_iNumDocs ( -1 )
13029 		, m_iReqLen ( -1 )
13030 		, m_bScattered ( false )
13031 		, m_iWorker ( 0 )
13032 	{
13033 		m_tWorkerMutex.Init();
13034 	}
~SnippetRequestBuilder_tSnippetRequestBuilder_t13035 	~SnippetRequestBuilder_t()
13036 	{
13037 		m_tWorkerMutex.Done();
13038 	}
13039 	virtual void BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const;
13040 
13041 private:
13042 	const SnippetsRemote_t * m_pWorker;
13043 	mutable int m_iNumDocs;		///< optimize numdocs/length calculation in scattered case
13044 	mutable int m_iReqLen;
13045 	mutable bool m_bScattered;
13046 	mutable int m_iWorker;
13047 	mutable CSphMutex m_tWorkerMutex;
13048 };
13049 
13050 
13051 struct SnippetReplyParser_t : public IReplyParser_t
13052 {
SnippetReplyParser_tSnippetReplyParser_t13053 	explicit SnippetReplyParser_t ( SnippetsRemote_t * pWorker )
13054 		: m_pWorker ( pWorker )
13055 	{}
13056 
13057 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const;
13058 
13059 private:
13060 	const SnippetsRemote_t * m_pWorker;
13061 };
13062 
13063 
BuildRequest(AgentConn_t & tAgent,NetOutputBuffer_c & tOut) const13064 void SnippetRequestBuilder_t::BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const
13065 {
13066 	// it sends either all queries to each agent or sequence of queries to current agent
13067 	m_tWorkerMutex.Lock();
13068 	int iWorker = m_iWorker++;
13069 	m_tWorkerMutex.Unlock();
13070 
13071 	const CSphVector<ExcerptQuery_t> & dQueries = m_pWorker->m_dQueries;
13072 	const ExcerptQuery_t & q = dQueries[0];
13073 	const SnippetWorker_t & tWorker = m_pWorker->m_dWorkers[iWorker];
13074 	tAgent.m_iWorkerTag = iWorker;
13075 
13076 	const char* sIndex = tAgent.m_sIndexes.cstr();
13077 
13078 	if ( m_iNumDocs < 0 )
13079 		m_bScattered = ( q.m_iLoadFiles & 2 )!=0;
13080 
13081 	if ( !m_bScattered || ( m_bScattered && m_iNumDocs<0 ) )
13082 	{
13083 		m_iReqLen = 60 // 15 ints/dwords - params, strlens, etc.
13084 		+ strlen ( sIndex )
13085 		+ q.m_sWords.Length()
13086 		+ q.m_sBeforeMatch.Length()
13087 		+ q.m_sAfterMatch.Length()
13088 		+ q.m_sChunkSeparator.Length()
13089 		+ q.m_sStripMode.Length()
13090 		+ q.m_sRawPassageBoundary.Length();
13091 
13092 		m_iNumDocs = 0;
13093 		for ( int iDoc = tWorker.m_iHead; iDoc!=EOF_ITEM; iDoc=dQueries[iDoc].m_iNext )
13094 		{
13095 			++m_iNumDocs;
13096 			m_iReqLen += 4 + dQueries[iDoc].m_sSource.Length();
13097 		}
13098 	}
13099 
13100 	tOut.SendWord ( SEARCHD_COMMAND_EXCERPT );
13101 	tOut.SendWord ( VER_COMMAND_EXCERPT );
13102 
13103 	tOut.SendInt ( m_iReqLen );
13104 
13105 	tOut.SendInt ( 0 );
13106 
13107 	if ( m_bScattered )
13108 		tOut.SendInt ( q.m_iRawFlags & ~EXCERPT_FLAG_LOAD_FILES );
13109 	else
13110 		tOut.SendInt ( q.m_iRawFlags );
13111 
13112 	tOut.SendString ( sIndex );
13113 	tOut.SendString ( q.m_sWords.cstr() );
13114 	tOut.SendString ( q.m_sBeforeMatch.cstr() );
13115 	tOut.SendString ( q.m_sAfterMatch.cstr() );
13116 	tOut.SendString ( q.m_sChunkSeparator.cstr() );
13117 	tOut.SendInt ( q.m_iLimit );
13118 	tOut.SendInt ( q.m_iAround );
13119 
13120 	tOut.SendInt ( q.m_iLimitPassages );
13121 	tOut.SendInt ( q.m_iLimitWords );
13122 	tOut.SendInt ( q.m_iPassageId );
13123 	tOut.SendString ( q.m_sStripMode.cstr() );
13124 	tOut.SendString ( q.m_sRawPassageBoundary.cstr() );
13125 
13126 	tOut.SendInt ( m_iNumDocs );
13127 	for ( int iDoc = tWorker.m_iHead; iDoc!=EOF_ITEM; iDoc=dQueries[iDoc].m_iNext )
13128 		tOut.SendString ( dQueries[iDoc].m_sSource.cstr() );
13129 }
13130 
ParseReply(MemInputBuffer_c & tReq,AgentConn_t & tAgent) const13131 bool SnippetReplyParser_t::ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & tAgent ) const
13132 {
13133 	int iWorker = tAgent.m_iWorkerTag;
13134 	CSphVector<ExcerptQuery_t> & dQueries = m_pWorker->m_dQueries;
13135 	const SnippetWorker_t & tWorker = m_pWorker->m_dWorkers[iWorker];
13136 
13137 	int iDoc = tWorker.m_iHead;
13138 	bool bOk = true;
13139 	while ( iDoc!=EOF_ITEM )
13140 	{
13141 		if ( ( dQueries[iDoc].m_iLoadFiles&2 )!=0 ) // NOLINT
13142 		{
13143 			if ( !tReq.GetString ( dQueries[iDoc].m_dRes ) )
13144 			{
13145 				bOk = false;
13146 				dQueries[iDoc].m_dRes.Resize ( 0 );
13147 			} else
13148 				dQueries[iDoc].m_sError = "";
13149 
13150 			iDoc = dQueries[iDoc].m_iNext;
13151 			continue;
13152 		}
13153 		tReq.GetString ( dQueries[iDoc].m_dRes );
13154 		int iNextDoc = dQueries[iDoc].m_iNext;
13155 		dQueries[iDoc].m_iNext = PROCESSED_ITEM;
13156 		iDoc = iNextDoc;
13157 	}
13158 
13159 	return bOk;
13160 }
13161 
13162 
SnippetThreadFunc(void * pArg)13163 void SnippetThreadFunc ( void * pArg )
13164 {
13165 	SnippetThread_t * pDesc = (SnippetThread_t*) pArg;
13166 
13167 	SphCrashLogger_c::SetLastQuery ( pDesc->m_tCrashQuery );
13168 
13169 	SnippetContext_t tCtx;
13170 	tCtx.Setup ( pDesc->m_pIndex, *pDesc->m_pQueries, pDesc->m_pQueries->m_sError );
13171 
13172 	for ( ;; )
13173 	{
13174 		pDesc->m_pLock->Lock();
13175 		if ( *pDesc->m_pCurQuery==pDesc->m_iQueries )
13176 		{
13177 			pDesc->m_pLock->Unlock();
13178 			return;
13179 		}
13180 
13181 		ExcerptQuery_t * pQuery = pDesc->m_pQueries + (*pDesc->m_pCurQuery);
13182 		(*pDesc->m_pCurQuery)++;
13183 		bool bDone = ( *pDesc->m_pCurQuery==pDesc->m_iQueries );
13184 		pDesc->m_pLock->Unlock();
13185 
13186 		if ( pQuery->m_iNext!=PROCESSED_ITEM )
13187 			continue;
13188 
13189 		sphBuildExcerpt ( *pQuery, pDesc->m_pIndex, tCtx.m_tStripper.Ptr(), tCtx.m_tExtQuery, tCtx.m_eExtQuerySPZ,
13190 			pQuery->m_sWarning, pQuery->m_sError, tCtx.m_pDict, tCtx.m_tTokenizer.Ptr(), tCtx.m_pQueryTokenizer );
13191 
13192 		if ( bDone )
13193 			return;
13194 	}
13195 }
13196 
GetRawSnippetFlags(const ExcerptQuery_t & q)13197 int GetRawSnippetFlags ( const ExcerptQuery_t& q )
13198 {
13199 	int iRawFlags = 0;
13200 
13201 	iRawFlags |= q.m_bRemoveSpaces ? EXCERPT_FLAG_REMOVESPACES : 0;
13202 	iRawFlags |= q.m_bUseBoundaries ? EXCERPT_FLAG_USEBOUNDARIES : 0;
13203 	iRawFlags |= q.m_bWeightOrder ? EXCERPT_FLAG_WEIGHTORDER : 0;
13204 	iRawFlags |= q.m_bHighlightQuery ? EXCERPT_FLAG_QUERY : 0;
13205 	iRawFlags |= q.m_bForceAllWords ? EXCERPT_FLAG_FORCE_ALL_WORDS : 0;
13206 	iRawFlags |= q.m_iLimitPassages ? EXCERPT_FLAG_SINGLEPASSAGE : 0;
13207 	iRawFlags |= ( q.m_iLoadFiles & 1 ) ? EXCERPT_FLAG_LOAD_FILES : 0;
13208 	iRawFlags |= ( q.m_iLoadFiles & 2 ) ? EXCERPT_FLAG_FILES_SCATTERED : 0;
13209 	iRawFlags |= q.m_bAllowEmpty ? EXCERPT_FLAG_ALLOW_EMPTY : 0;
13210 	iRawFlags |= q.m_bEmitZones ? EXCERPT_FLAG_EMIT_ZONES : 0;
13211 
13212 	return iRawFlags;
13213 }
13214 
SnippetFormatErrorMessage(CSphString * pError,const CSphString & sQueryError)13215 bool SnippetFormatErrorMessage ( CSphString * pError, const CSphString & sQueryError )
13216 {
13217 	assert ( pError );
13218 	if ( sQueryError.IsEmpty() )
13219 		return false;
13220 
13221 	if ( pError->IsEmpty() )
13222 		pError->SetSprintf ( "%s", sQueryError.cstr() );
13223 	else
13224 		pError->SetSprintf ( "%s; %s", pError->cstr(), sQueryError.cstr() );
13225 
13226 	return true;
13227 }
13228 
MakeSnippets(CSphString sIndex,CSphVector<ExcerptQuery_t> & dQueries,CSphString & sError,ThdDesc_t * pThd)13229 bool MakeSnippets ( CSphString sIndex, CSphVector<ExcerptQuery_t> & dQueries, CSphString & sError, ThdDesc_t * pThd )
13230 {
13231 	SnippetsRemote_t dRemoteSnippets ( dQueries );
13232 	CSphVector<CSphString> dDistLocal;
13233 	ExcerptQuery_t & q = dQueries[0];
13234 
13235 	g_tDistLock.Lock();
13236 	DistributedIndex_t * pDist = g_hDistIndexes ( sIndex );
13237 	bool bRemote = ( pDist!=NULL );
13238 
13239 	// hack! load_files && load_files_scattered is the 'final' call. It will report the absent files as errors.
13240 	// simple load_files_scattered without load_files just omits the absent files (returns empty strings).
13241 	bool bScattered = ( q.m_iLoadFiles & 2 )!=0;
13242 	bool bSkipAbsentFiles = !( q.m_iLoadFiles & 1 );
13243 
13244 	if ( bRemote )
13245 	{
13246 		dRemoteSnippets.m_iAgentConnectTimeout = pDist->m_iAgentConnectTimeout;
13247 		dRemoteSnippets.m_iAgentQueryTimeout = pDist->m_iAgentQueryTimeout;
13248 		dDistLocal = pDist->m_dLocal;
13249 		dRemoteSnippets.m_dAgents.Resize ( pDist->m_dAgents.GetLength() );
13250 		ARRAY_FOREACH ( i, pDist->m_dAgents )
13251 			dRemoteSnippets.m_dAgents[i].TakeTraits ( *pDist->m_dAgents[i].GetRRAgent() );
13252 	}
13253 	g_tDistLock.Unlock();
13254 
13255 	if ( bRemote )
13256 	{
13257 		if ( dDistLocal.GetLength()!=1 )
13258 		{
13259 			sError.SetSprintf ( "%s", "The distributed index for snippets must have exactly one local agent" );
13260 			return false;
13261 		}
13262 
13263 		if ( !q.m_iLoadFiles )
13264 		{
13265 			sError.SetSprintf ( "%s", "The distributed index for snippets available only when using external files" );
13266 			return false;
13267 		}
13268 
13269 		if ( g_iDistThreads<=1 && bScattered )
13270 		{
13271 			sError.SetSprintf ( "%s", "load_files_scattered works only together with dist_threads>1" );
13272 			return false;
13273 		}
13274 		sIndex = dDistLocal[0];
13275 
13276 		// no remote - roll back to simple local query
13277 		if ( dRemoteSnippets.m_dAgents.GetLength()==0 )
13278 			bRemote = false;
13279 	}
13280 
13281 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( sIndex );
13282 
13283 	if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
13284 	{
13285 		if ( pServed )
13286 			pServed->Unlock();
13287 		pServed = g_pTemplateIndexes->GetRlockedEntry ( sIndex );
13288 		if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
13289 		{
13290 			sError.SetSprintf ( "unknown local index '%s' in search request", sIndex.cstr() );
13291 			if ( pServed )
13292 				pServed->Unlock();
13293 			return false;
13294 		}
13295 	}
13296 
13297 	CSphIndex * pIndex = pServed->m_pIndex;
13298 
13299 	SnippetContext_t tCtx;
13300 	if ( !tCtx.Setup ( pIndex, q, sError ) ) // same path for single - threaded snippets, bail out here on error
13301 	{
13302 		sError.SetSprintf ( "%s", sError.cstr() );
13303 		pServed->Unlock();
13304 		return false;
13305 	}
13306 
13307 	///////////////////
13308 	// do highlighting
13309 	///////////////////
13310 
13311 	bool bOk = true;
13312 	int iAbsentHead = EOF_ITEM;
13313 	if ( g_iDistThreads<=1 || dQueries.GetLength()<2 )
13314 	{
13315 		// boring single threaded loop
13316 		ARRAY_FOREACH ( i, dQueries )
13317 		{
13318 			sphBuildExcerpt ( dQueries[i], pIndex, tCtx.m_tStripper.Ptr(), tCtx.m_tExtQuery, tCtx.m_eExtQuerySPZ,
13319 				dQueries[i].m_sWarning, dQueries[i].m_sError, tCtx.m_pDict, tCtx.m_tTokenizer.Ptr(), tCtx.m_pQueryTokenizer );
13320 			bOk = ( bOk && ( !SnippetFormatErrorMessage ( &sError, dQueries[i].m_sError ) ) );
13321 		}
13322 	} else
13323 	{
13324 		// get file sizes
13325 		ARRAY_FOREACH ( i, dQueries )
13326 		{
13327 			dQueries[i].m_iNext = PROCESSED_ITEM;
13328 			if ( dQueries[i].m_iLoadFiles )
13329 			{
13330 				struct stat st;
13331 				CSphString sFilename;
13332 				sFilename.SetSprintf ( "%s%s", g_sSnippetsFilePrefix.cstr(), dQueries[i].m_sSource.cstr() );
13333 				if ( ::stat ( sFilename.cstr(), &st )<0 )
13334 				{
13335 					if ( !bScattered )
13336 					{
13337 						sError.SetSprintf ( "failed to stat %s: %s", dQueries[i].m_sSource.cstr(), strerror(errno) );
13338 						pServed->Unlock();
13339 						return false;
13340 					}
13341 					dQueries[i].m_iNext = EOF_ITEM;
13342 				} else
13343 				{
13344 					assert ( st.st_size>0 );
13345 					dQueries[i].m_iSize = -st.st_size; // so that sort would put bigger ones first
13346 				}
13347 			} else
13348 			{
13349 				dQueries[i].m_iSize = -dQueries[i].m_sSource.Length();
13350 			}
13351 			dQueries[i].m_iSeq = i;
13352 		}
13353 
13354 		// set correct data size for snippets
13355 		if ( pThd )
13356 			pThd->SetSnippetThreadInfo ( dQueries );
13357 
13358 		// tough jobs first
13359 		if ( !bScattered )
13360 			dQueries.Sort ( bind ( &ExcerptQuery_t::m_iSize ) );
13361 
13362 		ARRAY_FOREACH ( i, dQueries )
13363 			if ( dQueries[i].m_iNext==EOF_ITEM )
13364 			{
13365 				dQueries[i].m_iNext = iAbsentHead;
13366 				iAbsentHead = i;
13367 				if ( !bSkipAbsentFiles )
13368 					dQueries[i].m_sError.SetSprintf ( "failed to stat %s: %s", dQueries[i].m_sSource.cstr(), strerror(errno) );
13369 			}
13370 
13371 
13372 
13373 		// check if all files are available locally.
13374 		if ( bScattered && iAbsentHead==EOF_ITEM )
13375 		{
13376 			bRemote = false;
13377 			dRemoteSnippets.m_dAgents.Reset();
13378 		}
13379 
13380 		if ( bRemote )
13381 		{
13382 			// schedule jobs across workers (the worker is remote agent).
13383 			// simple LPT (Least Processing Time) scheduling for now
13384 			// might add dynamic programming or something later if needed
13385 			int iRemoteAgents = dRemoteSnippets.m_dAgents.GetLength();
13386 			dRemoteSnippets.m_dWorkers.Resize ( iRemoteAgents );
13387 
13388 			if ( bScattered )
13389 			{
13390 				// on scattered case - the queries with m_iNext==PROCESSED_ITEM are here, and has to be scheduled to local agent
13391 				// the rest has to be sent to remotes, all of them!
13392 				for ( int i=0; i<iRemoteAgents; i++ )
13393 					dRemoteSnippets.m_dWorkers[i].m_iHead = iAbsentHead;
13394 			} else
13395 			{
13396 				ARRAY_FOREACH ( i, dQueries )
13397 				{
13398 					dRemoteSnippets.m_dWorkers[0].m_iTotal -= dQueries[i].m_iSize;
13399 					// queries sheduled for local still have iNext==-2
13400 					dQueries[i].m_iNext = dRemoteSnippets.m_dWorkers[0].m_iHead;
13401 					dRemoteSnippets.m_dWorkers[0].m_iHead = i;
13402 
13403 					dRemoteSnippets.m_dWorkers.Sort ( bind ( &SnippetWorker_t::m_iTotal ) );
13404 				}
13405 			}
13406 		}
13407 
13408 		// do MT searching
13409 		CSphMutex tLock;
13410 		tLock.Init();
13411 
13412 		CrashQuery_t tCrashQuery = SphCrashLogger_c::GetQuery(); // transfer query info for crash logger to new thread
13413 		int iCurQuery = 0;
13414 		CSphVector<SnippetThread_t> dThreads ( g_iDistThreads );
13415 		for ( int i=0; i<g_iDistThreads; i++ )
13416 		{
13417 			SnippetThread_t & t = dThreads[i];
13418 			t.m_pLock = &tLock;
13419 			t.m_iQueries = dQueries.GetLength();
13420 			t.m_pQueries = dQueries.Begin();
13421 			t.m_pCurQuery = &iCurQuery;
13422 			t.m_pIndex = pIndex;
13423 			t.m_tCrashQuery = tCrashQuery;
13424 			if ( i )
13425 				SphCrashLogger_c::ThreadCreate ( &dThreads[i].m_tThd, SnippetThreadFunc, &dThreads[i] );
13426 		}
13427 
13428 		CSphScopedPtr<SnippetRequestBuilder_t> tReqBuilder ( NULL );
13429 		CSphScopedPtr<CSphRemoteAgentsController> tDistCtrl ( NULL );
13430 		if ( bRemote && dRemoteSnippets.m_dAgents.GetLength() )
13431 		{
13432 			// connect to remote agents and query them
13433 			tReqBuilder = new SnippetRequestBuilder_t ( &dRemoteSnippets );
13434 			tDistCtrl = new CSphRemoteAgentsController ( g_iDistThreads, dRemoteSnippets.m_dAgents,
13435 				*tReqBuilder.Ptr(), dRemoteSnippets.m_iAgentConnectTimeout );
13436 		}
13437 
13438 		SnippetThreadFunc ( &dThreads[0] );
13439 
13440 		int iAgentsDone = 0;
13441 		if ( bRemote && dRemoteSnippets.m_dAgents.GetLength() )
13442 		{
13443 			iAgentsDone = tDistCtrl->Finish();
13444 		}
13445 
13446 		int iSuccesses = 0;
13447 		if ( iAgentsDone )
13448 		{
13449 			SnippetReplyParser_t tParser ( &dRemoteSnippets );
13450 			iSuccesses = RemoteWaitForAgents ( dRemoteSnippets.m_dAgents, dRemoteSnippets.m_iAgentQueryTimeout, tParser ); // FIXME? profile update time too?
13451 		}
13452 
13453 		for ( int i=1; i<dThreads.GetLength(); i++ )
13454 			sphThreadJoin ( &dThreads[i].m_tThd );
13455 
13456 		if ( iSuccesses!=dRemoteSnippets.m_dAgents.GetLength() )
13457 		{
13458 			sphWarning ( "Remote snippets: some of the agents didn't answered: %d queried, %d available, %d answered",
13459 				dRemoteSnippets.m_dAgents.GetLength(),
13460 				iAgentsDone,
13461 				iSuccesses );
13462 
13463 			if ( !bScattered )
13464 			{
13465 				// inverse the success/failed state - so that the queries with negative m_iNext are treated as failed
13466 				ARRAY_FOREACH ( i, dQueries )
13467 					dQueries[i].m_iNext = (dQueries[i].m_iNext==PROCESSED_ITEM)?0:PROCESSED_ITEM;
13468 
13469 				// failsafe - one more turn for failed queries on local agent
13470 				SnippetThread_t & t = dThreads[0];
13471 				t.m_pQueries = dQueries.Begin();
13472 				iCurQuery = 0;
13473 				SnippetThreadFunc ( &dThreads[0] );
13474 			}
13475 		}
13476 		tLock.Done();
13477 
13478 		// back in query order
13479 		dQueries.Sort ( bind ( &ExcerptQuery_t::m_iSeq ) );
13480 
13481 		ARRAY_FOREACH ( i, dQueries )
13482 		{
13483 			bOk = ( bOk && ( !SnippetFormatErrorMessage ( &sError, dQueries[i].m_sError ) ) );
13484 		}
13485 	}
13486 
13487 	pServed->Unlock();
13488 	return bOk;
13489 }
13490 
13491 
HandleCommandExcerpt(int iSock,int iVer,InputBuffer_c & tReq,ThdDesc_t * pThd)13492 void HandleCommandExcerpt ( int iSock, int iVer, InputBuffer_c & tReq, ThdDesc_t * pThd )
13493 {
13494 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_EXCERPT, tReq ) )
13495 		return;
13496 
13497 	/////////////////////////////
13498 	// parse and process request
13499 	/////////////////////////////
13500 
13501 	const int EXCERPT_MAX_ENTRIES			= 1024;
13502 
13503 	// v.1.1
13504 	ExcerptQuery_t q;
13505 
13506 	tReq.GetInt (); // mode field is for now reserved and ignored
13507 	int iFlags = tReq.GetInt ();
13508 	q.m_iRawFlags = iFlags;
13509 	CSphString sIndex = tReq.GetString ();
13510 
13511 	q.m_sWords = tReq.GetString ();
13512 	q.m_sBeforeMatch = tReq.GetString ();
13513 	q.m_sAfterMatch = tReq.GetString ();
13514 	q.m_sChunkSeparator = tReq.GetString ();
13515 	q.m_iLimit = tReq.GetInt ();
13516 	q.m_iAround = tReq.GetInt ();
13517 
13518 	if ( iVer>=0x102 )
13519 	{
13520 		q.m_iLimitPassages = tReq.GetInt();
13521 		q.m_iLimitWords = tReq.GetInt();
13522 		q.m_iPassageId = tReq.GetInt();
13523 		q.m_sStripMode = tReq.GetString();
13524 		if ( q.m_sStripMode!="none" && q.m_sStripMode!="index" && q.m_sStripMode!="strip" && q.m_sStripMode!="retain" )
13525 		{
13526 			tReq.SendErrorReply ( "unknown html_strip_mode=%s", q.m_sStripMode.cstr() );
13527 			return;
13528 		}
13529 	}
13530 
13531 	q.m_bHasBeforePassageMacro = SnippetTransformPassageMacros ( q.m_sBeforeMatch, q.m_sBeforeMatchPassage );
13532 	q.m_bHasAfterPassageMacro = SnippetTransformPassageMacros ( q.m_sAfterMatch, q.m_sAfterMatchPassage );
13533 
13534 	CSphString sPassageBoundaryMode;
13535 	if ( iVer>=0x103 )
13536 		q.m_sRawPassageBoundary = tReq.GetString();
13537 
13538 	q.m_bRemoveSpaces = ( iFlags & EXCERPT_FLAG_REMOVESPACES )!=0;
13539 	q.m_bExactPhrase = ( iFlags & EXCERPT_FLAG_EXACTPHRASE )!=0;
13540 	q.m_bUseBoundaries = ( iFlags & EXCERPT_FLAG_USEBOUNDARIES )!=0;
13541 	q.m_bWeightOrder = ( iFlags & EXCERPT_FLAG_WEIGHTORDER )!=0;
13542 	q.m_bHighlightQuery = ( iFlags & EXCERPT_FLAG_QUERY )!=0;
13543 	q.m_bForceAllWords = ( iFlags & EXCERPT_FLAG_FORCE_ALL_WORDS )!=0;
13544 	if ( iFlags & EXCERPT_FLAG_SINGLEPASSAGE )
13545 		q.m_iLimitPassages = 1;
13546 	q.m_iLoadFiles = (( iFlags & EXCERPT_FLAG_LOAD_FILES )!=0)?1:0;
13547 	bool bScattered = ( iFlags & EXCERPT_FLAG_FILES_SCATTERED )!=0;
13548 	q.m_iLoadFiles |= bScattered?2:0;
13549 	if ( q.m_iLoadFiles )
13550 		q.m_sFilePrefix = g_sSnippetsFilePrefix;
13551 	q.m_bAllowEmpty = ( iFlags & EXCERPT_FLAG_ALLOW_EMPTY )!=0;
13552 	q.m_bEmitZones = ( iFlags & EXCERPT_FLAG_EMIT_ZONES )!=0;
13553 
13554 	int iCount = tReq.GetInt ();
13555 	if ( iCount<=0 || iCount>EXCERPT_MAX_ENTRIES )
13556 	{
13557 		tReq.SendErrorReply ( "invalid entries count %d", iCount );
13558 		return;
13559 	}
13560 
13561 	q.m_ePassageSPZ = sphGetPassageBoundary ( q.m_sRawPassageBoundary );
13562 
13563 	CSphString sError;
13564 
13565 	if ( !sphCheckOptionsSPZ ( q, q.m_sRawPassageBoundary, sError ) )
13566 	{
13567 		tReq.SendErrorReply ( "%s", sError.cstr() );
13568 		return;
13569 	}
13570 
13571 	CSphVector<ExcerptQuery_t> dQueries ( iCount );
13572 
13573 	ARRAY_FOREACH ( i, dQueries )
13574 	{
13575 		dQueries[i] = q; // copy settings
13576 		dQueries[i].m_sSource = tReq.GetString (); // fetch data
13577 		if ( tReq.GetError() )
13578 		{
13579 			tReq.SendErrorReply ( "invalid or truncated request" );
13580 			return;
13581 		}
13582 	}
13583 
13584 	if ( pThd )
13585 		pThd->SetSnippetThreadInfo ( dQueries );
13586 
13587 	if ( !MakeSnippets ( sIndex, dQueries, sError, pThd ) )
13588 	{
13589 		tReq.SendErrorReply ( "%s", sError.cstr() );
13590 		return;
13591 	}
13592 
13593 	////////////////
13594 	// serve result
13595 	////////////////
13596 
13597 	int iRespLen = 0;
13598 	ARRAY_FOREACH ( i, dQueries )
13599 	{
13600 		// handle errors
13601 		if ( !dQueries[i].m_dRes.GetLength() )
13602 		{
13603 			if ( !bScattered )
13604 			{
13605 				tReq.SendErrorReply ( "highlighting failed: %s", dQueries[i].m_sError.cstr() );
13606 				return;
13607 			}
13608 			iRespLen += 4;
13609 		} else
13610 			iRespLen += 4 + strlen ( (const char *)dQueries[i].m_dRes.Begin() );
13611 	}
13612 
13613 	NetOutputBuffer_c tOut ( iSock );
13614 	tOut.SendWord ( SEARCHD_OK );
13615 	tOut.SendWord ( VER_COMMAND_EXCERPT );
13616 	tOut.SendInt ( iRespLen );
13617 	ARRAY_FOREACH ( i, dQueries )
13618 	{
13619 		if ( dQueries[i].m_dRes.GetLength() )
13620 			tOut.SendString ( (const char *)dQueries[i].m_dRes.Begin() );
13621 		else
13622 			tOut.SendString ( "" );
13623 	}
13624 
13625 	tOut.Flush ();
13626 	assert ( tOut.GetError()==true || tOut.GetSentCount()==iRespLen+8 );
13627 }
13628 
13629 /////////////////////////////////////////////////////////////////////////////
13630 // KEYWORDS HANDLER
13631 /////////////////////////////////////////////////////////////////////////////
13632 
HandleCommandKeywords(int iSock,int iVer,InputBuffer_c & tReq)13633 void HandleCommandKeywords ( int iSock, int iVer, InputBuffer_c & tReq )
13634 {
13635 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_KEYWORDS, tReq ) )
13636 		return;
13637 
13638 	CSphString sQuery = tReq.GetString ();
13639 	CSphString sIndex = tReq.GetString ();
13640 	bool bGetStats = !!tReq.GetInt ();
13641 
13642 	const ServedIndex_t * pIndex = g_pLocalIndexes->GetRlockedEntry ( sIndex );
13643 	if ( !pIndex )
13644 	{
13645 		pIndex = g_pTemplateIndexes->GetRlockedEntry ( sIndex );
13646 		if ( !pIndex )
13647 		{
13648 			tReq.SendErrorReply ( "unknown local index '%s' in search request", sIndex.cstr() );
13649 			return;
13650 		}
13651 	}
13652 
13653 	CSphString sError;
13654 	CSphVector < CSphKeywordInfo > dKeywords;
13655 	if ( !pIndex->m_pIndex->GetKeywords ( dKeywords, sQuery.cstr (), bGetStats, &sError ) )
13656 	{
13657 		tReq.SendErrorReply ( "error generating keywords: %s", sError.cstr () );
13658 		pIndex->Unlock();
13659 		return;
13660 	}
13661 
13662 	pIndex->Unlock();
13663 
13664 	int iRespLen = 4;
13665 	ARRAY_FOREACH ( i, dKeywords )
13666 	{
13667 		iRespLen += 4 + strlen ( dKeywords[i].m_sTokenized.cstr () );
13668 		iRespLen += 4 + strlen ( dKeywords[i].m_sNormalized.cstr () );
13669 		if ( bGetStats )
13670 			iRespLen += 8;
13671 	}
13672 
13673 	NetOutputBuffer_c tOut ( iSock );
13674 	tOut.SendWord ( SEARCHD_OK );
13675 	tOut.SendWord ( VER_COMMAND_KEYWORDS );
13676 	tOut.SendInt ( iRespLen );
13677 	tOut.SendInt ( dKeywords.GetLength () );
13678 	ARRAY_FOREACH ( i, dKeywords )
13679 	{
13680 		tOut.SendString ( dKeywords[i].m_sTokenized.cstr () );
13681 		tOut.SendString ( dKeywords[i].m_sNormalized.cstr () );
13682 		if ( bGetStats )
13683 		{
13684 			tOut.SendInt ( dKeywords[i].m_iDocs );
13685 			tOut.SendInt ( dKeywords[i].m_iHits );
13686 		}
13687 	}
13688 
13689 	tOut.Flush ();
13690 	assert ( tOut.GetError()==true || tOut.GetSentCount()==iRespLen+8 );
13691 }
13692 
13693 /////////////////////////////////////////////////////////////////////////////
13694 // UPDATES HANDLER
13695 /////////////////////////////////////////////////////////////////////////////
13696 
13697 struct UpdateRequestBuilder_t : public IRequestBuilder_t
13698 {
UpdateRequestBuilder_tUpdateRequestBuilder_t13699 	explicit UpdateRequestBuilder_t ( const CSphAttrUpdate & pUpd ) : m_tUpd ( pUpd ) {}
13700 	virtual void BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const;
13701 
13702 protected:
13703 	const CSphAttrUpdate & m_tUpd;
13704 };
13705 
13706 
13707 struct UpdateReplyParser_t : public IReplyParser_t
13708 {
UpdateReplyParser_tUpdateReplyParser_t13709 	explicit UpdateReplyParser_t ( int * pUpd )
13710 		: m_pUpdated ( pUpd )
13711 	{}
13712 
ParseReplyUpdateReplyParser_t13713 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const
13714 	{
13715 		*m_pUpdated += tReq.GetDword ();
13716 		return true;
13717 	}
13718 
13719 protected:
13720 	int * m_pUpdated;
13721 };
13722 
13723 
BuildRequest(AgentConn_t & tAgent,NetOutputBuffer_c & tOut) const13724 void UpdateRequestBuilder_t::BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const
13725 {
13726 	const char* sIndexes = tAgent.m_sIndexes.cstr();
13727 	int iReqSize = 4+strlen(sIndexes); // indexes string
13728 	iReqSize += 8; // attrs array len, data, non-existent flags
13729 	ARRAY_FOREACH ( i, m_tUpd.m_dAttrs )
13730 		iReqSize += 8 + strlen ( m_tUpd.m_dAttrs[i] );
13731 	iReqSize += 4; // number of updates
13732 	iReqSize += 8 * m_tUpd.m_dDocids.GetLength() + 4 * m_tUpd.m_dPool.GetLength(); // 64bit ids, 32bit values
13733 
13734 	bool bMva = false;
13735 	ARRAY_FOREACH ( i, m_tUpd.m_dTypes )
13736 	{
13737 		assert ( m_tUpd.m_dTypes[i]!=SPH_ATTR_INT64SET ); // mva64 goes only via SphinxQL (SphinxqlRequestBuilder_t)
13738 		bMva |= ( m_tUpd.m_dTypes[i]==SPH_ATTR_UINT32SET );
13739 	}
13740 
13741 	if ( bMva )
13742 	{
13743 		int iMvaCount = 0;
13744 		ARRAY_FOREACH ( iDoc, m_tUpd.m_dDocids )
13745 		{
13746 			const DWORD * pPool = m_tUpd.m_dPool.Begin() + m_tUpd.m_dRowOffset[iDoc];
13747 			ARRAY_FOREACH ( iAttr, m_tUpd.m_dTypes )
13748 			{
13749 				if ( m_tUpd.m_dTypes[iAttr]==SPH_ATTR_UINT32SET )
13750 				{
13751 					DWORD uVal = *pPool++;
13752 					iMvaCount += uVal;
13753 					pPool += uVal;
13754 				} else
13755 				{
13756 					pPool++;
13757 				}
13758 			}
13759 		}
13760 
13761 		iReqSize -= ( iMvaCount * 4 / 2 ); // mva64 only via SphinxQL
13762 	}
13763 
13764 	// header
13765 	tOut.SendWord ( SEARCHD_COMMAND_UPDATE );
13766 	tOut.SendWord ( VER_COMMAND_UPDATE );
13767 	tOut.SendInt ( iReqSize );
13768 
13769 	tOut.SendString ( sIndexes );
13770 	tOut.SendInt ( m_tUpd.m_dAttrs.GetLength() );
13771 	tOut.SendInt ( m_tUpd.m_bIgnoreNonexistent ? 1 : 0 );
13772 	ARRAY_FOREACH ( i, m_tUpd.m_dAttrs )
13773 	{
13774 		tOut.SendString ( m_tUpd.m_dAttrs[i] );
13775 		tOut.SendInt ( ( m_tUpd.m_dTypes[i]==SPH_ATTR_UINT32SET ) ? 1 : 0 );
13776 	}
13777 	tOut.SendInt ( m_tUpd.m_dDocids.GetLength() );
13778 
13779 	if ( !bMva )
13780 	{
13781 		ARRAY_FOREACH ( iDoc, m_tUpd.m_dDocids )
13782 		{
13783 			tOut.SendUint64 ( m_tUpd.m_dDocids[iDoc] );
13784 			int iHead = m_tUpd.m_dRowOffset[iDoc];
13785 			int iTail = m_tUpd.m_dPool.GetLength ();
13786 			if ( (iDoc+1)<m_tUpd.m_dDocids.GetLength() )
13787 				iTail = m_tUpd.m_dRowOffset[iDoc+1];
13788 
13789 			for ( int j=iHead; j<iTail; j++ )
13790 				tOut.SendDword ( m_tUpd.m_dPool[j] );
13791 		}
13792 	} else
13793 	{
13794 		// size down in case of MVA
13795 		// MVA stored as mva64 in pool but API could handle only mva32 due to HandleCommandUpdate
13796 		// SphinxQL only could work either mva32 or mva64 and only SphinxQL could receive mva64 updates
13797 		// SphinxQL master communicate to agent via SphinxqlRequestBuilder_t
13798 
13799 		ARRAY_FOREACH ( iDoc, m_tUpd.m_dDocids )
13800 		{
13801 			tOut.SendUint64 ( m_tUpd.m_dDocids[iDoc] );
13802 
13803 			const DWORD * pPool = m_tUpd.m_dPool.Begin() + m_tUpd.m_dRowOffset[iDoc];
13804 			ARRAY_FOREACH ( iAttr, m_tUpd.m_dTypes )
13805 			{
13806 				DWORD uVal = *pPool++;
13807 				if ( m_tUpd.m_dTypes[iAttr]!=SPH_ATTR_UINT32SET )
13808 				{
13809 					tOut.SendDword ( uVal );
13810 				} else
13811 				{
13812 					const DWORD * pEnd = pPool + uVal;
13813 					tOut.SendDword ( uVal/2 );
13814 					while ( pPool<pEnd )
13815 					{
13816 						tOut.SendDword ( *pPool );
13817 						pPool += 2;
13818 					}
13819 				}
13820 			}
13821 		}
13822 	}
13823 }
13824 
DoCommandUpdate(const char * sIndex,const CSphAttrUpdate & tUpd,int & iSuccesses,int & iUpdated,SearchFailuresLog_c & dFails,const ServedIndex_t * pServed)13825 static void DoCommandUpdate ( const char * sIndex, const CSphAttrUpdate & tUpd,
13826 	int & iSuccesses, int & iUpdated,
13827 	SearchFailuresLog_c & dFails, const ServedIndex_t * pServed )
13828 {
13829 	if ( !pServed || !pServed->m_pIndex || !pServed->m_bEnabled )
13830 	{
13831 		dFails.Submit ( sIndex, "index not available" );
13832 		return;
13833 	}
13834 
13835 	CSphString sError, sWarning;
13836 	int iUpd = pServed->m_pIndex->UpdateAttributes ( tUpd, -1, sError, sWarning );
13837 
13838 	if ( iUpd<0 )
13839 	{
13840 		dFails.Submit ( sIndex, sError.cstr() );
13841 
13842 	} else
13843 	{
13844 		iUpdated += iUpd;
13845 		iSuccesses++;
13846 		if ( sWarning.Length() )
13847 			dFails.Submit ( sIndex, sWarning.cstr() );
13848 	}
13849 }
13850 
UpdateGetLockedIndex(const CSphString & sName,bool bMvaUpdate)13851 static const ServedIndex_t * UpdateGetLockedIndex ( const CSphString & sName, bool bMvaUpdate )
13852 {
13853 	// MVA updates have to be done sequentially
13854 	if ( !bMvaUpdate )
13855 		return g_pLocalIndexes->GetRlockedEntry ( sName );
13856 
13857 	return g_pLocalIndexes->GetWlockedEntry ( sName );
13858 }
13859 
13860 
HandleCommandUpdate(int iSock,int iVer,InputBuffer_c & tReq)13861 void HandleCommandUpdate ( int iSock, int iVer, InputBuffer_c & tReq )
13862 {
13863 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_UPDATE, tReq ) )
13864 		return;
13865 
13866 	// parse request
13867 	CSphString sIndexes = tReq.GetString ();
13868 	CSphAttrUpdate tUpd;
13869 	CSphVector<DWORD> dMva;
13870 
13871 	bool bMvaUpdate = false;
13872 
13873 	tUpd.m_dAttrs.Resize ( tReq.GetDword() ); // FIXME! check this
13874 	tUpd.m_dTypes.Resize ( tUpd.m_dAttrs.GetLength() );
13875 	if ( iVer>=0x103 )
13876 		tUpd.m_bIgnoreNonexistent = ( tReq.GetDword() & 1 )!=0;
13877 	ARRAY_FOREACH ( i, tUpd.m_dAttrs )
13878 	{
13879 		tUpd.m_dAttrs[i] = tReq.GetString().ToLower().Leak();
13880 		tUpd.m_dTypes[i] = SPH_ATTR_INTEGER;
13881 		if ( iVer>=0x102 )
13882 		{
13883 			if ( tReq.GetDword() )
13884 			{
13885 				tUpd.m_dTypes[i] = SPH_ATTR_UINT32SET;
13886 				bMvaUpdate = true;
13887 			}
13888 		}
13889 	}
13890 
13891 	int iNumUpdates = tReq.GetInt (); // FIXME! check this
13892 	tUpd.m_dDocids.Reserve ( iNumUpdates );
13893 	tUpd.m_dRowOffset.Reserve ( iNumUpdates );
13894 
13895 	for ( int i=0; i<iNumUpdates; i++ )
13896 	{
13897 		// v.1.0 always sends 32-bit ids; v.1.1+ always send 64-bit ones
13898 		uint64_t uDocid = ( iVer>=0x101 ) ? tReq.GetUint64 () : tReq.GetDword ();
13899 
13900 		tUpd.m_dDocids.Add ( (SphDocID_t)uDocid ); // FIXME! check this
13901 		tUpd.m_dRowOffset.Add ( tUpd.m_dPool.GetLength() );
13902 
13903 		ARRAY_FOREACH ( iAttr, tUpd.m_dAttrs )
13904 		{
13905 			if ( tUpd.m_dTypes[iAttr]==SPH_ATTR_UINT32SET )
13906 			{
13907 				DWORD uCount = tReq.GetDword ();
13908 				if ( !uCount )
13909 				{
13910 					tUpd.m_dPool.Add ( 0 );
13911 					continue;
13912 				}
13913 
13914 				dMva.Resize ( uCount );
13915 				for ( DWORD j=0; j<uCount; j++ )
13916 				{
13917 					dMva[j] = tReq.GetDword();
13918 				}
13919 				dMva.Uniq(); // don't need dupes within MVA
13920 
13921 				tUpd.m_dPool.Add ( dMva.GetLength()*2 );
13922 				ARRAY_FOREACH ( j, dMva )
13923 				{
13924 					tUpd.m_dPool.Add ( dMva[j] );
13925 					tUpd.m_dPool.Add ( 0 ); // dummy expander mva32 -> mva64
13926 				}
13927 			} else
13928 			{
13929 				tUpd.m_dPool.Add ( tReq.GetDword() );
13930 			}
13931 		}
13932 	}
13933 
13934 	if ( tReq.GetError() )
13935 	{
13936 		tReq.SendErrorReply ( "invalid or truncated request" );
13937 		return;
13938 	}
13939 
13940 	// check index names
13941 	CSphVector<CSphString> dIndexNames;
13942 	ParseIndexList ( sIndexes, dIndexNames );
13943 
13944 	if ( !dIndexNames.GetLength() )
13945 	{
13946 		tReq.SendErrorReply ( "no valid indexes in update request" );
13947 		return;
13948 	}
13949 
13950 	CSphVector<DistributedIndex_t> dDistributed ( dIndexNames.GetLength() ); // lock safe storage for distributed indexes
13951 	ARRAY_FOREACH ( i, dIndexNames )
13952 	{
13953 		if ( !g_pLocalIndexes->Exists ( dIndexNames[i] ) )
13954 		{
13955 			// search amongst distributed and copy for further processing
13956 			g_tDistLock.Lock();
13957 			const DistributedIndex_t * pDistIndex = g_hDistIndexes ( dIndexNames[i] );
13958 
13959 			if ( pDistIndex )
13960 			{
13961 				dDistributed[i] = *pDistIndex;
13962 			}
13963 
13964 			g_tDistLock.Unlock();
13965 
13966 			if ( pDistIndex )
13967 				continue;
13968 			else
13969 			{
13970 				tReq.SendErrorReply ( "unknown index '%s' in update request", dIndexNames[i].cstr() );
13971 				return;
13972 			}
13973 		}
13974 	}
13975 
13976 	// do update
13977 	SearchFailuresLog_c dFails;
13978 	int iSuccesses = 0;
13979 	int iUpdated = 0;
13980 	tUpd.m_dRows.Resize ( tUpd.m_dDocids.GetLength() );
13981 	ARRAY_FOREACH ( i, tUpd.m_dRows ) tUpd.m_dRows[i] = NULL;
13982 
13983 	ARRAY_FOREACH ( iIdx, dIndexNames )
13984 	{
13985 		const char * sReqIndex = dIndexNames[iIdx].cstr();
13986 		const ServedIndex_t * pLocked = UpdateGetLockedIndex ( sReqIndex, bMvaUpdate );
13987 		if ( pLocked )
13988 		{
13989 			DoCommandUpdate ( sReqIndex, tUpd, iSuccesses, iUpdated, dFails, pLocked );
13990 			pLocked->Unlock();
13991 		} else
13992 		{
13993 			assert ( dDistributed[iIdx].m_dLocal.GetLength() || dDistributed[iIdx].m_dAgents.GetLength() );
13994 			CSphVector<CSphString>& dLocal = dDistributed[iIdx].m_dLocal;
13995 
13996 			ARRAY_FOREACH ( i, dLocal )
13997 			{
13998 				const char * sLocal = dLocal[i].cstr();
13999 				const ServedIndex_t * pServed = UpdateGetLockedIndex ( sLocal, bMvaUpdate );
14000 				DoCommandUpdate ( sLocal, tUpd, iSuccesses, iUpdated, dFails, pServed );
14001 				if ( pServed )
14002 					pServed->Unlock();
14003 			}
14004 		}
14005 
14006 		// update remote agents
14007 		if ( dDistributed[iIdx].m_dAgents.GetLength() )
14008 		{
14009 			DistributedIndex_t & tDist = dDistributed[iIdx];
14010 
14011 			CSphVector<AgentConn_t> dAgents;
14012 			tDist.GetAllAgents ( &dAgents );
14013 
14014 			// connect to remote agents and query them
14015 			UpdateRequestBuilder_t tReqBuilder ( tUpd );
14016 			CSphRemoteAgentsController tDistCtrl ( g_iDistThreads, dAgents, tReqBuilder, tDist.m_iAgentConnectTimeout );
14017 			int iAgentsDone = tDistCtrl.Finish();
14018 			if ( iAgentsDone )
14019 			{
14020 				UpdateReplyParser_t tParser ( &iUpdated );
14021 				iSuccesses += RemoteWaitForAgents ( dAgents, tDist.m_iAgentQueryTimeout, tParser ); // FIXME? profile update time too?
14022 			}
14023 		}
14024 	}
14025 
14026 	// serve reply to client
14027 	CSphStringBuilder sReport;
14028 	dFails.BuildReport ( sReport );
14029 
14030 	if ( !iSuccesses )
14031 	{
14032 		tReq.SendErrorReply ( "%s", sReport.cstr() );
14033 		return;
14034 	}
14035 
14036 	NetOutputBuffer_c tOut ( iSock );
14037 	if ( dFails.IsEmpty() )
14038 	{
14039 		tOut.SendWord ( SEARCHD_OK );
14040 		tOut.SendWord ( VER_COMMAND_UPDATE );
14041 		tOut.SendInt ( 4 );
14042 	} else
14043 	{
14044 		tOut.SendWord ( SEARCHD_WARNING );
14045 		tOut.SendWord ( VER_COMMAND_UPDATE );
14046 		tOut.SendInt ( 8 + strlen ( sReport.cstr() ) );
14047 		tOut.SendString ( sReport.cstr() );
14048 	}
14049 	tOut.SendInt ( iUpdated );
14050 	tOut.Flush ();
14051 }
14052 
14053 // 'like' matcher
14054 class CheckLike
14055 {
14056 private:
14057 	CSphString m_sPattern;
14058 
14059 public:
CheckLike(const char * sPattern)14060 	explicit CheckLike ( const char * sPattern )
14061 	{
14062 		if ( !sPattern )
14063 			return;
14064 
14065 		m_sPattern.Reserve ( 2*strlen ( sPattern ) );
14066 		char * d = const_cast<char*> ( m_sPattern.cstr() );
14067 
14068 		// remap from SQL LIKE syntax to Sphinx wildcards syntax
14069 		// '_' maps to '?', match any single char
14070 		// '%' maps to '*', match zero or mor chars
14071 		for ( const char * s = sPattern; *s; s++ )
14072 		{
14073 			switch ( *s )
14074 			{
14075 				case '_':	*d++ = '?'; break;
14076 				case '%':	*d++ = '*'; break;
14077 				case '?':	*d++ = '\\'; *d++ = '?'; break;
14078 				case '*':	*d++ = '\\'; *d++ = '*'; break;
14079 				default:	*d++ = *s; break;
14080 			}
14081 		}
14082 		*d = '\0';
14083 	}
14084 
Match(const char * sValue)14085 	bool Match ( const char * sValue )
14086 	{
14087 		return sValue && ( m_sPattern.IsEmpty() || sphWildcardMatch ( sValue, m_sPattern.cstr() ) );
14088 	}
14089 };
14090 
14091 // string vector with 'like' matcher
14092 class VectorLike : public CSphVector<CSphString>, public CheckLike
14093 {
14094 public:
14095 	CSphString m_sColKey;
14096 	CSphString m_sColValue;
14097 
14098 public:
14099 
VectorLike()14100 	VectorLike ()
14101 		: CheckLike ( NULL )
14102 	{}
14103 
VectorLike(const CSphString & sPattern)14104 	explicit VectorLike ( const CSphString& sPattern )
14105 		: CheckLike ( sPattern.cstr() )
14106 		, m_sColKey ( "Variable_name" )
14107 		, m_sColValue ( "Value" )
14108 	{}
14109 
szColKey() const14110 	inline const char * szColKey() const
14111 	{
14112 		return m_sColKey.cstr();
14113 	}
14114 
szColValue() const14115 	inline const char * szColValue() const
14116 	{
14117 		return m_sColValue.cstr();
14118 	}
14119 
MatchAdd(const char * sValue)14120 	bool MatchAdd ( const char* sValue )
14121 	{
14122 		if ( Match ( sValue ) )
14123 		{
14124 			Add ( sValue );
14125 			return true;
14126 		}
14127 		return false;
14128 	}
14129 
MatchAddVa(const char * sTemplate,...)14130 	bool MatchAddVa ( const char * sTemplate, ... ) __attribute__ ( ( format ( printf, 2, 3 ) ) )
14131 	{
14132 		va_list ap;
14133 		CSphString sValue;
14134 
14135 		va_start ( ap, sTemplate );
14136 		sValue.SetSprintfVa ( sTemplate, ap );
14137 		va_end ( ap );
14138 
14139 		return MatchAdd ( sValue.cstr() );
14140 	}
14141 };
14142 
14143 //////////////////////////////////////////////////////////////////////////
14144 // STATUS HANDLER
14145 //////////////////////////////////////////////////////////////////////////
14146 
FormatMsec(CSphString & sOut,int64_t tmTime)14147 static inline void FormatMsec ( CSphString & sOut, int64_t tmTime )
14148 {
14149 	sOut.SetSprintf ( "%d.%03d", (int)( tmTime/1000000 ), (int)( (tmTime%1000000)/1000 ) );
14150 }
14151 
BuildStatus(VectorLike & dStatus)14152 void BuildStatus ( VectorLike & dStatus )
14153 {
14154 	assert ( g_pStats );
14155 	const char * FMT64 = INT64_FMT;
14156 	const char * OFF = "OFF";
14157 
14158 	const int64_t iQueriesDiv = Max ( g_pStats->m_iQueries, 1 );
14159 	const int64_t iDistQueriesDiv = Max ( g_pStats->m_iDistQueries, 1 );
14160 
14161 	dStatus.m_sColKey = "Counter";
14162 
14163 	// FIXME? non-transactional!!!
14164 	if ( dStatus.MatchAdd ( "uptime" ) )
14165 		dStatus.Add().SetSprintf ( "%u", (DWORD)time(NULL)-g_pStats->m_uStarted );
14166 	if ( dStatus.MatchAdd ( "connections" ) )
14167 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iConnections );
14168 	if ( dStatus.MatchAdd ( "maxed_out" ) )
14169 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iMaxedOut );
14170 	if ( dStatus.MatchAdd ( "command_search" ) )
14171 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_SEARCH] );
14172 	if ( dStatus.MatchAdd ( "command_excerpt" ) )
14173 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_EXCERPT] );
14174 	if ( dStatus.MatchAdd ( "command_update" ) )
14175 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_UPDATE] );
14176 	if ( dStatus.MatchAdd ( "command_delete" ) )
14177 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_DELETE] );
14178 	if ( dStatus.MatchAdd ( "command_keywords" ) )
14179 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_KEYWORDS] );
14180 	if ( dStatus.MatchAdd ( "command_persist" ) )
14181 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_PERSIST] );
14182 	if ( dStatus.MatchAdd ( "command_status" ) )
14183 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_STATUS] );
14184 	if ( dStatus.MatchAdd ( "command_flushattrs" ) )
14185 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iCommandCount[SEARCHD_COMMAND_FLUSHATTRS] );
14186 	if ( dStatus.MatchAdd ( "agent_connect" ) )
14187 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iAgentConnect );
14188 	if ( dStatus.MatchAdd ( "agent_retry" ) )
14189 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iAgentRetry );
14190 	if ( dStatus.MatchAdd ( "queries" ) )
14191 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iQueries );
14192 	if ( dStatus.MatchAdd ( "dist_queries" ) )
14193 		dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iDistQueries );
14194 
14195 	g_tDistLock.Lock();
14196 	g_hDistIndexes.IterateStart();
14197 	while ( g_hDistIndexes.IterateNext() )
14198 	{
14199 		const char * sIdx = g_hDistIndexes.IterateGetKey().cstr();
14200 		CSphVector<MetaAgentDesc_t> & dAgents = g_hDistIndexes.IterateGet().m_dAgents;
14201 		ARRAY_FOREACH ( i, dAgents )
14202 			ARRAY_FOREACH ( j, dAgents[i].GetAgents() )
14203 		{
14204 			int iIndex = dAgents[i].GetAgents()[j].m_iStatsIndex;
14205 			if ( iIndex<0 || iIndex>=STATS_MAX_AGENTS )
14206 				continue;
14207 
14208 			AgentStats_t & tStats = g_pStats->m_dAgentStats.m_dItemStats[iIndex];
14209 			for ( int k=0; k<eMaxStat; ++k )
14210 				if ( dStatus.MatchAddVa ( "ag_%s_%d_%d_%s", sIdx, i+1, j+1, tStats.m_sNames[k] ) )
14211 					dStatus.Add().SetSprintf ( FMT64, tStats.m_iStats[k] );
14212 		}
14213 	}
14214 	g_tDistLock.Unlock();
14215 
14216 	if ( dStatus.MatchAdd ( "query_wall" ) )
14217 		FormatMsec ( dStatus.Add(), g_pStats->m_iQueryTime );
14218 
14219 	if ( dStatus.MatchAdd ( "query_cpu" ) )
14220 	{
14221 		if ( g_bCpuStats )
14222 			FormatMsec ( dStatus.Add(), g_pStats->m_iQueryCpuTime );
14223 		else
14224 			dStatus.Add() = OFF;
14225 	}
14226 
14227 	if ( dStatus.MatchAdd ( "dist_wall" ) )
14228 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistWallTime );
14229 	if ( dStatus.MatchAdd ( "dist_local" ) )
14230 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistLocalTime );
14231 	if ( dStatus.MatchAdd ( "dist_wait" ) )
14232 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistWaitTime );
14233 
14234 	if ( g_bIOStats )
14235 	{
14236 		if ( dStatus.MatchAdd ( "query_reads" ) )
14237 			dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iDiskReads );
14238 		if ( dStatus.MatchAdd ( "query_readkb" ) )
14239 			dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iDiskReadBytes/1024 );
14240 		if ( dStatus.MatchAdd ( "query_readtime" ) )
14241 			FormatMsec ( dStatus.Add(), g_pStats->m_iDiskReadTime );
14242 	} else
14243 	{
14244 		if ( dStatus.MatchAdd ( "query_reads" ) )
14245 			dStatus.Add() = OFF;
14246 		if ( dStatus.MatchAdd ( "query_readkb" ) )
14247 			dStatus.Add() = OFF;
14248 		if ( dStatus.MatchAdd ( "query_readtime" ) )
14249 			dStatus.Add() = OFF;
14250 	}
14251 
14252 	if ( g_pStats->m_iPredictedTime || g_pStats->m_iAgentPredictedTime )
14253 	{
14254 		if ( dStatus.MatchAdd ( "predicted_time" ) )
14255 			dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iPredictedTime );
14256 		if ( dStatus.MatchAdd ( "dist_predicted_time" ) )
14257 			dStatus.Add().SetSprintf ( FMT64, g_pStats->m_iAgentPredictedTime );
14258 	}
14259 
14260 	if ( dStatus.MatchAdd ( "avg_query_wall" ) )
14261 		FormatMsec ( dStatus.Add(), g_pStats->m_iQueryTime / iQueriesDiv );
14262 	if ( dStatus.MatchAdd ( "avg_query_cpu" ) )
14263 	{
14264 		if ( g_bCpuStats )
14265 			FormatMsec ( dStatus.Add(), g_pStats->m_iQueryCpuTime / iQueriesDiv );
14266 		else
14267 			dStatus.Add ( OFF );
14268 	}
14269 
14270 	if ( dStatus.MatchAdd ( "avg_dist_wall" ) )
14271 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistWallTime / iDistQueriesDiv );
14272 	if ( dStatus.MatchAdd ( "avg_dist_local" ) )
14273 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistLocalTime / iDistQueriesDiv );
14274 	if ( dStatus.MatchAdd ( "avg_dist_wait" ) )
14275 		FormatMsec ( dStatus.Add(), g_pStats->m_iDistWaitTime / iDistQueriesDiv );
14276 	if ( g_bIOStats )
14277 	{
14278 		if ( dStatus.MatchAdd ( "avg_query_reads" ) )
14279 			dStatus.Add().SetSprintf ( "%.1f", (float)( g_pStats->m_iDiskReads*10/iQueriesDiv )/10.0f );
14280 		if ( dStatus.MatchAdd ( "avg_query_readkb" ) )
14281 			dStatus.Add().SetSprintf ( "%.1f", (float)( g_pStats->m_iDiskReadBytes/iQueriesDiv )/1024.0f );
14282 		if ( dStatus.MatchAdd ( "avg_query_readtime" ) )
14283 			FormatMsec ( dStatus.Add(), g_pStats->m_iDiskReadTime/iQueriesDiv );
14284 	} else
14285 	{
14286 		if ( dStatus.MatchAdd ( "avg_query_reads" ) )
14287 			dStatus.Add() = OFF;
14288 		if ( dStatus.MatchAdd ( "avg_query_readkb" ) )
14289 			dStatus.Add() = OFF;
14290 		if ( dStatus.MatchAdd ( "avg_query_readtime" ) )
14291 			dStatus.Add() = OFF;
14292 	}
14293 }
14294 
BuildOneAgentStatus(VectorLike & dStatus,const CSphString & sAgent,const char * sPrefix="agent")14295 void BuildOneAgentStatus ( VectorLike & dStatus, const CSphString& sAgent, const char * sPrefix="agent" )
14296 {
14297 	if ( !g_pStats )
14298 	{
14299 		dStatus.Add("No agent status");
14300 		dStatus.Add("Error");
14301 		return;
14302 	}
14303 
14304 	g_tStatsMutex.Lock ();
14305 	int * pIndex = g_pStats->m_hDashBoard ( sAgent );
14306 	if ( !pIndex )
14307 	{
14308 		if ( dStatus.MatchAdd ( "status_error" ) )
14309 			dStatus.Add().SetSprintf ( "No such agent: %s", sAgent.cstr() );
14310 		g_tStatsMutex.Unlock ();
14311 		return;
14312 	}
14313 
14314 	int & iIndex = *pIndex;
14315 	const char * FMT64 = UINT64_FMT;
14316 	const char * FLOAT = "%.2f";
14317 
14318 	if ( dStatus.MatchAddVa ( "%s_hostname", sPrefix ) )
14319 		dStatus.Add().SetSprintf ( "%s", sAgent.cstr() );
14320 
14321 	HostDashboard_t & dDash = g_pStats->m_dDashboard.m_dItemStats[iIndex];
14322 	if ( dStatus.MatchAddVa ( "%s_references", sPrefix ) )
14323 		dStatus.Add().SetSprintf ( "%d", dDash.m_iRefCount );
14324 	uint64_t iCur = sphMicroTimer();
14325 	uint64_t iLastAccess = iCur - dDash.m_iLastQueryTime;
14326 	float fPeriod = (float)iLastAccess/1000000.0f;
14327 	if ( dStatus.MatchAddVa ( "%s_lastquery", sPrefix ) )
14328 		dStatus.Add().SetSprintf ( FLOAT, fPeriod );
14329 	iLastAccess = iCur - dDash.m_iLastAnswerTime;
14330 	fPeriod = (float)iLastAccess/1000000.0f;
14331 	if ( dStatus.MatchAddVa ( "%s_lastanswer", sPrefix ) )
14332 		dStatus.Add().SetSprintf ( FLOAT, fPeriod );
14333 	uint64_t iLastTimer = dDash.m_iLastAnswerTime-dDash.m_iLastQueryTime;
14334 	if ( dStatus.MatchAddVa ( "%s_lastperiodmsec", sPrefix ) )
14335 		dStatus.Add().SetSprintf ( FMT64, iLastTimer/1000 );
14336 	if ( dStatus.MatchAddVa ( "%s_errorsarow", sPrefix ) )
14337 		dStatus.Add().SetSprintf ( FMT64, dDash.m_iErrorsARow );
14338 
14339 	AgentDash_t dDashStat;
14340 	int iPeriods = 1;
14341 
14342 	while ( iPeriods>0 )
14343 	{
14344 		dDash.GetDashStat ( &dDashStat, iPeriods );
14345 		uint64_t uQueries = 0;
14346 
14347 		{
14348 			for ( int j=0; j<eMaxStat; ++j )
14349 				if ( j==eTotalMsecs ) // hack. Avoid microseconds in human-readable statistic
14350 				{
14351 					if ( dStatus.MatchAddVa ( "%s_%dperiods_msecsperqueryy", sPrefix, iPeriods ) )
14352 					{
14353 						if ( uQueries>0 )
14354 						{
14355 							float fAverageLatency = (float)(( dDashStat.m_iStats[eTotalMsecs] / 1000.0 ) / uQueries );
14356 							dStatus.Add().SetSprintf ( FLOAT, fAverageLatency );
14357 						} else
14358 							dStatus.Add("n/a");
14359 					}
14360 				} else
14361 				{
14362 					if ( dStatus.MatchAddVa ( "%s_%dperiods_%s", sPrefix, iPeriods, dDashStat.m_sNames[j] ) )
14363 						dStatus.Add().SetSprintf ( FMT64, dDashStat.m_iStats[j] );
14364 					uQueries += dDashStat.m_iStats[j];
14365 				}
14366 		}
14367 
14368 		if ( iPeriods==1 )
14369 			iPeriods = 5;
14370 		else if ( iPeriods==5 )
14371 			iPeriods = STATS_DASH_TIME;
14372 		else if ( iPeriods==STATS_DASH_TIME )
14373 			iPeriods = -1;
14374 	}
14375 	g_tStatsMutex.Unlock ();
14376 }
14377 
BuildDistIndexStatus(VectorLike & dStatus,const CSphString & sIndex)14378 void BuildDistIndexStatus ( VectorLike & dStatus, const CSphString& sIndex )
14379 {
14380 	if ( !g_pStats )
14381 	{
14382 		dStatus.Add("No distrindex status");
14383 		dStatus.Add("Error");
14384 		return;
14385 	}
14386 
14387 	g_tDistLock.Lock();
14388 	DistributedIndex_t * pDistr = g_hDistIndexes(sIndex);
14389 	g_tDistLock.Unlock();
14390 
14391 	if ( !pDistr )
14392 	{
14393 		if ( dStatus.MatchAdd ( "status_error" ) )
14394 			dStatus.Add().SetSprintf ( "No such index: %s", sIndex.cstr() );
14395 		return;
14396 	}
14397 
14398 	ARRAY_FOREACH ( i, pDistr->m_dLocal )
14399 	{
14400 		if ( dStatus.MatchAddVa ( "dstindex_local_%d", i+1 ) )
14401 			dStatus.Add ( pDistr->m_dLocal[i].cstr() );
14402 	}
14403 
14404 	CSphString sKey;
14405 	ARRAY_FOREACH ( i, pDistr->m_dAgents )
14406 	{
14407 		MetaAgentDesc_t & tAgents = pDistr->m_dAgents[i];
14408 		if ( dStatus.MatchAddVa ( "dstindex_%d_is_ha", i+1 ) )
14409 			dStatus.Add ( tAgents.IsHA()? "1": "0" );
14410 
14411 		ARRAY_FOREACH ( j, tAgents.GetAgents() )
14412 		{
14413 			if ( tAgents.IsHA() )
14414 				sKey.SetSprintf ( "dstindex_%dmirror%d", i+1, j+1 );
14415 			else
14416 				sKey.SetSprintf ( "dstindex_%dagent", i+1 );
14417 
14418 			const AgentDesc_t & dDesc = tAgents.GetAgents()[j];
14419 
14420 			if ( dStatus.MatchAddVa ( "%s_id", sKey.cstr() ) )
14421 				dStatus.Add().SetSprintf ( "%s:%s", dDesc.GetName().cstr(), dDesc.m_sIndexes.cstr() );
14422 
14423 			if ( tAgents.IsHA() && dStatus.MatchAddVa ( "%s_probability_weight", sKey.cstr() ) )
14424 				dStatus.Add().SetSprintf ( "%d", (DWORD)(tAgents.GetWeights()[j]) ); // FIXME! IPC unsafe, if critical need to be locked.
14425 
14426 			if ( dStatus.MatchAddVa ( "%s_is_blackhole", sKey.cstr() ) )
14427 				dStatus.Add ( dDesc.m_bBlackhole ? "1" : "0" );
14428 
14429 			if ( dStatus.MatchAddVa ( "%s_is_persistent", sKey.cstr() ) )
14430 				dStatus.Add ( dDesc.m_bPersistent ? "1" : "0" );
14431 		}
14432 	}
14433 }
14434 
BuildAgentStatus(VectorLike & dStatus,const CSphString & sAgent)14435 void BuildAgentStatus ( VectorLike & dStatus, const CSphString& sAgent )
14436 {
14437 	if ( !g_pStats )
14438 	{
14439 		dStatus.Add("No agent status");
14440 		dStatus.Add("Error");
14441 		return;
14442 	}
14443 
14444 	if ( !sAgent.IsEmpty() )
14445 	{
14446 		if ( g_hDistIndexes.Exists(sAgent) )
14447 			BuildDistIndexStatus ( dStatus, sAgent );
14448 		else
14449 			BuildOneAgentStatus ( dStatus, sAgent );
14450 		return;
14451 	}
14452 
14453 	dStatus.m_sColKey = "Key";
14454 
14455 	if ( dStatus.MatchAdd ( "status_period_seconds" ) )
14456 		dStatus.Add().SetSprintf ( "%d", g_uHAPeriodKarma );
14457 	if ( dStatus.MatchAdd ( "status_stored_periods" ) )
14458 		dStatus.Add().SetSprintf ( "%d", STATS_DASH_TIME );
14459 
14460 	CSphVector<CSphNamedInt> dAgents;
14461 	g_tStatsMutex.Lock ();
14462 	g_pStats->m_hDashBoard.IterateStart ();
14463 	while ( g_pStats->m_hDashBoard.IterateNext() )
14464 	{
14465 		CSphNamedInt & tAgent = dAgents.Add();
14466 		tAgent.m_iValue = g_pStats->m_hDashBoard.IterateGet();
14467 		tAgent.m_sName = g_pStats->m_hDashBoard.IterateGetKey();
14468 	}
14469 	g_tStatsMutex.Unlock ();
14470 
14471 	CSphString sPrefix;
14472 	ARRAY_FOREACH ( i, dAgents )
14473 	{
14474 		sPrefix.SetSprintf ( "ag_%d", dAgents[i].m_iValue );
14475 		BuildOneAgentStatus ( dStatus, dAgents[i].m_sName, sPrefix.cstr() );
14476 	}
14477 }
14478 
14479 
14480 
AddIOStatsToMeta(VectorLike & dStatus,const CSphIOStats & tStats,const char * sPrefix)14481 static void AddIOStatsToMeta ( VectorLike & dStatus, const CSphIOStats & tStats, const char * sPrefix )
14482 {
14483 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_read_time" ) )
14484 		dStatus.Add().SetSprintf ( "%d.%03d", (int)( tStats.m_iReadTime/1000 ), (int)( tStats.m_iReadTime%1000 ) );
14485 
14486 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_read_ops" ) )
14487 		dStatus.Add().SetSprintf ( "%u", tStats.m_iReadOps );
14488 
14489 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_read_kbytes" ) )
14490 		dStatus.Add().SetSprintf ( "%d.%d", (int)( tStats.m_iReadBytes/1024 ), (int)( tStats.m_iReadBytes%1024 )/100 );
14491 
14492 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_write_time" ) )
14493 		dStatus.Add().SetSprintf ( "%d.%03d", (int)( tStats.m_iWriteTime/1000 ), (int)( tStats.m_iWriteTime%1000 ) );
14494 
14495 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_write_ops" ) )
14496 		dStatus.Add().SetSprintf ( "%u", tStats.m_iWriteOps );
14497 
14498 	if ( dStatus.MatchAddVa ( "%s%s", sPrefix, "io_write_kbytes" ) )
14499 		dStatus.Add().SetSprintf ( "%d.%d", (int)( tStats.m_iWriteBytes/1024 ), (int)( tStats.m_iWriteBytes%1024 )/100 );
14500 }
14501 
BuildMeta(VectorLike & dStatus,const CSphQueryResultMeta & tMeta)14502 void BuildMeta ( VectorLike & dStatus, const CSphQueryResultMeta & tMeta )
14503 {
14504 	if ( !tMeta.m_sError.IsEmpty() && dStatus.MatchAdd ( "error" ) )
14505 		dStatus.Add ( tMeta.m_sError );
14506 
14507 	if ( !tMeta.m_sWarning.IsEmpty() && dStatus.MatchAdd ( "warning" ) )
14508 		dStatus.Add ( tMeta.m_sWarning );
14509 
14510 	if ( dStatus.MatchAdd ( "total" ) )
14511 		dStatus.Add().SetSprintf ( "%d", tMeta.m_iMatches );
14512 
14513 	if ( dStatus.MatchAdd ( "total_found" ) )
14514 		dStatus.Add().SetSprintf ( INT64_FMT, tMeta.m_iTotalMatches );
14515 
14516 	if ( dStatus.MatchAdd ( "time" ) )
14517 		dStatus.Add().SetSprintf ( "%d.%03d", tMeta.m_iQueryTime/1000, tMeta.m_iQueryTime%1000 );
14518 
14519 	if ( g_bCpuStats )
14520 	{
14521 		if ( dStatus.MatchAdd ( "cpu_time" ) )
14522 			dStatus.Add().SetSprintf ( "%d.%03d", (int)( tMeta.m_iCpuTime/1000 ), (int)( tMeta.m_iCpuTime%1000 ) );
14523 
14524 		if ( dStatus.MatchAdd ( "agents_cpu_time" ) )
14525 			dStatus.Add().SetSprintf ( "%d.%03d", (int)( tMeta.m_iAgentCpuTime/1000 ), (int)( tMeta.m_iAgentCpuTime%1000 ) );
14526 	}
14527 
14528 	if ( g_bIOStats )
14529 	{
14530 		AddIOStatsToMeta ( dStatus, tMeta.m_tIOStats, "" );
14531 		AddIOStatsToMeta ( dStatus, tMeta.m_tAgentIOStats, "agent_" );
14532 	}
14533 
14534 	if ( tMeta.m_bHasPrediction )
14535 	{
14536 		if ( dStatus.MatchAdd ( "local_fetched_docs" ) )
14537 			dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iFetchedDocs );
14538 		if ( dStatus.MatchAdd ( "local_fetched_hits" ) )
14539 			dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iFetchedHits );
14540 		if ( dStatus.MatchAdd ( "local_fetched_skips" ) )
14541 			dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iSkips );
14542 
14543 		if ( dStatus.MatchAdd ( "predicted_time" ) )
14544 			dStatus.Add().SetSprintf ( "%lld", INT64 ( tMeta.m_iPredictedTime ) );
14545 		if ( tMeta.m_iAgentPredictedTime && dStatus.MatchAdd ( "dist_predicted_time" ) )
14546 			dStatus.Add().SetSprintf ( "%lld", INT64 ( tMeta.m_iAgentPredictedTime ) );
14547 		if ( tMeta.m_iAgentFetchedDocs || tMeta.m_iAgentFetchedHits || tMeta.m_iAgentFetchedSkips )
14548 		{
14549 			if ( dStatus.MatchAdd ( "dist_fetched_docs" ) )
14550 				dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iFetchedDocs + tMeta.m_iAgentFetchedDocs );
14551 			if ( dStatus.MatchAdd ( "dist_fetched_hits" ) )
14552 				dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iFetchedHits + tMeta.m_iAgentFetchedHits );
14553 			if ( dStatus.MatchAdd ( "dist_fetched_skips" ) )
14554 				dStatus.Add().SetSprintf ( "%d", tMeta.m_tStats.m_iSkips + tMeta.m_iAgentFetchedSkips );
14555 		}
14556 	}
14557 
14558 
14559 	int iWord = 0;
14560 	tMeta.m_hWordStats.IterateStart();
14561 	while ( tMeta.m_hWordStats.IterateNext() )
14562 	{
14563 		const CSphQueryResultMeta::WordStat_t & tStat = tMeta.m_hWordStats.IterateGet();
14564 
14565 		if ( dStatus.MatchAddVa ( "keyword[%d]", iWord ) )
14566 			dStatus.Add ( tMeta.m_hWordStats.IterateGetKey() );
14567 
14568 		if ( dStatus.MatchAddVa ( "docs[%d]", iWord ) )
14569 			dStatus.Add().SetSprintf ( INT64_FMT, tStat.m_iDocs );
14570 
14571 		if ( dStatus.MatchAddVa ( "hits[%d]", iWord ) )
14572 			dStatus.Add().SetSprintf ( INT64_FMT, tStat.m_iHits );
14573 
14574 		iWord++;
14575 	}
14576 }
14577 
14578 
HandleCommandStatus(int iSock,int iVer,InputBuffer_c & tReq)14579 void HandleCommandStatus ( int iSock, int iVer, InputBuffer_c & tReq )
14580 {
14581 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_STATUS, tReq ) )
14582 		return;
14583 
14584 	if ( !g_pStats )
14585 	{
14586 		tReq.SendErrorReply ( "performance counters disabled" );
14587 		return;
14588 	}
14589 
14590 	bool bGlobalStat = tReq.GetDword ()!=0;
14591 
14592 	VectorLike dStatus;
14593 
14594 	if ( bGlobalStat )
14595 		BuildStatus ( dStatus );
14596 	else
14597 	{
14598 		g_tLastMetaMutex.Lock();
14599 		BuildMeta ( dStatus, g_tLastMeta );
14600 		if ( g_pStats->m_iPredictedTime || g_pStats->m_iAgentPredictedTime )
14601 		{
14602 			if ( dStatus.MatchAdd ( "predicted_time" ) )
14603 				dStatus.Add().SetSprintf ( INT64_FMT, g_pStats->m_iPredictedTime );
14604 			if ( dStatus.MatchAdd ( "dist_predicted_time" ) )
14605 				dStatus.Add().SetSprintf ( INT64_FMT, g_pStats->m_iAgentPredictedTime );
14606 		}
14607 		g_tLastMetaMutex.Unlock();
14608 	}
14609 
14610 	int iRespLen = 8; // int rows, int cols
14611 	ARRAY_FOREACH ( i, dStatus )
14612 		iRespLen += 4 + strlen ( dStatus[i].cstr() );
14613 
14614 	NetOutputBuffer_c tOut ( iSock );
14615 	tOut.SendWord ( SEARCHD_OK );
14616 	tOut.SendWord ( VER_COMMAND_STATUS );
14617 	tOut.SendInt ( iRespLen );
14618 
14619 	tOut.SendInt ( dStatus.GetLength()/2 ); // rows
14620 	tOut.SendInt ( 2 ); // cols
14621 	ARRAY_FOREACH ( i, dStatus )
14622 		tOut.SendString ( dStatus[i].cstr() );
14623 
14624 	tOut.Flush ();
14625 	assert ( tOut.GetError()==true || tOut.GetSentCount()==8+iRespLen );
14626 }
14627 
14628 //////////////////////////////////////////////////////////////////////////
14629 // FLUSH HANDLER
14630 //////////////////////////////////////////////////////////////////////////
14631 
CommandFlush(CSphString & sError)14632 static int CommandFlush ( CSphString & sError )
14633 {
14634 	if ( g_eWorkers==MPM_NONE )
14635 	{
14636 		sError = ( "console mode, command ignored" );
14637 	} else
14638 	{
14639 		// force a check in head process, and wait it until completes
14640 		// FIXME! semi active wait..
14641 		sphLogDebug ( "attrflush: forcing check, tag=%d", g_pFlush->m_iFlushTag );
14642 		g_pFlush->m_bForceCheck = true;
14643 		while ( g_pFlush->m_bForceCheck )
14644 			sphSleepMsec ( 1 );
14645 
14646 		// if we are flushing now, wait until flush completes
14647 		while ( g_pFlush->m_bFlushing )
14648 			sphSleepMsec ( 10 );
14649 		sphLogDebug ( "attrflush: check finished, tag=%d", g_pFlush->m_iFlushTag );
14650 	}
14651 
14652 	return g_pFlush->m_iFlushTag;
14653 }
14654 
HandleCommandFlush(int iSock,int iVer,InputBuffer_c & tReq)14655 void HandleCommandFlush ( int iSock, int iVer, InputBuffer_c & tReq )
14656 {
14657 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_FLUSHATTRS, tReq ) )
14658 		return;
14659 
14660 	CSphString sError;
14661 	int iTag = CommandFlush ( sError );
14662 	if ( !sError.IsEmpty() )
14663 		sphLogDebug ( "attrflush: %s", sError.cstr() );
14664 
14665 	// return last flush tag, just for the fun of it
14666 	NetOutputBuffer_c tOut ( iSock );
14667 	tOut.SendWord ( SEARCHD_OK );
14668 	tOut.SendWord ( VER_COMMAND_FLUSHATTRS );
14669 	tOut.SendInt ( 4 ); // resplen, 1 dword
14670 	tOut.SendInt ( iTag );
14671 	tOut.Flush ();
14672 	assert ( tOut.GetError()==true || tOut.GetSentCount()==12 ); // 8+resplen
14673 }
14674 
14675 
14676 /////////////////////////////////////////////////////////////////////////////
14677 // GENERAL HANDLER
14678 /////////////////////////////////////////////////////////////////////////////
14679 
14680 #define THD_STATE(_state) \
14681 { \
14682 	DISABLE_CONST_COND_CHECK \
14683 	if ( pThd ) \
14684 	{ \
14685 		pThd->m_eThdState = _state; \
14686 		pThd->m_tmStart = sphMicroTimer(); \
14687 		if_const ( _state==THD_NET_IDLE ) \
14688 			pThd->m_dBuf[0] = '\0'; \
14689 	} \
14690 	ENABLE_CONST_COND_CHECK \
14691 }
14692 
14693 void HandleCommandSphinxql ( int iSock, int iVer, InputBuffer_c & tReq ); // definition is below
14694 void StatCountCommand ( int iCmd, int iCount=1 );
14695 void HandleCommandUserVar ( int iSock, int iVer, NetInputBuffer_c & tReq );
14696 
14697 /// ping/pong exchange over API
HandleCommandPing(int iSock,int iVer,InputBuffer_c & tReq)14698 void HandleCommandPing ( int iSock, int iVer, InputBuffer_c & tReq )
14699 {
14700 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_PING, tReq ) )
14701 		return;
14702 
14703 	// parse ping
14704 	int iCookie = tReq.GetInt();
14705 
14706 	// return last flush tag, just for the fun of it
14707 	NetOutputBuffer_c tOut ( iSock );
14708 	tOut.SendWord ( SEARCHD_OK );
14709 	tOut.SendWord ( VER_COMMAND_PING );
14710 	tOut.SendInt ( 4 ); // resplen, 1 dword
14711 	tOut.SendInt ( iCookie ); // echo the cookie back
14712 	tOut.Flush ();
14713 	assert ( tOut.GetError()==true || tOut.GetSentCount()==12 ); // 8+resplen
14714 }
14715 
DecPersCount()14716 inline void DecPersCount()
14717 {
14718 	CSphScopedLockedShare<InterWorkerStorage> dPersNum ( *g_pPersistentInUse );
14719 	DWORD& uPers = dPersNum.SharedValue<DWORD>();
14720 	if ( uPers )
14721 		--uPers;
14722 }
14723 
HandleClientSphinx(int iSock,const char * sClientIP,ThdDesc_t * pThd)14724 void HandleClientSphinx ( int iSock, const char * sClientIP, ThdDesc_t * pThd )
14725 {
14726 	MEMORY ( MEM_API_HANDLE );
14727 	THD_STATE ( THD_HANDSHAKE );
14728 
14729 	bool bPersist = false;
14730 	int iTimeout = g_iReadTimeout; // wait 5 sec until first command
14731 	NetInputBuffer_c tBuf ( iSock );
14732 	int64_t iCID = ( pThd ? pThd->m_iConnID : g_iConnID );
14733 
14734 	// send my version
14735 	DWORD uServer = htonl ( SPHINX_SEARCHD_PROTO );
14736 	if ( sphSockSend ( iSock, (char*)&uServer, sizeof(DWORD) )!=sizeof(DWORD) )
14737 	{
14738 		sphWarning ( "failed to send server version (client=%s(" INT64_FMT "))", sClientIP, iCID );
14739 		return;
14740 	}
14741 
14742 	// get client version and request
14743 	tBuf.ReadFrom ( 4 ); // FIXME! magic
14744 	int iMagic = tBuf.GetInt (); // client version is for now unused
14745 
14746 	sphLogDebugv ( "conn %s(" INT64_FMT "): got handshake, major v.%d, err %d", sClientIP, iCID, iMagic, (int)tBuf.GetError() );
14747 	if ( tBuf.GetError() )
14748 	{
14749 		sphLogDebugv ( "conn %s(" INT64_FMT "): exiting on handshake error", sClientIP, iCID );
14750 		return;
14751 	}
14752 
14753 	int iPconnIdle = 0;
14754 	do
14755 	{
14756 		// in "persistent connection" mode, we want interruptible waits
14757 		// so that the worker child could be forcibly restarted
14758 		//
14759 		// currently, the only signal allowed to interrupt this read is SIGTERM
14760 		// letting SIGHUP interrupt causes trouble under query/rotation pressure
14761 		// see sphSockRead() and ReadFrom() for details
14762 		THD_STATE ( THD_NET_IDLE );
14763 		bool bCommand = tBuf.ReadFrom ( 8, iTimeout, bPersist );
14764 
14765 		// on SIGTERM, bail unconditionally and immediately, at all times
14766 		if ( !bCommand && g_bGotSigterm )
14767 		{
14768 			sphLogDebugv ( "conn %s(" INT64_FMT "): bailing on SIGTERM", sClientIP, iCID );
14769 			break;
14770 		}
14771 
14772 		// on SIGHUP vs pconn, bail if a pconn was idle for 1 sec
14773 		if ( bPersist && !bCommand && g_bGotSighup && sphSockPeekErrno()==ETIMEDOUT )
14774 		{
14775 			sphLogDebugv ( "conn %s(" INT64_FMT "): bailing idle pconn on SIGHUP", sClientIP, iCID );
14776 			break;
14777 		}
14778 
14779 		// on pconn that was idle for 300 sec (client_timeout), bail
14780 		if ( bPersist && !bCommand && sphSockPeekErrno()==ETIMEDOUT )
14781 		{
14782 			iPconnIdle += iTimeout;
14783 			if ( iPconnIdle>=g_iClientTimeout )
14784 			{
14785 				sphLogDebugv ( "conn %s(" INT64_FMT "): bailing idle pconn on client_timeout", sClientIP, iCID );
14786 				break;
14787 			}
14788 			continue;
14789 		} else
14790 			iPconnIdle = 0;
14791 
14792 		// on any other signals vs pconn, ignore and keep looping
14793 		// (redundant for now, as the only allowed interruption is SIGTERM, but.. let's keep it)
14794 		if ( bPersist && !bCommand && tBuf.IsIntr() )
14795 			continue;
14796 
14797 		// okay, signal related mess should be over, try to parse the command
14798 		// (but some other socket error still might had happened, so beware)
14799 		THD_STATE ( THD_NET_READ );
14800 		int iCommand = tBuf.GetWord ();
14801 		int iCommandVer = tBuf.GetWord ();
14802 		int iLength = tBuf.GetInt ();
14803 		if ( tBuf.GetError() )
14804 		{
14805 			// under high load, there can be pretty frequent accept() vs connect() timeouts
14806 			// lets avoid agent log flood
14807 			//
14808 			// sphWarning ( "failed to receive client version and request (client=%s, error=%s)", sClientIP, sphSockError() );
14809 			sphLogDebugv ( "conn %s(" INT64_FMT "): bailing on failed request header (sockerr=%s)", sClientIP, iCID, sphSockError() );
14810 			if ( bPersist )
14811 				DecPersCount();
14812 			return;
14813 		}
14814 
14815 		// check request
14816 		if ( iCommand<0 || iCommand>=SEARCHD_COMMAND_TOTAL
14817 			|| iLength<0 || iLength>g_iMaxPacketSize )
14818 		{
14819 			// unknown command, default response header
14820 			tBuf.SendErrorReply ( "invalid command (code=%d, len=%d)", iCommand, iLength );
14821 
14822 			// if request length is insane, low level comm is broken, so we bail out
14823 			if ( iLength<0 || iLength>g_iMaxPacketSize )
14824 				sphWarning ( "ill-formed client request (length=%d out of bounds)", iLength );
14825 
14826 			// if command is insane, low level comm is broken, so we bail out
14827 			if ( iCommand<0 || iCommand>=SEARCHD_COMMAND_TOTAL )
14828 				sphWarning ( "ill-formed client request (command=%d, SEARCHD_COMMAND_TOTAL=%d)", iCommand, SEARCHD_COMMAND_TOTAL );
14829 
14830 			if ( bPersist )
14831 				DecPersCount();
14832 
14833 			return;
14834 		}
14835 
14836 		// count commands
14837 		StatCountCommand ( iCommand );
14838 
14839 		// get request body
14840 		assert ( iLength>=0 && iLength<=g_iMaxPacketSize );
14841 		if ( iLength && !tBuf.ReadFrom ( iLength ) )
14842 		{
14843 			sphWarning ( "failed to receive client request body (client=%s(" INT64_FMT "), exp=%d, error='%s')",
14844 				sClientIP, iCID, iLength, sphSockError() );
14845 
14846 			if ( bPersist )
14847 				DecPersCount();
14848 
14849 			return;
14850 		}
14851 
14852 		// set on query guard
14853 		CrashQuery_t tCrashQuery;
14854 		tCrashQuery.m_pQuery = tBuf.GetBufferPtr();
14855 		tCrashQuery.m_iSize = iLength;
14856 		tCrashQuery.m_bMySQL = false;
14857 		tCrashQuery.m_uCMD = (WORD)iCommand;
14858 		tCrashQuery.m_uVer = (WORD)iCommandVer;
14859 		SphCrashLogger_c::SetLastQuery ( tCrashQuery );
14860 
14861 		// handle known commands
14862 		assert ( iCommand>=0 && iCommand<SEARCHD_COMMAND_TOTAL );
14863 
14864 		if ( pThd )
14865 			pThd->m_sCommand = g_dApiCommands[iCommand];
14866 		THD_STATE ( THD_QUERY );
14867 
14868 		sphLogDebugv ( "conn %s(" INT64_FMT "): got command %d, handling", sClientIP, iCID, iCommand );
14869 		switch ( iCommand )
14870 		{
14871 			case SEARCHD_COMMAND_SEARCH:	HandleCommandSearch ( iSock, iCommandVer, tBuf, pThd ); break;
14872 			case SEARCHD_COMMAND_EXCERPT:	HandleCommandExcerpt ( iSock, iCommandVer, tBuf, pThd ); break;
14873 			case SEARCHD_COMMAND_KEYWORDS:	HandleCommandKeywords ( iSock, iCommandVer, tBuf ); break;
14874 			case SEARCHD_COMMAND_UPDATE:	HandleCommandUpdate ( iSock, iCommandVer, tBuf ); break;
14875 			case SEARCHD_COMMAND_PERSIST:
14876 				{
14877 					bPersist = ( tBuf.GetInt()!=0 );
14878 					iTimeout = 1;
14879 					sphLogDebugv ( "conn %s(" INT64_FMT "): pconn is now %s", sClientIP, iCID, bPersist ? "on" : "off" );
14880 					CSphScopedLockedShare<InterWorkerStorage> dPersNum ( *g_pPersistentInUse );
14881 					DWORD uMaxChildren = (g_eWorkers==MPM_PREFORK)?g_iPreforkChildren:g_iMaxChildren;
14882 					DWORD& uPers = dPersNum.SharedValue<DWORD>();
14883 					if ( bPersist )
14884 					{
14885 						if ( uMaxChildren && uPers+g_uAtLeastUnpersistent>=uMaxChildren )
14886 							bPersist = false; // this node can't became persistent
14887 						else
14888 							++uPers;
14889 					} else
14890 					{
14891 						if ( uPers )
14892 							--uPers;
14893 					}
14894 				}
14895 				break;
14896 			case SEARCHD_COMMAND_STATUS:	HandleCommandStatus ( iSock, iCommandVer, tBuf ); break;
14897 			case SEARCHD_COMMAND_FLUSHATTRS:HandleCommandFlush ( iSock, iCommandVer, tBuf ); break;
14898 			case SEARCHD_COMMAND_SPHINXQL:	HandleCommandSphinxql ( iSock, iCommandVer, tBuf ); break;
14899 			case SEARCHD_COMMAND_PING:		HandleCommandPing ( iSock, iCommandVer, tBuf ); break;
14900 			case SEARCHD_COMMAND_UVAR:		HandleCommandUserVar ( iSock, iCommandVer, tBuf ); break;
14901 			default:						assert ( 0 && "INTERNAL ERROR: unhandled command" ); break;
14902 		}
14903 
14904 		// set off query guard
14905 		SphCrashLogger_c::SetLastQuery ( CrashQuery_t() );
14906 	} while ( bPersist );
14907 
14908 	if ( bPersist )
14909 		DecPersCount();
14910 
14911 	sphLogDebugv ( "conn %s(" INT64_FMT "): exiting", sClientIP, iCID );
14912 }
14913 
14914 //////////////////////////////////////////////////////////////////////////
14915 // MYSQLD PRETENDER
14916 //////////////////////////////////////////////////////////////////////////
14917 
14918 // our copy of enum_field_types
14919 // we can't rely on mysql_com.h because it might be unavailable
14920 //
14921 // MYSQL_TYPE_DECIMAL = 0
14922 // MYSQL_TYPE_TINY = 1
14923 // MYSQL_TYPE_SHORT = 2
14924 // MYSQL_TYPE_LONG = 3
14925 // MYSQL_TYPE_FLOAT = 4
14926 // MYSQL_TYPE_DOUBLE = 5
14927 // MYSQL_TYPE_NULL = 6
14928 // MYSQL_TYPE_TIMESTAMP = 7
14929 // MYSQL_TYPE_LONGLONG = 8
14930 // MYSQL_TYPE_INT24 = 9
14931 // MYSQL_TYPE_DATE = 10
14932 // MYSQL_TYPE_TIME = 11
14933 // MYSQL_TYPE_DATETIME = 12
14934 // MYSQL_TYPE_YEAR = 13
14935 // MYSQL_TYPE_NEWDATE = 14
14936 // MYSQL_TYPE_VARCHAR = 15
14937 // MYSQL_TYPE_BIT = 16
14938 // MYSQL_TYPE_NEWDECIMAL = 246
14939 // MYSQL_TYPE_ENUM = 247
14940 // MYSQL_TYPE_SET = 248
14941 // MYSQL_TYPE_TINY_BLOB = 249
14942 // MYSQL_TYPE_MEDIUM_BLOB = 250
14943 // MYSQL_TYPE_LONG_BLOB = 251
14944 // MYSQL_TYPE_BLOB = 252
14945 // MYSQL_TYPE_VAR_STRING = 253
14946 // MYSQL_TYPE_STRING = 254
14947 // MYSQL_TYPE_GEOMETRY = 255
14948 
14949 enum MysqlColumnType_e
14950 {
14951 	MYSQL_COL_DECIMAL	= 0,
14952 	MYSQL_COL_LONG		= 3,
14953 	MYSQL_COL_FLOAT	= 4,
14954 	MYSQL_COL_LONGLONG	= 8,
14955 	MYSQL_COL_STRING	= 254
14956 };
14957 
14958 
14959 
SendMysqlFieldPacket(NetOutputBuffer_c & tOut,BYTE uPacketID,const char * sCol,MysqlColumnType_e eType)14960 void SendMysqlFieldPacket ( NetOutputBuffer_c & tOut, BYTE uPacketID, const char * sCol, MysqlColumnType_e eType )
14961 {
14962 	const char * sDB = "";
14963 	const char * sTable = "";
14964 
14965 	int iLen = 17 + MysqlPackedLen(sDB) + 2*( MysqlPackedLen(sTable) + MysqlPackedLen(sCol) );
14966 
14967 	int iColLen = 0;
14968 	switch ( eType )
14969 	{
14970 		case MYSQL_COL_DECIMAL:		iColLen = 20; break;
14971 		case MYSQL_COL_LONG:		iColLen = 11; break;
14972 		case MYSQL_COL_FLOAT:		iColLen = 20; break;
14973 		case MYSQL_COL_LONGLONG:	iColLen = 20; break;
14974 		case MYSQL_COL_STRING:		iColLen = 255; break;
14975 	}
14976 
14977 	tOut.SendLSBDword ( (uPacketID<<24) + iLen );
14978 	tOut.SendMysqlString ( "def" ); // catalog
14979 	tOut.SendMysqlString ( sDB ); // db
14980 	tOut.SendMysqlString ( sTable ); // table
14981 	tOut.SendMysqlString ( sTable ); // org_table
14982 	tOut.SendMysqlString ( sCol ); // name
14983 	tOut.SendMysqlString ( sCol ); // org_name
14984 
14985 	tOut.SendByte ( 12 ); // filler, must be 12 (following pseudo-string length)
14986 	tOut.SendByte ( 0x21 ); // charset_nr, 0x21 is utf8
14987 	tOut.SendByte ( 0 ); // charset_nr
14988 	tOut.SendLSBDword ( iColLen ); // length
14989 	tOut.SendByte ( BYTE(eType) ); // type (0=decimal)
14990 	tOut.SendWord ( 0 ); // flags
14991 	tOut.SendByte ( 0 ); // decimals
14992 	tOut.SendWord ( 0 ); // filler
14993 }
14994 
14995 
14996 // from mysqld_error.h
14997 enum MysqlErrors_e
14998 {
14999 	MYSQL_ERR_UNKNOWN_COM_ERROR			= 1047,
15000 	MYSQL_ERR_SERVER_SHUTDOWN			= 1053,
15001 	MYSQL_ERR_PARSE_ERROR				= 1064,
15002 	MYSQL_ERR_FIELD_SPECIFIED_TWICE		= 1110,
15003 	MYSQL_ERR_NO_SUCH_TABLE				= 1146
15004 };
15005 
15006 
SendMysqlErrorPacket(NetOutputBuffer_c & tOut,BYTE uPacketID,const char * sStmt,const char * sError,MysqlErrors_e iErr=MYSQL_ERR_PARSE_ERROR)15007 void SendMysqlErrorPacket ( NetOutputBuffer_c & tOut, BYTE uPacketID, const char * sStmt,
15008 	const char * sError, MysqlErrors_e iErr = MYSQL_ERR_PARSE_ERROR )
15009 {
15010 	if ( sError==NULL )
15011 		sError = "(null)";
15012 
15013 	LogSphinxqlError ( sStmt, sError );
15014 
15015 	int iErrorLen = strlen(sError)+1; // including the trailing zero
15016 	int iLen = 9 + iErrorLen;
15017 	int iError = iErr; // pretend to be mysql syntax error for now
15018 
15019 	// send packet header
15020 	tOut.SendLSBDword ( (uPacketID<<24) + iLen );
15021 	tOut.SendByte ( 0xff ); // field count, always 0xff for error packet
15022 	tOut.SendByte ( (BYTE)( iError & 0xff ) );
15023 	tOut.SendByte ( (BYTE)( iError>>8 ) );
15024 
15025 	// send sqlstate (1 byte marker, 5 byte state)
15026 	switch ( iErr )
15027 	{
15028 		case MYSQL_ERR_SERVER_SHUTDOWN:
15029 		case MYSQL_ERR_UNKNOWN_COM_ERROR:
15030 			tOut.SendBytes ( "#08S01", 6 );
15031 			break;
15032 		case MYSQL_ERR_NO_SUCH_TABLE:
15033 			tOut.SendBytes ( "#42S02", 6 );
15034 			break;
15035 		default:
15036 			tOut.SendBytes ( "#42000", 6 );
15037 			break;
15038 	}
15039 
15040 	// send error message
15041 	tOut.SendBytes ( sError, iErrorLen );
15042 }
15043 
SendMysqlEofPacket(NetOutputBuffer_c & tOut,BYTE uPacketID,int iWarns,bool bMoreResults=false)15044 void SendMysqlEofPacket ( NetOutputBuffer_c & tOut, BYTE uPacketID, int iWarns, bool bMoreResults=false )
15045 {
15046 	if ( iWarns<0 ) iWarns = 0;
15047 	if ( iWarns>65535 ) iWarns = 65535;
15048 	if ( bMoreResults )
15049 		iWarns |= ( SPH_MYSQL_FLAG_MORE_RESULTS<<16 );
15050 
15051 	tOut.SendLSBDword ( (uPacketID<<24) + 5 );
15052 	tOut.SendByte ( 0xfe );
15053 	tOut.SendLSBDword ( iWarns ); // N warnings, 0 status
15054 }
15055 
15056 
SendMysqlOkPacket(NetOutputBuffer_c & tOut,BYTE uPacketID,int iAffectedRows=0,int iWarns=0,const char * sMessage=NULL,bool bMoreResults=false)15057 void SendMysqlOkPacket ( NetOutputBuffer_c & tOut, BYTE uPacketID, int iAffectedRows=0, int iWarns=0, const char * sMessage=NULL, bool bMoreResults=false )
15058 {
15059 	DWORD iInsert_id = 0;
15060 	char sVarLen[20] = {0}; // max 18 for packed number, +1 more just for fun
15061 	void * pBuf = sVarLen;
15062 	pBuf = MysqlPack ( pBuf, iAffectedRows );
15063 	pBuf = MysqlPack ( pBuf, iInsert_id );
15064 	int iLen = (char *) pBuf - sVarLen;
15065 
15066 	int iMsgLen = 0;
15067 	if ( sMessage )
15068 		iMsgLen = strlen(sMessage) + 1; // FIXME! does or doesn't the trailing zero necessary in Ok packet?
15069 
15070 	tOut.SendLSBDword ( (uPacketID<<24) + iLen + iMsgLen + 5);
15071 	tOut.SendByte ( 0 );				// ok packet
15072 	tOut.SendBytes ( sVarLen, iLen );	// packed affected rows & insert_id
15073 	if ( iWarns<0 ) iWarns = 0;
15074 	if ( iWarns>65535 ) iWarns = 65535;
15075 	DWORD uWarnStatus = iWarns<<16;
15076 	if ( bMoreResults ) // order of WORDs is opposite to EOF packet
15077 		uWarnStatus |= ( SPH_MYSQL_FLAG_MORE_RESULTS );
15078 	tOut.SendLSBDword ( uWarnStatus );		// 0 status, N warnings
15079 	tOut.SendBytes ( sMessage, iMsgLen );
15080 }
15081 
15082 
15083 //////////////////////////////////////////////////////////////////////////
15084 // Mysql row buffer and command handler
15085 #define SPH_MAX_NUMERIC_STR 64
15086 class SqlRowBuffer_c : public ISphNoncopyable
15087 {
15088 public:
15089 
SqlRowBuffer_c(BYTE * pPacketID,NetOutputBuffer_c * pOut)15090 	SqlRowBuffer_c ( BYTE * pPacketID, NetOutputBuffer_c * pOut )
15091 		: m_pBuf ( NULL )
15092 		, m_iLen ( 0 )
15093 		, m_iLimit ( sizeof ( m_dBuf ) )
15094 		, m_uPacketID ( *pPacketID )
15095 		, m_tOut ( *pOut )
15096 		, m_iSize ( 0 )
15097 	{}
15098 
~SqlRowBuffer_c()15099 	~SqlRowBuffer_c ()
15100 	{
15101 		SafeDeleteArray ( m_pBuf );
15102 	}
15103 
Reserve(int iLen)15104 	char * Reserve ( int iLen )
15105 	{
15106 		int iNewSize = m_iLen+iLen;
15107 		if ( iNewSize<=m_iLimit )
15108 			return Get();
15109 
15110 		int iNewLimit = Max ( m_iLimit*2, iNewSize );
15111 		char * pBuf = new char [iNewLimit];
15112 		memcpy ( pBuf, m_pBuf ? m_pBuf : m_dBuf, m_iLen );
15113 		SafeDeleteArray ( m_pBuf );
15114 		m_pBuf = pBuf;
15115 		m_iLimit = iNewLimit;
15116 		return Get();
15117 	}
15118 
Get()15119 	char * Get ()
15120 	{
15121 		return m_pBuf ? m_pBuf+m_iLen : m_dBuf+m_iLen;
15122 	}
15123 
Off(int iOff)15124 	char * Off ( int iOff )
15125 	{
15126 		assert ( iOff<m_iLimit );
15127 		return m_pBuf ? m_pBuf+iOff : m_dBuf+iOff;
15128 	}
15129 
Length() const15130 	int Length () const
15131 	{
15132 		return m_iLen;
15133 	}
15134 
IncPtr(int iLen)15135 	void IncPtr ( int iLen	)
15136 	{
15137 		assert ( m_iLen+iLen<=m_iLimit );
15138 		m_iLen += iLen;
15139 	}
15140 
Reset()15141 	void Reset ()
15142 	{
15143 		m_iLen = 0;
15144 	}
15145 
15146 	template < typename T>
PutNumeric(const char * sFormat,T tVal)15147 	void PutNumeric ( const char * sFormat, T tVal )
15148 	{
15149 		Reserve ( SPH_MAX_NUMERIC_STR );
15150 		int iLen = snprintf ( Get()+1, SPH_MAX_NUMERIC_STR-1, sFormat, tVal );
15151 		*Get() = BYTE(iLen);
15152 		IncPtr ( 1+iLen );
15153 	}
15154 
PutString(const char * sMsg)15155 	void PutString ( const char * sMsg )
15156 	{
15157 		int iLen = 0;
15158 		if ( sMsg && *sMsg )
15159 			iLen = strlen ( sMsg );
15160 
15161 		Reserve ( 9+iLen ); // 9 is taken from MysqlPack() implementation (max possible offset)
15162 		char * pBegin = Get();
15163 		char * pStr = (char *)MysqlPack ( pBegin, iLen );
15164 		if ( pStr>pBegin )
15165 		{
15166 			memcpy ( pStr, sMsg, iLen );
15167 			IncPtr ( ( pStr-pBegin )+iLen );
15168 		}
15169 	}
15170 
PutMicrosec(int64_t iUsec)15171 	void PutMicrosec ( int64_t iUsec )
15172 	{
15173 		iUsec = Max ( iUsec, 0 );
15174 		int iSec = (int)( iUsec / 1000000 );
15175 		int iFrac = (int)( iUsec % 1000000 );
15176 		Reserve ( 18 ); // 1..10 bytes for sec, 1 for dot, 6 for frac, 1 for len
15177 		int iLen = snprintf ( Get()+1, 18, "%d.%06d", iSec, iFrac ); //NOLINT
15178 		*Get() = BYTE(iLen);
15179 		IncPtr ( 1+iLen );
15180 	}
15181 
PutNULL()15182 	void PutNULL ()
15183 	{
15184 		Reserve ( 1 );
15185 		*( (BYTE *) Get() ) = 0xfb; // MySQL NULL is 0xfb at VLB length
15186 		IncPtr ( 1 );
15187 	}
15188 
15189 	/// more high level. Processing the whole tables.
15190 	// sends collected data, then reset
Commit()15191 	void Commit()
15192 	{
15193 		m_tOut.SendLSBDword ( ((m_uPacketID++)<<24) + ( Length() ) );
15194 		m_tOut.SendBytes ( Off ( 0 ), Length() );
15195 		Reset();
15196 	}
15197 
15198 	// wrappers for popular packets
Eof(bool bMoreResults=false,int iWarns=0)15199 	inline void Eof ( bool bMoreResults=false, int iWarns=0 )
15200 	{
15201 		SendMysqlEofPacket ( m_tOut, m_uPacketID++, iWarns, bMoreResults );
15202 	}
15203 
Error(const char * sStmt,const char * sError,MysqlErrors_e iErr=MYSQL_ERR_PARSE_ERROR)15204 	inline void Error ( const char * sStmt, const char * sError, MysqlErrors_e iErr = MYSQL_ERR_PARSE_ERROR )
15205 	{
15206 		SendMysqlErrorPacket ( m_tOut, m_uPacketID, sStmt, sError, iErr );
15207 	}
15208 
ErrorEx(MysqlErrors_e iErr,const char * sTemplate,...)15209 	inline void ErrorEx ( MysqlErrors_e iErr, const char * sTemplate, ... )
15210 	{
15211 		char sBuf[1024];
15212 		va_list ap;
15213 
15214 		va_start ( ap, sTemplate );
15215 		vsnprintf ( sBuf, sizeof(sBuf), sTemplate, ap );
15216 		va_end ( ap );
15217 
15218 		Error ( NULL, sBuf, iErr );
15219 	}
15220 
Ok(int iAffectedRows=0,int iWarns=0,const char * sMessage=NULL,bool bMoreResults=false)15221 	inline void Ok ( int iAffectedRows=0, int iWarns=0, const char * sMessage=NULL, bool bMoreResults=false )
15222 	{
15223 		SendMysqlOkPacket ( m_tOut, m_uPacketID, iAffectedRows, iWarns, sMessage, bMoreResults );
15224 		if ( bMoreResults )
15225 			m_uPacketID++;
15226 	}
15227 
15228 	// Header of the table with defined num of columns
HeadBegin(int iColumns)15229 	inline void HeadBegin ( int iColumns )
15230 	{
15231 		m_tOut.SendLSBDword ( ((m_uPacketID++)<<24) + MysqlPackedLen ( iColumns ) );
15232 		m_tOut.SendMysqlInt ( iColumns );
15233 		m_iSize = iColumns;
15234 	}
15235 
HeadEnd(bool bMoreResults=false,int iWarns=0)15236 	inline void HeadEnd ( bool bMoreResults=false, int iWarns=0 )
15237 	{
15238 		Eof ( bMoreResults, iWarns );
15239 		Reset();
15240 	}
15241 
15242 	// add the next column. The EOF after the tull set will be fired automatically
HeadColumn(const char * sName,MysqlColumnType_e uType=MYSQL_COL_STRING)15243 	inline void HeadColumn ( const char * sName, MysqlColumnType_e uType=MYSQL_COL_STRING )
15244 	{
15245 		assert ( m_iSize>0 && "you try to send more mysql columns than declared in InitHead" );
15246 		SendMysqlFieldPacket ( m_tOut, m_uPacketID++, sName, uType );
15247 		--m_iSize;
15248 	}
15249 
15250 	// Fire he header for table with iSize string columns
HeadOfStrings(const char ** ppNames,size_t iSize)15251 	inline void HeadOfStrings ( const char ** ppNames, size_t iSize )
15252 	{
15253 		HeadBegin(iSize);
15254 		for ( ; iSize>0 ; --iSize )
15255 			HeadColumn ( *ppNames++ );
15256 		HeadEnd();
15257 	}
15258 
15259 	// table of 2 columns (we really often use them!)
HeadTuplet(const char * pLeft,const char * pRight)15260 	inline void HeadTuplet ( const char * pLeft, const char * pRight )
15261 	{
15262 		HeadBegin(2);
15263 		HeadColumn(pLeft);
15264 		HeadColumn(pRight);
15265 		HeadEnd();
15266 	}
15267 
15268 	// popular pattern of 2 columns of data
DataTuplet(const char * pLeft,const char * pRight)15269 	inline void DataTuplet ( const char * pLeft, const char * pRight )
15270 	{
15271 		PutString ( pLeft );
15272 		PutString ( pRight );
15273 		Commit();
15274 	}
15275 
DataTuplet(const char * pLeft,int64_t iRight)15276 	inline void DataTuplet ( const char * pLeft, int64_t iRight )
15277 	{
15278 		char sTmp[SPH_MAX_NUMERIC_STR];
15279 		snprintf ( sTmp, sizeof(sTmp), INT64_FMT, iRight );
15280 		DataTuplet ( pLeft, sTmp );
15281 	}
15282 
15283 private:
15284 	char m_dBuf[4096];
15285 	char * m_pBuf;
15286 	int m_iLen;
15287 	int m_iLimit;
15288 
15289 private:
15290 	BYTE & m_uPacketID;
15291 	NetOutputBuffer_c & m_tOut;
15292 	size_t m_iSize;
15293 };
15294 
15295 class TableLike : public CheckLike
15296 				, public ISphNoncopyable
15297 {
15298 	SqlRowBuffer_c & m_tOut;
15299 public:
15300 
TableLike(SqlRowBuffer_c & tOut,const char * sPattern=NULL)15301 	explicit TableLike ( SqlRowBuffer_c & tOut, const char * sPattern = NULL )
15302 		: CheckLike ( sPattern )
15303 		, m_tOut ( tOut )
15304 	{}
15305 
MatchAdd(const char * sValue)15306 	bool MatchAdd ( const char* sValue )
15307 	{
15308 		if ( Match ( sValue ) )
15309 		{
15310 			m_tOut.PutString ( sValue );
15311 			return true;
15312 		}
15313 		return false;
15314 	}
15315 
MatchAddVa(const char * sTemplate,...)15316 	bool MatchAddVa ( const char * sTemplate, ... ) __attribute__ ( ( format ( printf, 2, 3 ) ) )
15317 	{
15318 		va_list ap;
15319 		CSphString sValue;
15320 
15321 		va_start ( ap, sTemplate );
15322 		sValue.SetSprintfVa ( sTemplate, ap );
15323 		va_end ( ap );
15324 
15325 		return MatchAdd ( sValue.cstr() );
15326 	}
15327 
15328 	// popular pattern of 2 columns of data
MatchDataTuplet(const char * pLeft,const char * pRight)15329 	inline void MatchDataTuplet ( const char * pLeft, const char * pRight )
15330 	{
15331 		if ( Match ( pLeft ) )
15332 			m_tOut.DataTuplet ( pLeft, pRight );
15333 	}
15334 
MatchDataTuplet(const char * pLeft,int iRight)15335 	inline void MatchDataTuplet ( const char * pLeft, int iRight )
15336 	{
15337 		if ( Match ( pLeft ) )
15338 			m_tOut.DataTuplet ( pLeft, iRight );
15339 	}
15340 };
15341 
HandleMysqlInsert(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt,bool bReplace,bool bCommit,CSphString & sWarning)15342 void HandleMysqlInsert ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt,
15343 	bool bReplace, bool bCommit, CSphString & sWarning )
15344 {
15345 	MEMORY ( MEM_SQL_INSERT );
15346 
15347 	CSphString sError;
15348 
15349 	// get that index
15350 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
15351 	if ( !pServed )
15352 	{
15353 		sError.SetSprintf ( "no such index '%s'", tStmt.m_sIndex.cstr() );
15354 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15355 		return;
15356 	}
15357 
15358 	if ( !pServed->m_bRT || !pServed->m_bEnabled )
15359 	{
15360 		pServed->Unlock();
15361 		sError.SetSprintf ( "index '%s' does not support INSERT (enabled=%d)", tStmt.m_sIndex.cstr(), pServed->m_bEnabled );
15362 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15363 		return;
15364 	}
15365 
15366 	ISphRtIndex * pIndex = (ISphRtIndex*)pServed->m_pIndex;
15367 
15368 	// get schema, check values count
15369 	const CSphSchema & tSchema = pIndex->GetInternalSchema();
15370 	int iSchemaSz = tSchema.GetAttrsCount() + tSchema.m_dFields.GetLength() + 1;
15371 	if ( pIndex->GetSettings().m_bIndexFieldLens )
15372 		iSchemaSz -= tSchema.m_dFields.GetLength();
15373 	int iExp = tStmt.m_iSchemaSz;
15374 	int iGot = tStmt.m_dInsertValues.GetLength();
15375 	if ( !tStmt.m_dInsertSchema.GetLength() && ( iSchemaSz!=tStmt.m_iSchemaSz ) )
15376 	{
15377 		pServed->Unlock();
15378 		sError.SetSprintf ( "column count does not match schema (expected %d, got %d)", iSchemaSz, iGot );
15379 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15380 		return;
15381 	}
15382 
15383 	if ( ( iGot % iExp )!=0 )
15384 	{
15385 		pServed->Unlock();
15386 		sError.SetSprintf ( "column count does not match value count (expected %d, got %d)", iExp, iGot );
15387 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15388 		return;
15389 	}
15390 
15391 	CSphVector<int> dAttrSchema ( tSchema.GetAttrsCount() );
15392 	CSphVector<int> dFieldSchema ( tSchema.m_dFields.GetLength() );
15393 	int iIdIndex = 0;
15394 	if ( !tStmt.m_dInsertSchema.GetLength() )
15395 	{
15396 		// no columns list, use index schema
15397 		ARRAY_FOREACH ( i, dFieldSchema )
15398 			dFieldSchema[i] = i+1;
15399 		int iFields = dFieldSchema.GetLength();
15400 		ARRAY_FOREACH ( j, dAttrSchema )
15401 			dAttrSchema[j] = j+iFields+1;
15402 	} else
15403 	{
15404 		// got a list of columns, check for 1) existance, 2) dupes
15405 		CSphVector<CSphString> dCheck = tStmt.m_dInsertSchema;
15406 		ARRAY_FOREACH ( i, dCheck )
15407 			// OPTIMIZE! GetAttrIndex and GetFieldIndex use the linear searching. M.b. hash instead?
15408 			if ( dCheck[i]!="id" && tSchema.GetAttrIndex ( dCheck[i].cstr() )==-1 && tSchema.GetFieldIndex ( dCheck[i].cstr() )==-1 )
15409 			{
15410 				pServed->Unlock();
15411 				sError.SetSprintf ( "unknown column: '%s'", dCheck[i].cstr() );
15412 				tOut.Error ( tStmt.m_sStmt, sError.cstr(), MYSQL_ERR_PARSE_ERROR );
15413 				return;
15414 			}
15415 
15416 		dCheck.Sort();
15417 		for ( int i=1; i<dCheck.GetLength(); i++ )
15418 			if ( dCheck[i-1]==dCheck[i] )
15419 			{
15420 				pServed->Unlock();
15421 				sError.SetSprintf ( "column '%s' specified twice", dCheck[i].cstr() );
15422 				tOut.Error ( tStmt.m_sStmt, sError.cstr(), MYSQL_ERR_FIELD_SPECIFIED_TWICE );
15423 				return;
15424 			}
15425 
15426 		// hash column list
15427 		// OPTIMIZE! hash index columns once (!) instead
15428 		SmallStringHash_T<int> dInsertSchema;
15429 		ARRAY_FOREACH ( i, tStmt.m_dInsertSchema )
15430 			dInsertSchema.Add ( i, tStmt.m_dInsertSchema[i] );
15431 
15432 		// get id index
15433 		if ( !dInsertSchema.Exists("id") )
15434 		{
15435 			pServed->Unlock();
15436 			tOut.Error ( tStmt.m_sStmt, "column list must contain an 'id' column" );
15437 			return;
15438 		}
15439 		iIdIndex = dInsertSchema["id"];
15440 
15441 		// map fields
15442 		bool bIdDupe = false;
15443 		ARRAY_FOREACH ( i, dFieldSchema )
15444 		{
15445 			if ( dInsertSchema.Exists ( tSchema.m_dFields[i].m_sName ) )
15446 			{
15447 				int iField = dInsertSchema[tSchema.m_dFields[i].m_sName];
15448 				if ( iField==iIdIndex )
15449 				{
15450 					bIdDupe = true;
15451 					break;
15452 				}
15453 				dFieldSchema[i] = iField;
15454 			} else
15455 				dFieldSchema[i] = -1;
15456 		}
15457 		if ( bIdDupe )
15458 		{
15459 			pServed->Unlock();
15460 			tOut.Error ( tStmt.m_sStmt, "fields must never be named 'id' (fix your config)" );
15461 			return;
15462 		}
15463 
15464 		// map attrs
15465 		ARRAY_FOREACH ( j, dAttrSchema )
15466 		{
15467 			if ( dInsertSchema.Exists ( tSchema.GetAttr(j).m_sName ) )
15468 			{
15469 				int iField = dInsertSchema[tSchema.GetAttr(j).m_sName];
15470 				if ( iField==iIdIndex )
15471 				{
15472 					bIdDupe = true;
15473 					break;
15474 				}
15475 				dAttrSchema[j] = iField;
15476 			} else
15477 				dAttrSchema[j] = -1;
15478 		}
15479 		if ( bIdDupe )
15480 		{
15481 			pServed->Unlock();
15482 			sError.SetSprintf ( "attributes must never be named 'id' (fix your config)" );
15483 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15484 			return;
15485 		}
15486 	}
15487 
15488 	CSphVector<const char *> dStrings;
15489 	CSphVector<DWORD> dMvas;
15490 
15491 	// convert attrs
15492 	for ( int c=0; c<tStmt.m_iRowsAffected; c++ )
15493 	{
15494 		assert ( sError.IsEmpty() );
15495 
15496 		CSphMatchVariant tDoc;
15497 		tDoc.Reset ( tSchema.GetRowSize() );
15498 		tDoc.m_uDocID = (SphDocID_t)CSphMatchVariant::ToDocid ( tStmt.m_dInsertValues[iIdIndex + c * iExp] );
15499 		dStrings.Resize ( 0 );
15500 		dMvas.Resize ( 0 );
15501 
15502 		int iSchemaAttrCount = tSchema.GetAttrsCount();
15503 		if ( pIndex->GetSettings().m_bIndexFieldLens )
15504 			iSchemaAttrCount -= tSchema.m_dFields.GetLength();
15505 		for ( int i=0; i<iSchemaAttrCount; i++ )
15506 		{
15507 			// shortcuts!
15508 			const CSphColumnInfo & tCol = tSchema.GetAttr(i);
15509 			CSphAttrLocator tLoc = tCol.m_tLocator;
15510 			tLoc.m_bDynamic = true;
15511 
15512 			int iQuerySchemaIdx = dAttrSchema[i];
15513 			bool bResult;
15514 			if ( iQuerySchemaIdx < 0 )
15515 			{
15516 				bResult = tDoc.SetDefaultAttr ( tLoc, tCol.m_eAttrType );
15517 				if ( tCol.m_eAttrType==SPH_ATTR_STRING || tCol.m_eAttrType==SPH_ATTR_JSON )
15518 					dStrings.Add ( NULL );
15519 				if ( tCol.m_eAttrType==SPH_ATTR_UINT32SET || tCol.m_eAttrType==SPH_ATTR_INT64SET )
15520 					dMvas.Add ( 0 );
15521 			} else
15522 			{
15523 				const SqlInsert_t & tVal = tStmt.m_dInsertValues[iQuerySchemaIdx + c * iExp];
15524 
15525 				// sanity checks
15526 				if ( tVal.m_iType!=TOK_QUOTED_STRING && tVal.m_iType!=TOK_CONST_INT && tVal.m_iType!=TOK_CONST_FLOAT && tVal.m_iType!=TOK_CONST_MVA )
15527 				{
15528 					sError.SetSprintf ( "row %d, column %d: internal error: unknown insval type %d", 1+c, 1+iQuerySchemaIdx, tVal.m_iType ); // 1 for human base
15529 					break;
15530 				}
15531 				if ( tVal.m_iType==TOK_CONST_MVA && !( tCol.m_eAttrType==SPH_ATTR_UINT32SET || tCol.m_eAttrType==SPH_ATTR_INT64SET ) )
15532 				{
15533 					sError.SetSprintf ( "row %d, column %d: MVA value specified for a non-MVA column", 1+c, 1+iQuerySchemaIdx ); // 1 for human base
15534 					break;
15535 				}
15536 				if ( ( tCol.m_eAttrType==SPH_ATTR_UINT32SET || tCol.m_eAttrType==SPH_ATTR_INT64SET ) && tVal.m_iType!=TOK_CONST_MVA )
15537 				{
15538 					sError.SetSprintf ( "row %d, column %d: non-MVA value specified for a MVA column", 1+c, 1+iQuerySchemaIdx ); // 1 for human base
15539 					break;
15540 				}
15541 
15542 				// ok, checks passed; do work
15543 				// MVA column? grab the values
15544 				if ( tCol.m_eAttrType==SPH_ATTR_UINT32SET || tCol.m_eAttrType==SPH_ATTR_INT64SET )
15545 				{
15546 					// collect data from scattered insvals
15547 					// FIXME! maybe remove this mess, and just have a single m_dMvas pool in parser instead?
15548 					int iLen = 0;
15549 					if ( tVal.m_pVals.Ptr() )
15550 					{
15551 						tVal.m_pVals->Uniq();
15552 						iLen = tVal.m_pVals->GetLength();
15553 					}
15554 					if ( tCol.m_eAttrType==SPH_ATTR_INT64SET )
15555 					{
15556 						dMvas.Add ( iLen*2 );
15557 						for ( int j=0; j<iLen; j++ )
15558 						{
15559 							uint64_t uVal = ( *tVal.m_pVals.Ptr() )[j];
15560 							DWORD uLow = (DWORD)uVal;
15561 							DWORD uHi = (DWORD)( uVal>>32 );
15562 							dMvas.Add ( uLow );
15563 							dMvas.Add ( uHi );
15564 						}
15565 					} else
15566 					{
15567 						dMvas.Add ( iLen );
15568 						for ( int j=0; j<iLen; j++ )
15569 							dMvas.Add ( (DWORD)( *tVal.m_pVals.Ptr() )[j] );
15570 					}
15571 				}
15572 
15573 				// FIXME? index schema is lawfully static, but our temp match obviously needs to be dynamic
15574 				bResult = tDoc.SetAttr ( tLoc, tVal, tCol.m_eAttrType );
15575 				if ( tCol.m_eAttrType==SPH_ATTR_STRING || tCol.m_eAttrType==SPH_ATTR_JSON )
15576 					dStrings.Add ( tVal.m_sVal.cstr() );
15577 			}
15578 
15579 			if ( !bResult )
15580 			{
15581 				sError.SetSprintf ( "internal error: unknown attribute type in INSERT (typeid=%d)", tCol.m_eAttrType );
15582 				break;
15583 			}
15584 		}
15585 		if ( !sError.IsEmpty() )
15586 			break;
15587 
15588 		// convert fields
15589 		CSphVector<const char*> dFields;
15590 		ARRAY_FOREACH ( i, tSchema.m_dFields )
15591 		{
15592 			int iQuerySchemaIdx = dFieldSchema[i];
15593 			if ( iQuerySchemaIdx < 0 )
15594 				dFields.Add ( "" ); // default value
15595 			else
15596 			{
15597 				if ( tStmt.m_dInsertValues [ iQuerySchemaIdx + c * iExp ].m_iType!=TOK_QUOTED_STRING )
15598 				{
15599 					sError.SetSprintf ( "row %d, column %d: string expected", 1+c, 1+iQuerySchemaIdx ); // 1 for human base
15600 					break;
15601 				}
15602 				dFields.Add ( tStmt.m_dInsertValues[ iQuerySchemaIdx + c * iExp ].m_sVal.cstr() );
15603 			}
15604 		}
15605 		if ( !sError.IsEmpty() )
15606 			break;
15607 
15608 		// do add
15609 		pIndex->AddDocument ( dFields.GetLength(), dFields.Begin(), tDoc,
15610 			bReplace, tStmt.m_sStringParam,
15611 			dStrings.Begin(), dMvas, sError, sWarning );
15612 
15613 		if ( !sError.IsEmpty() )
15614 			break;
15615 	}
15616 
15617 	// fire exit
15618 	if ( !sError.IsEmpty() )
15619 	{
15620 		pIndex->RollBack(); // clean up collected data
15621 		pServed->Unlock();
15622 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15623 		return;
15624 	}
15625 
15626 	// no errors so far
15627 	if ( bCommit )
15628 		pIndex->Commit ();
15629 
15630 	pServed->Unlock();
15631 
15632 	// my OK packet
15633 	tOut.Ok ( tStmt.m_iRowsAffected, sWarning.IsEmpty() ? 0 : 1 );
15634 }
15635 
15636 
15637 // our copy of enum_server_command
15638 // we can't rely on mysql_com.h because it might be unavailable
15639 //
15640 // MYSQL_COM_SLEEP = 0
15641 // MYSQL_COM_QUIT = 1
15642 // MYSQL_COM_INIT_DB = 2
15643 // MYSQL_COM_QUERY = 3
15644 // MYSQL_COM_FIELD_LIST = 4
15645 // MYSQL_COM_CREATE_DB = 5
15646 // MYSQL_COM_DROP_DB = 6
15647 // MYSQL_COM_REFRESH = 7
15648 // MYSQL_COM_SHUTDOWN = 8
15649 // MYSQL_COM_STATISTICS = 9
15650 // MYSQL_COM_PROCESS_INFO = 10
15651 // MYSQL_COM_CONNECT = 11
15652 // MYSQL_COM_PROCESS_KILL = 12
15653 // MYSQL_COM_DEBUG = 13
15654 // MYSQL_COM_PING = 14
15655 // MYSQL_COM_TIME = 15
15656 // MYSQL_COM_DELAYED_INSERT = 16
15657 // MYSQL_COM_CHANGE_USER = 17
15658 // MYSQL_COM_BINLOG_DUMP = 18
15659 // MYSQL_COM_TABLE_DUMP = 19
15660 // MYSQL_COM_CONNECT_OUT = 20
15661 // MYSQL_COM_REGISTER_SLAVE = 21
15662 // MYSQL_COM_STMT_PREPARE = 22
15663 // MYSQL_COM_STMT_EXECUTE = 23
15664 // MYSQL_COM_STMT_SEND_LONG_DATA = 24
15665 // MYSQL_COM_STMT_CLOSE = 25
15666 // MYSQL_COM_STMT_RESET = 26
15667 // MYSQL_COM_SET_OPTION = 27
15668 // MYSQL_COM_STMT_FETCH = 28
15669 
15670 enum
15671 {
15672 	MYSQL_COM_QUIT		= 1,
15673 	MYSQL_COM_INIT_DB	= 2,
15674 	MYSQL_COM_QUERY		= 3,
15675 	MYSQL_COM_PING		= 14,
15676 	MYSQL_COM_SET_OPTION	= 27
15677 };
15678 
15679 
HandleMysqlCallSnippets(SqlRowBuffer_c & tOut,SqlStmt_t & tStmt)15680 void HandleMysqlCallSnippets ( SqlRowBuffer_c & tOut, SqlStmt_t & tStmt )
15681 {
15682 	CSphString sError;
15683 
15684 	// check arguments
15685 	// string data, string index, string query, [named opts]
15686 	if ( tStmt.m_dInsertValues.GetLength()!=3 )
15687 	{
15688 		tOut.Error ( tStmt.m_sStmt, "SNIPPETS() expectes exactly 3 arguments (data, index, query)" );
15689 		return;
15690 	}
15691 	if ( tStmt.m_dInsertValues[0].m_iType!=TOK_QUOTED_STRING && tStmt.m_dInsertValues[0].m_iType!=TOK_CONST_STRINGS )
15692 	{
15693 		tOut.Error ( tStmt.m_sStmt, "SNIPPETS() argument 1 must be a string or a string list" );
15694 		return;
15695 	}
15696 	if ( tStmt.m_dInsertValues[1].m_iType!=TOK_QUOTED_STRING )
15697 	{
15698 		tOut.Error ( tStmt.m_sStmt, "SNIPPETS() argument 2 must be a string" );
15699 		return;
15700 	}
15701 	if ( tStmt.m_dInsertValues[2].m_iType!=TOK_QUOTED_STRING )
15702 	{
15703 		tOut.Error ( tStmt.m_sStmt, "SNIPPETS() argument 3 must be a string" );
15704 		return;
15705 	}
15706 
15707 	// do magics
15708 	CSphString sIndex = tStmt.m_dInsertValues[1].m_sVal;
15709 
15710 	ExcerptQuery_t q;
15711 	q.m_sWords = tStmt.m_dInsertValues[2].m_sVal;
15712 
15713 	ARRAY_FOREACH ( i, tStmt.m_dCallOptNames )
15714 	{
15715 		CSphString & sOpt = tStmt.m_dCallOptNames[i];
15716 		const SqlInsert_t & v = tStmt.m_dCallOptValues[i];
15717 
15718 		sOpt.ToLower();
15719 		int iExpType = -1;
15720 
15721 		if ( sOpt=="before_match" )				{ q.m_sBeforeMatch = v.m_sVal; iExpType = TOK_QUOTED_STRING; }
15722 		else if ( sOpt=="after_match" )			{ q.m_sAfterMatch = v.m_sVal; iExpType = TOK_QUOTED_STRING; }
15723 		else if ( sOpt=="chunk_separator" )		{ q.m_sChunkSeparator = v.m_sVal; iExpType = TOK_QUOTED_STRING; }
15724 		else if ( sOpt=="html_strip_mode" )		{ q.m_sStripMode = v.m_sVal; iExpType = TOK_QUOTED_STRING; }
15725 		else if ( sOpt=="passage_boundary" )	{ q.m_sRawPassageBoundary = v.m_sVal; iExpType = TOK_QUOTED_STRING; }
15726 
15727 		else if ( sOpt=="limit" )				{ q.m_iLimit = (int)v.m_iVal; iExpType = TOK_CONST_INT; }
15728 		else if ( sOpt=="limit_words" )			{ q.m_iLimitWords = (int)v.m_iVal; iExpType = TOK_CONST_INT; }
15729 		else if ( sOpt=="limit_passages" )		{ q.m_iLimitPassages = (int)v.m_iVal; iExpType = TOK_CONST_INT; }
15730 		else if ( sOpt=="around" )				{ q.m_iAround = (int)v.m_iVal; iExpType = TOK_CONST_INT; }
15731 		else if ( sOpt=="start_passage_id" )	{ q.m_iPassageId = (int)v.m_iVal; iExpType = TOK_CONST_INT; }
15732 
15733 		else if ( sOpt=="exact_phrase" )		{ q.m_bExactPhrase = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15734 		else if ( sOpt=="use_boundaries" )		{ q.m_bUseBoundaries = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15735 		else if ( sOpt=="weight_order" )		{ q.m_bWeightOrder = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15736 		else if ( sOpt=="query_mode" )			{ q.m_bHighlightQuery = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15737 		else if ( sOpt=="force_all_words" )		{ q.m_bForceAllWords = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15738 		else if ( sOpt=="load_files" )			{ q.m_iLoadFiles = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15739 		else if ( sOpt=="load_files_scattered" ) { q.m_iLoadFiles |= ( v.m_iVal!=0 )?2:0; iExpType = TOK_CONST_INT; }
15740 		else if ( sOpt=="allow_empty" )			{ q.m_bAllowEmpty = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15741 		else if ( sOpt=="emit_zones" )			{ q.m_bEmitZones = ( v.m_iVal!=0 ); iExpType = TOK_CONST_INT; }
15742 
15743 		else
15744 		{
15745 			sError.SetSprintf ( "unknown option %s", sOpt.cstr() );
15746 			break;
15747 		}
15748 
15749 		// post-conf type check
15750 		if ( iExpType!=v.m_iType )
15751 		{
15752 			sError.SetSprintf ( "unexpected option %s type", sOpt.cstr() );
15753 			break;
15754 		}
15755 	}
15756 	if ( !sError.IsEmpty() )
15757 	{
15758 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15759 		return;
15760 	}
15761 
15762 	if ( q.m_iLoadFiles )
15763 		q.m_sFilePrefix = g_sSnippetsFilePrefix;
15764 
15765 	q.m_ePassageSPZ = sphGetPassageBoundary ( q.m_sRawPassageBoundary );
15766 
15767 	if ( !sphCheckOptionsSPZ ( q, q.m_sRawPassageBoundary, sError ) )
15768 	{
15769 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15770 		return;
15771 	}
15772 
15773 	q.m_bHasBeforePassageMacro = SnippetTransformPassageMacros ( q.m_sBeforeMatch, q.m_sBeforeMatchPassage );
15774 	q.m_bHasAfterPassageMacro = SnippetTransformPassageMacros ( q.m_sAfterMatch, q.m_sAfterMatchPassage );
15775 	q.m_iRawFlags = GetRawSnippetFlags ( q );
15776 
15777 	CSphVector<ExcerptQuery_t> dQueries;
15778 	if ( tStmt.m_dInsertValues[0].m_iType==TOK_QUOTED_STRING )
15779 	{
15780 		q.m_sSource = tStmt.m_dInsertValues[0].m_sVal; // OPTIMIZE?
15781 		dQueries.Add ( q );
15782 	} else
15783 	{
15784 		dQueries.Resize ( tStmt.m_dCallStrings.GetLength() );
15785 		ARRAY_FOREACH ( i, tStmt.m_dCallStrings )
15786 		{
15787 			dQueries[i] = q; // copy the settings
15788 			dQueries[i].m_sSource = tStmt.m_dCallStrings[i]; // OPTIMIZE?
15789 		}
15790 	}
15791 
15792 	// FIXME!!! SphinxQL but need to provide data size too
15793 	if ( !MakeSnippets ( sIndex, dQueries, sError, NULL ) )
15794 	{
15795 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15796 		return;
15797 	}
15798 
15799 	CSphVector<const char*> dResults ( dQueries.GetLength() );
15800 	ARRAY_FOREACH ( i, dResults )
15801 		dResults[i] = (const char *)dQueries[i].m_dRes.Begin();
15802 
15803 	bool bGotData = ARRAY_ANY ( bGotData, dResults, dResults[_any]!=NULL );
15804 	if ( !bGotData )
15805 	{
15806 		// just one last error instead of all errors is hopefully ok
15807 		sError.SetSprintf ( "highlighting failed: %s", sError.cstr() );
15808 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15809 		return;
15810 	}
15811 
15812 	// result set header packet
15813 	tOut.HeadBegin(1);
15814 	tOut.HeadColumn("snippet");
15815 	tOut.HeadEnd();
15816 
15817 	// data
15818 	ARRAY_FOREACH ( i, dResults )
15819 	{
15820 		tOut.PutString ( dResults[i] ? dResults[i] : "" );
15821 		tOut.Commit();
15822 	}
15823 	tOut.Eof();
15824 }
15825 
15826 
HandleMysqlCallKeywords(SqlRowBuffer_c & tOut,SqlStmt_t & tStmt)15827 void HandleMysqlCallKeywords ( SqlRowBuffer_c & tOut, SqlStmt_t & tStmt )
15828 {
15829 	CSphString sError;
15830 
15831 	// string query, string index, [bool hits]
15832 	int iArgs = tStmt.m_dInsertValues.GetLength();
15833 	if ( iArgs<2
15834 		|| iArgs>3
15835 		|| tStmt.m_dInsertValues[0].m_iType!=TOK_QUOTED_STRING
15836 		|| tStmt.m_dInsertValues[1].m_iType!=TOK_QUOTED_STRING
15837 		|| ( iArgs==3 && tStmt.m_dInsertValues[2].m_iType!=TOK_CONST_INT ) )
15838 	{
15839 		tOut.Error ( tStmt.m_sStmt, "bad argument count or types in KEYWORDS() call" );
15840 		return;
15841 	}
15842 
15843 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_dInsertValues[1].m_sVal );
15844 	if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
15845 	{
15846 		if ( pServed )
15847 			pServed->Unlock();
15848 
15849 		pServed = g_pTemplateIndexes->GetRlockedEntry ( tStmt.m_dInsertValues[1].m_sVal );
15850 		if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
15851 		{
15852 			sError.SetSprintf ( "no such index %s", tStmt.m_dInsertValues[1].m_sVal.cstr() );
15853 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15854 			if ( pServed )
15855 				pServed->Unlock();
15856 			return;
15857 		}
15858 	}
15859 
15860 	CSphVector<CSphKeywordInfo> dKeywords;
15861 	bool bStats = ( iArgs==3 && tStmt.m_dInsertValues[2].m_iVal!=0 );
15862 	bool bRes = pServed->m_pIndex->GetKeywords ( dKeywords, tStmt.m_dInsertValues[0].m_sVal.cstr(), bStats, &sError );
15863 	pServed->Unlock ();
15864 
15865 	if ( !bRes )
15866 	{
15867 		sError.SetSprintf ( "keyword extraction failed: %s", sError.cstr() );
15868 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
15869 		return;
15870 	}
15871 
15872 	// result set header packet
15873 	tOut.HeadBegin ( bStats ? 5 : 3 );
15874 	tOut.HeadColumn("qpos");
15875 	tOut.HeadColumn("tokenized");
15876 	tOut.HeadColumn("normalized");
15877 	if ( bStats )
15878 	{
15879 		tOut.HeadColumn("docs");
15880 		tOut.HeadColumn("hits");
15881 	}
15882 	tOut.HeadEnd();
15883 
15884 	// data
15885 	char sBuf[16];
15886 	ARRAY_FOREACH ( i, dKeywords )
15887 	{
15888 		snprintf ( sBuf, sizeof(sBuf), "%d", dKeywords[i].m_iQpos );
15889 		tOut.PutString ( sBuf );
15890 		tOut.PutString ( dKeywords[i].m_sTokenized.cstr() );
15891 		tOut.PutString ( dKeywords[i].m_sNormalized.cstr() );
15892 		if ( bStats )
15893 		{
15894 			snprintf ( sBuf, sizeof(sBuf), "%d", dKeywords[i].m_iDocs );
15895 			tOut.PutString ( sBuf );
15896 			snprintf ( sBuf, sizeof(sBuf), "%d", dKeywords[i].m_iHits );
15897 			tOut.PutString ( sBuf );
15898 		}
15899 		tOut.Commit();
15900 	}
15901 	tOut.Eof();
15902 }
15903 
15904 
HandleMysqlDescribe(SqlRowBuffer_c & tOut,SqlStmt_t & tStmt)15905 void HandleMysqlDescribe ( SqlRowBuffer_c & tOut, SqlStmt_t & tStmt )
15906 {
15907 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
15908 	DistributedIndex_t * pDistr = NULL;
15909 	if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
15910 	{
15911 		if ( pServed )
15912 			pServed->Unlock();
15913 		pServed = g_pTemplateIndexes->GetRlockedEntry ( tStmt.m_sIndex );
15914 		if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex )
15915 		{
15916 			if ( pServed )
15917 				pServed->Unlock();
15918 			g_tDistLock.Lock();
15919 			pDistr = g_hDistIndexes ( tStmt.m_sIndex );
15920 			g_tDistLock.Unlock();
15921 
15922 			if ( !pDistr )
15923 			{
15924 				CSphString sError;
15925 				sError.SetSprintf ( "no such index '%s'", tStmt.m_sIndex.cstr() );
15926 				tOut.Error ( tStmt.m_sStmt, sError.cstr(), MYSQL_ERR_NO_SUCH_TABLE );
15927 				return;
15928 			}
15929 		}
15930 	}
15931 
15932 	TableLike dCondOut ( tOut, tStmt.m_sStringParam.cstr() );
15933 
15934 	if ( pDistr )
15935 	{
15936 		tOut.HeadTuplet ( "Agent", "Type" );
15937 		ARRAY_FOREACH ( i, pDistr->m_dLocal )
15938 			dCondOut.MatchDataTuplet ( pDistr->m_dLocal[i].cstr(), "local" );
15939 
15940 		ARRAY_FOREACH ( i, pDistr->m_dAgents )
15941 		{
15942 			MetaAgentDesc_t & tAgents = pDistr->m_dAgents[i];
15943 			if ( tAgents.GetAgents().GetLength() > 1 )
15944 			{
15945 				ARRAY_FOREACH ( j, tAgents.GetAgents() )
15946 				{
15947 					CSphString sKey;
15948 					sKey.SetSprintf ( "remote_%d_mirror_%d", i+1, j+1 );
15949 					const AgentDesc_t & dDesc = tAgents.GetAgents()[j];
15950 					CSphString sValue;
15951 					sValue.SetSprintf ( "%s:%s", dDesc.GetName().cstr(), dDesc.m_sIndexes.cstr() );
15952 					dCondOut.MatchDataTuplet ( sValue.cstr(), sKey.cstr() );
15953 				}
15954 			} else
15955 			{
15956 				CSphString sKey;
15957 				sKey.SetSprintf ( "remote_%d", i+1 );
15958 				CSphString sValue;
15959 				sValue.SetSprintf ( "%s:%s", tAgents.GetAgents()[0].GetName().cstr(), tAgents.GetAgents()[0].m_sIndexes.cstr() );
15960 				dCondOut.MatchDataTuplet ( sValue.cstr(), sKey.cstr() );
15961 			}
15962 		}
15963 
15964 		tOut.Eof();
15965 		return;
15966 	}
15967 
15968 	// result set header packet
15969 	tOut.HeadTuplet ( "Field", "Type" );
15970 
15971 	// data
15972 	dCondOut.MatchDataTuplet ( "id", USE_64BIT ? "bigint" : "uint" );
15973 
15974 	const CSphSchema & tSchema = pServed->m_pIndex->GetMatchSchema();
15975 	ARRAY_FOREACH ( i, tSchema.m_dFields )
15976 		dCondOut.MatchDataTuplet ( tSchema.m_dFields[i].m_sName.cstr(), "field" );
15977 
15978 	char sTmp[SPH_MAX_WORD_LEN];
15979 	for ( int i=0; i<tSchema.GetAttrsCount(); i++ )
15980 	{
15981 		const CSphColumnInfo & tCol = tSchema.GetAttr(i);
15982 		if ( tCol.m_eAttrType==SPH_ATTR_INTEGER && tCol.m_tLocator.m_iBitCount!=ROWITEM_BITS )
15983 		{
15984 			snprintf ( sTmp, sizeof(sTmp), "%s:%d", sphTypeName ( tCol.m_eAttrType ), tCol.m_tLocator.m_iBitCount );
15985 			dCondOut.MatchDataTuplet ( tCol.m_sName.cstr(), sTmp );
15986 		} else
15987 		{
15988 			dCondOut.MatchDataTuplet ( tCol.m_sName.cstr(), sphTypeName ( tCol.m_eAttrType ) );
15989 		}
15990 	}
15991 
15992 	pServed->Unlock();
15993 	tOut.Eof();
15994 }
15995 
15996 
15997 struct IndexNameLess_fn
15998 {
IsLessIndexNameLess_fn15999 	inline bool IsLess ( const CSphNamedInt & a, const CSphNamedInt & b ) const
16000 	{
16001 		return strcasecmp ( a.m_sName.cstr(), b.m_sName.cstr() )<0;
16002 	}
16003 };
16004 
16005 
HandleMysqlShowTables(SqlRowBuffer_c & tOut,SqlStmt_t & tStmt)16006 void HandleMysqlShowTables ( SqlRowBuffer_c & tOut, SqlStmt_t & tStmt )
16007 {
16008 	// result set header packet
16009 	tOut.HeadTuplet ( "Index", "Type" );
16010 
16011 	// all the indexes
16012 	// 0 local, 1 distributed, 2 rt
16013 	CSphVector<CSphNamedInt> dIndexes;
16014 
16015 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
16016 		if ( it.Get().m_bEnabled )
16017 	{
16018 		CSphNamedInt & tIdx = dIndexes.Add();
16019 		tIdx.m_sName = it.GetKey();
16020 		tIdx.m_iValue = it.Get().m_bRT ? 2 : 0;
16021 	}
16022 
16023 	for ( IndexHashIterator_c it ( g_pTemplateIndexes ); it.Next(); )
16024 	if ( it.Get().m_bEnabled )
16025 	{
16026 		CSphNamedInt & tIdx = dIndexes.Add();
16027 		tIdx.m_sName = it.GetKey();
16028 		tIdx.m_iValue = 3;
16029 	}
16030 
16031 	g_tDistLock.Lock();
16032 	g_hDistIndexes.IterateStart();
16033 	while ( g_hDistIndexes.IterateNext() )
16034 	{
16035 		CSphNamedInt & tIdx = dIndexes.Add();
16036 		tIdx.m_sName = g_hDistIndexes.IterateGetKey();
16037 		tIdx.m_iValue = 1;
16038 	}
16039 	g_tDistLock.Unlock();
16040 
16041 	dIndexes.Sort ( IndexNameLess_fn() );
16042 
16043 	TableLike dCondOut ( tOut, tStmt.m_sStringParam.cstr() );
16044 	ARRAY_FOREACH ( i, dIndexes )
16045 	{
16046 		const char * sType = "?";
16047 		switch ( dIndexes[i].m_iValue )
16048 		{
16049 			case 0: sType = "local"; break;
16050 			case 1: sType = "distributed"; break;
16051 			case 2: sType = "rt"; break;
16052 			case 3: sType = "template"; break;
16053 		}
16054 
16055 		dCondOut.MatchDataTuplet ( dIndexes[i].m_sName.cstr(), sType );
16056 	}
16057 
16058 	tOut.Eof();
16059 }
16060 
16061 // MySQL Workbench (and maybe other clients) crashes without it
HandleMysqlShowDatabases(SqlRowBuffer_c & tOut,SqlStmt_t &)16062 void HandleMysqlShowDatabases ( SqlRowBuffer_c & tOut, SqlStmt_t & )
16063 {
16064 	tOut.HeadBegin ( 1 );
16065 	tOut.HeadColumn ( "Databases" );
16066 	tOut.HeadEnd();
16067 	tOut.Eof();
16068 }
16069 
16070 
HandleMysqlShowPlugins(SqlRowBuffer_c & tOut,SqlStmt_t &)16071 void HandleMysqlShowPlugins ( SqlRowBuffer_c & tOut, SqlStmt_t & )
16072 {
16073 	CSphVector<PluginInfo_t> dPlugins;
16074 	sphPluginList ( dPlugins );
16075 
16076 	tOut.HeadBegin ( 5 );
16077 	tOut.HeadColumn ( "Type" );
16078 	tOut.HeadColumn ( "Name" );
16079 	tOut.HeadColumn ( "Library" );
16080 	tOut.HeadColumn ( "Users" );
16081 	tOut.HeadColumn ( "Extra" );
16082 	tOut.HeadEnd();
16083 
16084 	ARRAY_FOREACH ( i, dPlugins )
16085 	{
16086 		const PluginInfo_t & p = dPlugins[i];
16087 		tOut.PutString ( g_dPluginTypes[p.m_eType] );
16088 		tOut.PutString ( p.m_sName.cstr() );
16089 		tOut.PutString ( p.m_sLib.cstr() );
16090 		tOut.PutNumeric ( "%d", p.m_iUsers );
16091 		tOut.PutString ( p.m_sExtra.cstr() ? p.m_sExtra.cstr() : "" );
16092 		tOut.Commit();
16093 	}
16094 	tOut.Eof();
16095 }
16096 
16097 
HandleMysqlShowThreads(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)16098 void HandleMysqlShowThreads ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
16099 {
16100 	if ( g_eWorkers!=MPM_THREADS )
16101 	{
16102 		tOut.Ok();
16103 		return;
16104 	}
16105 
16106 	int64_t tmNow = sphMicroTimer();
16107 
16108 	g_tThdMutex.Lock();
16109 	tOut.HeadBegin ( 5 );
16110 	tOut.HeadColumn ( "Tid" );
16111 	tOut.HeadColumn ( "Proto" );
16112 	tOut.HeadColumn ( "State" );
16113 	tOut.HeadColumn ( "Time" );
16114 	tOut.HeadColumn ( "Info" );
16115 	tOut.HeadEnd();
16116 
16117 	const ListNode_t * pIt = g_dThd.Begin();
16118 	while ( pIt!=g_dThd.End() )
16119 	{
16120 		const ThdDesc_t * pThd = (const ThdDesc_t *)pIt;
16121 
16122 		int iLen = strnlen ( pThd->m_dBuf.Begin(), pThd->m_dBuf.GetLength() );
16123 		if ( tStmt.m_iThreadsCols>0 && iLen>tStmt.m_iThreadsCols )
16124 		{
16125 			const_cast<ThdDesc_t *>(pThd)->m_dBuf[tStmt.m_iThreadsCols] = '\0';
16126 		}
16127 
16128 		tOut.PutNumeric ( "%d", pThd->m_iTid );
16129 		tOut.PutString ( g_dProtoNames [ pThd->m_eProto ] );
16130 		tOut.PutString ( g_dThdStates [ pThd->m_eThdState ] );
16131 		tOut.PutMicrosec ( tmNow - pThd->m_tmStart );
16132 		tOut.PutString ( pThd->m_dBuf.Begin() );
16133 
16134 		tOut.Commit();
16135 		pIt = pIt->m_pNext;
16136 	}
16137 
16138 	tOut.Eof();
16139 	g_tThdMutex.Unlock();
16140 }
16141 
16142 
16143 // The pinger
16144 struct PingRequestBuilder_t : public IRequestBuilder_t
16145 {
PingRequestBuilder_tPingRequestBuilder_t16146 	explicit PingRequestBuilder_t ( int iCookie = 0 )
16147 		: m_iCookie ( iCookie )
16148 	{}
BuildRequestPingRequestBuilder_t16149 	virtual void BuildRequest ( AgentConn_t &, NetOutputBuffer_c & tOut ) const
16150 	{
16151 		// header
16152 		tOut.SendWord ( SEARCHD_COMMAND_PING );
16153 		tOut.SendWord ( VER_COMMAND_PING );
16154 		tOut.SendInt ( 4 );
16155 		tOut.SendInt ( m_iCookie );
16156 	}
16157 
16158 protected:
16159 	const int m_iCookie;
16160 };
16161 
16162 struct PingReplyParser_t : public IReplyParser_t
16163 {
PingReplyParser_tPingReplyParser_t16164 	explicit PingReplyParser_t ( int * pCookie )
16165 		: m_pCookie ( pCookie )
16166 	{}
16167 
ParseReplyPingReplyParser_t16168 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const
16169 	{
16170 		*m_pCookie += tReq.GetDword ();
16171 		return true;
16172 	}
16173 
16174 protected:
16175 	int * m_pCookie;
16176 };
16177 
16178 
CheckPing(CSphVector<AgentConn_t> & dAgents,int64_t iNow)16179 static void CheckPing ( CSphVector<AgentConn_t> & dAgents, int64_t iNow )
16180 {
16181 	if ( !g_pStats )
16182 		return;
16183 
16184 	dAgents.Resize ( 0 );
16185 	int iCookie = (int)iNow;
16186 
16187 	g_tStatsMutex.Lock();
16188 	g_pStats->m_hDashBoard.IterateStart();
16189 	while ( g_pStats->m_hDashBoard.IterateNext() )
16190 	{
16191 		int iIndex = g_pStats->m_hDashBoard.IterateGet();
16192 		const HostDashboard_t & dDash = g_pStats->m_dDashboard.m_dItemStats[iIndex];
16193 		if ( dDash.m_bNeedPing && dDash.IsOlder ( iNow ) )
16194 		{
16195 			AgentConn_t & dAgent = dAgents.Add();
16196 			dAgent = dDash.m_dDescriptor;
16197 			dAgent.m_bPing = true;
16198 		}
16199 	}
16200 	g_tStatsMutex.Unlock();
16201 
16202 	if ( !dAgents.GetLength() )
16203 		return;
16204 
16205 	CSphScopedPtr<PingRequestBuilder_t> tReqBuilder ( NULL );
16206 	CSphScopedPtr<CSphRemoteAgentsController> tDistCtrl ( NULL );
16207 	// connect to remote agents and query them
16208 	tReqBuilder = new PingRequestBuilder_t ( iCookie );
16209 	tDistCtrl = new CSphRemoteAgentsController ( g_iDistThreads, dAgents, *tReqBuilder.Ptr(), g_iPingInterval );
16210 
16211 	if ( g_bShutdown )
16212 		return;
16213 
16214 	int iAgentsDone = tDistCtrl->Finish();
16215 
16216 	if ( g_bShutdown )
16217 		return;
16218 
16219 	int iReplyCookie = 0;
16220 	if ( iAgentsDone )
16221 	{
16222 		PingReplyParser_t tParser ( &iReplyCookie );
16223 		RemoteWaitForAgents ( dAgents, g_iPingInterval, tParser );
16224 	}
16225 }
16226 
16227 
PingThreadFunc(void *)16228 static void PingThreadFunc ( void * )
16229 {
16230 	if ( g_iPingInterval<=0 )
16231 		return;
16232 
16233 	// crash logging for the thread
16234 	SphCrashLogger_c tQueryTLS;
16235 	tQueryTLS.SetupTLS ();
16236 
16237 	int64_t iLastCheck = 0;
16238 	CSphVector<AgentConn_t> dAgents;
16239 
16240 	while ( !g_bShutdown || !g_bHeadDaemon )
16241 	{
16242 		// check if we have work to do
16243 		int64_t iNow = sphMicroTimer();
16244 		if ( ( iNow-iLastCheck )<g_iPingInterval*1000 )
16245 		{
16246 			sphSleepMsec ( 50 );
16247 			continue;
16248 		}
16249 
16250 		CheckPing ( dAgents, iNow );
16251 		iLastCheck = sphMicroTimer();
16252 	}
16253 }
16254 
16255 
16256 /////////////////////////////////////////////////////////////////////////////
16257 // user variables these send from master to agents
16258 /////////////////////////////////////////////////////////////////////////////
16259 
16260 struct UVarRequestBuilder_t : public IRequestBuilder_t
16261 {
UVarRequestBuilder_tUVarRequestBuilder_t16262 	explicit UVarRequestBuilder_t ( const char * sName, int iUserVars, const BYTE * pBuf, int iLength )
16263 		: m_sName ( sName )
16264 		, m_iUserVars ( iUserVars )
16265 		, m_pBuf ( pBuf )
16266 		, m_iLength ( iLength )
16267 	{}
16268 
BuildRequestUVarRequestBuilder_t16269 	virtual void BuildRequest ( AgentConn_t &, NetOutputBuffer_c & tOut ) const
16270 	{
16271 		// header
16272 		tOut.SendWord ( SEARCHD_COMMAND_UVAR );
16273 		tOut.SendWord ( VER_COMMAND_UVAR );
16274 		tOut.SendInt ( strlen ( m_sName ) + 12 + m_iLength );
16275 
16276 		tOut.SendString ( m_sName );
16277 		tOut.SendInt ( m_iUserVars );
16278 		tOut.SendInt ( m_iLength );
16279 		tOut.SendBytes ( m_pBuf, m_iLength );
16280 	}
16281 
16282 	const char * m_sName;
16283 	int m_iUserVars;
16284 	const BYTE * m_pBuf;
16285 	int m_iLength;
16286 };
16287 
16288 struct UVarReplyParser_t : public IReplyParser_t
16289 {
UVarReplyParser_tUVarReplyParser_t16290 	explicit UVarReplyParser_t ()
16291 	{}
16292 
ParseReplyUVarReplyParser_t16293 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const
16294 	{
16295 		// error got handled at call site
16296 		bool bOk = ( tReq.GetByte()==1 );
16297 		return bOk;
16298 	}
16299 };
16300 
16301 
16302 static void UservarAdd ( const CSphString & sName, CSphVector<SphAttr_t> & dVal );
16303 
16304 // create or update the variable
SetLocalUserVar(const CSphString & sName,CSphVector<SphAttr_t> & dSetValues)16305 static void SetLocalUserVar ( const CSphString & sName, CSphVector<SphAttr_t> & dSetValues )
16306 {
16307 	g_tUservarsMutex.Lock();
16308 	UservarAdd ( sName, dSetValues );
16309 	g_tmSphinxqlState = sphMicroTimer();
16310 	g_tUservarsMutex.Unlock();
16311 }
16312 
16313 
SendUserVar(const char * sIndex,const char * sUserVarName,CSphVector<SphAttr_t> & dSetValues,CSphString & sError)16314 static bool SendUserVar ( const char * sIndex, const char * sUserVarName, CSphVector<SphAttr_t> & dSetValues, CSphString & sError )
16315 {
16316 	CSphVector<AgentConn_t> dAgents;
16317 	bool bGotLocal = false;
16318 	g_tDistLock.Lock();
16319 	const DistributedIndex_t * pIndex = g_hDistIndexes ( sIndex );
16320 	if ( pIndex )
16321 	{
16322 		pIndex->GetAllAgents ( &dAgents );
16323 		bGotLocal = ( pIndex->m_dLocal.GetLength()>0 );
16324 	}
16325 	g_tDistLock.Unlock();
16326 
16327 	if ( !pIndex )
16328 	{
16329 		sError.SetSprintf ( "unknown index '%s' in Set statement", sIndex );
16330 		return false;
16331 	}
16332 
16333 	// FIXME!!! warn on missed agents
16334 	if ( !dAgents.GetLength() && !bGotLocal )
16335 		return true;
16336 
16337 	dSetValues.Uniq();
16338 
16339 	// FIXME!!! warn on empty agents
16340 	if ( dAgents.GetLength() )
16341 	{
16342 		int iUserVarsCount = dSetValues.GetLength();
16343 		CSphFixedVector<BYTE> dBuf ( iUserVarsCount * sizeof(dSetValues[0]) );
16344 		int iLength = 0;
16345 		if ( iUserVarsCount )
16346 		{
16347 			SphAttr_t iLast = 0;
16348 			BYTE * pCur = dBuf.Begin();
16349 			ARRAY_FOREACH ( i, dSetValues )
16350 			{
16351 				SphAttr_t iDelta = dSetValues[i] - iLast;
16352 				assert ( iDelta>0 );
16353 				iLast = dSetValues[i];
16354 				pCur += sphEncodeVLB8 ( pCur, iDelta );
16355 			}
16356 
16357 			iLength = pCur - dBuf.Begin();
16358 		}
16359 
16360 		// connect to remote agents and query them
16361 		if ( dAgents.GetLength() )
16362 		{
16363 			// connect to remote agents and query them
16364 			UVarRequestBuilder_t tReqBuilder ( sUserVarName, iUserVarsCount, dBuf.Begin(), iLength );
16365 			CSphRemoteAgentsController tDistCtrl ( g_iDistThreads, dAgents, tReqBuilder, g_iPingInterval );
16366 
16367 			int iAgentsDone = tDistCtrl.Finish();
16368 			if ( iAgentsDone )
16369 			{
16370 				UVarReplyParser_t tParser;
16371 				RemoteWaitForAgents ( dAgents, g_iPingInterval, tParser );
16372 			}
16373 		}
16374 	}
16375 
16376 	// should be at the end due to swap of dSetValues values
16377 	if ( bGotLocal )
16378 		SetLocalUserVar ( sUserVarName, dSetValues );
16379 
16380 	return true;
16381 }
16382 
16383 
HandleCommandUserVar(int iSock,int iVer,NetInputBuffer_c & tReq)16384 void HandleCommandUserVar ( int iSock, int iVer, NetInputBuffer_c & tReq )
16385 {
16386 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_UVAR, tReq ) )
16387 		return;
16388 
16389 	CSphString sUserVar = tReq.GetString();
16390 	int iCount = tReq.GetInt();
16391 	CSphVector<SphAttr_t> dUserVar ( iCount );
16392 	int iLength = tReq.GetInt();
16393 	CSphFixedVector<BYTE> dBuf ( iLength );
16394 	tReq.GetBytes ( dBuf.Begin(), iLength );
16395 
16396 	if ( tReq.GetError() )
16397 	{
16398 		tReq.SendErrorReply ( "invalid or truncated request" );
16399 		return;
16400 	}
16401 
16402 	SphAttr_t iLast = 0;
16403 	const BYTE * pCur = dBuf.Begin();
16404 	ARRAY_FOREACH ( i, dUserVar )
16405 	{
16406 		uint64_t iDelta = 0;
16407 		pCur = spnDecodeVLB8 ( pCur, iDelta );
16408 		assert ( iDelta>0 );
16409 		iLast += iDelta;
16410 		dUserVar[i] = iLast;
16411 	}
16412 
16413 	SetLocalUserVar ( sUserVar, dUserVar );
16414 
16415 	NetOutputBuffer_c tOut ( iSock );
16416 	tOut.SendWord ( SEARCHD_OK );
16417 	tOut.SendWord ( VER_COMMAND_UVAR );
16418 	tOut.SendInt ( 4 ); // resplen, 1 dword
16419 	tOut.SendInt ( 1 );
16420 	tOut.Flush ();
16421 	assert ( tOut.GetError() || tOut.GetSentCount()==12 );
16422 }
16423 
16424 
16425 
16426 /////////////////////////////////////////////////////////////////////////////
16427 // SMART UPDATES HANDLER
16428 /////////////////////////////////////////////////////////////////////////////
16429 
16430 struct SphinxqlRequestBuilder_t : public IRequestBuilder_t
16431 {
SphinxqlRequestBuilder_tSphinxqlRequestBuilder_t16432 	explicit SphinxqlRequestBuilder_t ( const CSphString& sQuery, const SqlStmt_t & tStmt )
16433 		: m_sBegin ( sQuery.cstr(), tStmt.m_iListStart )
16434 		, m_sEnd ( sQuery.cstr() + tStmt.m_iListEnd, sQuery.Length() - tStmt.m_iListEnd )
16435 	{
16436 	}
16437 	virtual void BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const;
16438 
16439 protected:
16440 	const CSphString m_sBegin;
16441 	const CSphString m_sEnd;
16442 };
16443 
16444 
16445 struct SphinxqlReplyParser_t : public IReplyParser_t
16446 {
SphinxqlReplyParser_tSphinxqlReplyParser_t16447 	explicit SphinxqlReplyParser_t ( int * pUpd, int * pWarns )
16448 		: m_pUpdated ( pUpd )
16449 		, m_pWarns ( pWarns )
16450 	{}
16451 
ParseReplySphinxqlReplyParser_t16452 	virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const
16453 	{
16454 		DWORD uSize = ( tReq.GetLSBDword() & 0x00ffffff ) - 1;
16455 		BYTE uCommand = tReq.GetByte();
16456 
16457 		if ( uCommand==0 ) // ok packet
16458 		{
16459 			*m_pUpdated += MysqlUnpack ( tReq, &uSize );
16460 			MysqlUnpack ( tReq, &uSize ); ///< int Insert_id (don't used).
16461 			*m_pWarns += tReq.GetLSBDword(); ///< num of warnings
16462 			uSize -= 4;
16463 			if ( uSize )
16464 				tReq.GetRawString ( uSize );
16465 			return true;
16466 		}
16467 		if ( uCommand==0xff ) // error packet
16468 		{
16469 			tReq.GetByte();
16470 			tReq.GetByte(); ///< num of errors (2 bytes), we don't use it for now.
16471 			uSize -= 2;
16472 			if ( uSize )
16473 				tReq.GetRawString ( uSize );
16474 		}
16475 
16476 		return false;
16477 	}
16478 
16479 protected:
16480 	int * m_pUpdated;
16481 	int * m_pWarns;
16482 };
16483 
16484 
BuildRequest(AgentConn_t & tAgent,NetOutputBuffer_c & tOut) const16485 void SphinxqlRequestBuilder_t::BuildRequest ( AgentConn_t & tAgent, NetOutputBuffer_c & tOut ) const
16486 {
16487 	const char* sIndexes = tAgent.m_sIndexes.cstr();
16488 	int iReqSize = strlen(sIndexes) + m_sBegin.Length() + m_sEnd.Length(); // indexes string
16489 
16490 	// header
16491 	tOut.SendWord ( SEARCHD_COMMAND_SPHINXQL );
16492 	tOut.SendWord ( VER_COMMAND_SPHINXQL );
16493 	tOut.SendInt ( iReqSize + 4 );
16494 
16495 	tOut.SendInt ( iReqSize );
16496 	tOut.SendBytes ( m_sBegin.cstr(), m_sBegin.Length() );
16497 	tOut.SendBytes ( sIndexes, strlen(sIndexes) );
16498 	tOut.SendBytes ( m_sEnd.cstr(), m_sEnd.Length() );
16499 }
16500 
16501 //////////////////////////////////////////////////////////////////////////
DoExtendedUpdate(const char * sIndex,const SqlStmt_t & tStmt,int & iSuccesses,int & iUpdated,bool bCommit,SearchFailuresLog_c & dFails,const ServedIndex_t * pServed,CSphString & sWarning)16502 static void DoExtendedUpdate ( const char * sIndex, const SqlStmt_t & tStmt,
16503 							int & iSuccesses, int & iUpdated, bool bCommit,
16504 							SearchFailuresLog_c & dFails, const ServedIndex_t * pServed, CSphString & sWarning )
16505 {
16506 	if ( !pServed || !pServed->m_pIndex || !pServed->m_bEnabled )
16507 	{
16508 		if ( pServed )
16509 			pServed->Unlock();
16510 		dFails.Submit ( sIndex, "index not available" );
16511 		return;
16512 	}
16513 
16514 	SearchHandler_c tHandler ( 1, true, false ); // handler unlocks index at destructor - no need to do it manually
16515 	CSphAttrUpdateEx tUpdate;
16516 	CSphString sError;
16517 
16518 	tUpdate.m_pUpdate = &tStmt.m_tUpdate;
16519 	tUpdate.m_pIndex = pServed->m_pIndex;
16520 	tUpdate.m_pError = &sError;
16521 	tUpdate.m_pWarning = &sWarning;
16522 
16523 	tHandler.RunUpdates ( tStmt.m_tQuery, sIndex, &tUpdate );
16524 
16525 	if ( sError.Length() )
16526 	{
16527 		dFails.Submit ( sIndex, sError.cstr() );
16528 		return;
16529 	}
16530 
16531 	if ( bCommit && pServed->m_bRT )
16532 	{
16533 		ISphRtIndex * pIndex = static_cast<ISphRtIndex *> ( pServed->m_pIndex );
16534 		pIndex->Commit ();
16535 	}
16536 
16537 	iUpdated += tUpdate.m_iAffected;
16538 	iSuccesses++;
16539 }
16540 
16541 
ExtractDistributedIndexes(const CSphVector<CSphString> & dNames,CSphVector<DistributedIndex_t> & dDistributed)16542 static const char * ExtractDistributedIndexes ( const CSphVector<CSphString> & dNames, CSphVector<DistributedIndex_t> & dDistributed )
16543 {
16544 	assert ( dNames.GetLength()==dDistributed.GetLength() );
16545 	ARRAY_FOREACH ( i, dNames )
16546 	{
16547 		if ( !g_pLocalIndexes->Exists ( dNames[i] ) )
16548 		{
16549 			// search amongst distributed and copy for further processing
16550 			g_tDistLock.Lock();
16551 			const DistributedIndex_t * pDistIndex = g_hDistIndexes ( dNames[i] );
16552 
16553 			if ( pDistIndex )
16554 			{
16555 				dDistributed[i] = *pDistIndex;
16556 			}
16557 
16558 			g_tDistLock.Unlock();
16559 
16560 			if ( !pDistIndex )
16561 				return dNames[i].cstr();
16562 		}
16563 	}
16564 
16565 	return NULL;
16566 }
16567 
16568 
HandleMysqlUpdate(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt,const CSphString & sQuery,bool bCommit,CSphString & sWarning)16569 void HandleMysqlUpdate ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt, const CSphString & sQuery, bool bCommit, CSphString & sWarning )
16570 {
16571 	CSphString sError;
16572 
16573 	// extract index names
16574 	CSphVector<CSphString> dIndexNames;
16575 	ParseIndexList ( tStmt.m_sIndex, dIndexNames );
16576 	if ( !dIndexNames.GetLength() )
16577 	{
16578 		sError.SetSprintf ( "no such index '%s'", tStmt.m_sIndex.cstr() );
16579 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
16580 		return;
16581 	}
16582 
16583 	// lock safe storage for distributed indexes
16584 	CSphVector<DistributedIndex_t> dDistributed ( dIndexNames.GetLength() );
16585 	// copy distributed indexes description
16586 	const char * sMissedDist = NULL;
16587 	if ( ( sMissedDist = ExtractDistributedIndexes ( dIndexNames, dDistributed ) )!=NULL )
16588 	{
16589 		sError.SetSprintf ( "unknown index '%s' in update request", sMissedDist );
16590 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
16591 		return;
16592 	}
16593 
16594 	// do update
16595 	SearchFailuresLog_c dFails;
16596 	int iSuccesses = 0;
16597 	int iUpdated = 0;
16598 	int iWarns = 0;
16599 
16600 	bool bMvaUpdate = false;
16601 	ARRAY_FOREACH_COND ( i, tStmt.m_tUpdate.m_dAttrs, !bMvaUpdate )
16602 	{
16603 		bMvaUpdate = ( tStmt.m_tUpdate.m_dTypes[i]==SPH_ATTR_UINT32SET
16604 			|| tStmt.m_tUpdate.m_dTypes[i]==SPH_ATTR_INT64SET );
16605 	}
16606 
16607 	ARRAY_FOREACH ( iIdx, dIndexNames )
16608 	{
16609 		const char * sReqIndex = dIndexNames[iIdx].cstr();
16610 		const ServedIndex_t * pLocked = UpdateGetLockedIndex ( sReqIndex, bMvaUpdate );
16611 		if ( pLocked )
16612 		{
16613 			DoExtendedUpdate ( sReqIndex, tStmt, iSuccesses, iUpdated, bCommit, dFails, pLocked, sWarning );
16614 		} else
16615 		{
16616 			assert ( dDistributed[iIdx].m_dLocal.GetLength() || dDistributed[iIdx].m_dAgents.GetLength() );
16617 			CSphVector<CSphString>& dLocal = dDistributed[iIdx].m_dLocal;
16618 
16619 			ARRAY_FOREACH ( i, dLocal )
16620 			{
16621 				const char * sLocal = dLocal[i].cstr();
16622 				const ServedIndex_t * pServed = UpdateGetLockedIndex ( sLocal, bMvaUpdate );
16623 				DoExtendedUpdate ( sLocal, tStmt, iSuccesses, iUpdated, bCommit, dFails, pServed, sWarning );
16624 			}
16625 		}
16626 
16627 		// update remote agents
16628 		if ( dDistributed[iIdx].m_dAgents.GetLength() )
16629 		{
16630 			DistributedIndex_t & tDist = dDistributed[iIdx];
16631 
16632 			CSphVector<AgentConn_t> dAgents;
16633 			tDist.GetAllAgents ( &dAgents );
16634 
16635 			// connect to remote agents and query them
16636 			SphinxqlRequestBuilder_t tReqBuilder ( sQuery, tStmt );
16637 			CSphRemoteAgentsController tDistCtrl ( g_iDistThreads, dAgents, tReqBuilder, tDist.m_iAgentConnectTimeout );
16638 			int iAgentsDone = tDistCtrl.Finish();
16639 			if ( iAgentsDone )
16640 			{
16641 				SphinxqlReplyParser_t tParser ( &iUpdated, &iWarns );
16642 				iSuccesses += RemoteWaitForAgents ( dAgents, tDist.m_iAgentQueryTimeout, tParser ); // FIXME? profile update time too?
16643 			}
16644 		}
16645 	}
16646 
16647 	CSphStringBuilder sReport;
16648 	dFails.BuildReport ( sReport );
16649 
16650 	if ( !iSuccesses )
16651 	{
16652 		tOut.Error ( tStmt.m_sStmt, sReport.cstr() );
16653 		return;
16654 	}
16655 
16656 	tOut.Ok ( iUpdated, iWarns );
16657 }
16658 
HandleMysqlSelect(SqlRowBuffer_c & dRows,SearchHandler_c & tHandler)16659 bool HandleMysqlSelect ( SqlRowBuffer_c & dRows, SearchHandler_c & tHandler )
16660 {
16661 	// lets check all query for errors
16662 	CSphString sError;
16663 	CSphVector<int64_t> dAgentTimes; // dummy for error reporting
16664 	ARRAY_FOREACH ( i, tHandler.m_dQueries )
16665 	{
16666 		CheckQuery ( tHandler.m_dQueries[i], tHandler.m_dResults[i].m_sError );
16667 		if ( !tHandler.m_dResults[i].m_sError.IsEmpty() )
16668 		{
16669 			LogQuery ( tHandler.m_dQueries[i], tHandler.m_dResults[i], dAgentTimes );
16670 			if ( sError.IsEmpty() )
16671 			{
16672 				if ( tHandler.m_dQueries.GetLength()==1 )
16673 					sError = tHandler.m_dResults[0].m_sError;
16674 				else
16675 					sError.SetSprintf ( "query %d error: %s", i, tHandler.m_dResults[i].m_sError.cstr() );
16676 			} else
16677 				sError.SetSprintf ( "%s; query %d error: %s", sError.cstr(), i, tHandler.m_dResults[i].m_sError.cstr() );
16678 		}
16679 	}
16680 
16681 	if ( sError.Length() )
16682 	{
16683 		// stmt is intentionally NULL, as we did all the reporting just above
16684 		dRows.Error ( NULL, sError.cstr() );
16685 		return false;
16686 	}
16687 
16688 	// actual searching
16689 	tHandler.RunQueries();
16690 
16691 	if ( g_bGotSigterm )
16692 	{
16693 		sphLogDebug ( "HandleClientMySQL: got SIGTERM, sending the packet MYSQL_ERR_SERVER_SHUTDOWN" );
16694 		dRows.Error ( NULL, "Server shutdown in progress", MYSQL_ERR_SERVER_SHUTDOWN );
16695 		return false;
16696 	}
16697 
16698 	return true;
16699 }
16700 
16701 
FormatFactors(CSphVector<BYTE> & dOut,const unsigned int * pFactors,bool bJson)16702 static void FormatFactors ( CSphVector<BYTE> & dOut, const unsigned int * pFactors, bool bJson )
16703 {
16704 	const int MAX_STR_LEN = 512;
16705 	int iLen;
16706 	int iOff = dOut.GetLength();
16707 	dOut.Resize ( iOff+MAX_STR_LEN );
16708 	if ( !bJson )
16709 	{
16710 		iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, "bm25=%d, bm25a=%f, field_mask=%u, doc_word_count=%d",
16711 			sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_BM25 ), sphinx_get_doc_factor_float ( pFactors, SPH_DOCF_BM25A ),
16712 			sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_MATCHED_FIELDS ), sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_DOC_WORD_COUNT ) );
16713 	} else
16714 	{
16715 		iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, "{\"bm25\":%d, \"bm25a\":%f, \"field_mask\":%u, \"doc_word_count\":%d",
16716 			sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_BM25 ), sphinx_get_doc_factor_float ( pFactors, SPH_DOCF_BM25A ),
16717 			sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_MATCHED_FIELDS ), sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_DOC_WORD_COUNT ) );
16718 	}
16719 	dOut.Resize ( iOff+iLen );
16720 
16721 	if ( bJson )
16722 	{
16723 		iOff = dOut.GetLength();
16724 		dOut.Resize ( iOff+MAX_STR_LEN );
16725 		iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, ", \"fields\":[" );
16726 		dOut.Resize ( iOff+iLen );
16727 	}
16728 
16729 	const unsigned int * pExactHit = sphinx_get_doc_factor_ptr ( pFactors, SPH_DOCF_EXACT_HIT_MASK );
16730 	const unsigned int * pExactOrder = sphinx_get_doc_factor_ptr ( pFactors, SPH_DOCF_EXACT_ORDER_MASK );
16731 
16732 	int iFields = sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_NUM_FIELDS );
16733 	bool bFields = false;
16734 	for ( int i = 0; i<iFields; i++ )
16735 	{
16736 		const unsigned int * pField = sphinx_get_field_factors ( pFactors, i );
16737 		int iHits = sphinx_get_field_factor_int ( pField, SPH_FIELDF_HIT_COUNT );
16738 		if ( !iHits )
16739 			continue;
16740 
16741 		iOff = dOut.GetLength();
16742 		dOut.Resize ( iOff+MAX_STR_LEN );
16743 
16744 		int iFieldPos = i/32;
16745 		int iFieldBit = 1<<( i % 32 );
16746 		int iGotExactHit = ( ( pExactHit [ iFieldPos ] & iFieldBit )==0 ? 0 : 1 );
16747 		int iGotExactOrder = ( ( pExactOrder [ iFieldPos ] & iFieldBit )==0 ? 0 : 1 );
16748 
16749 		if ( !bJson )
16750 		{
16751 			iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, ", field%d="
16752 				"(lcs=%u, hit_count=%u, word_count=%u, "
16753 				"tf_idf=%f, min_idf=%f, max_idf=%f, sum_idf=%f, "
16754 				"min_hit_pos=%d, min_best_span_pos=%d, exact_hit=%u, max_window_hits=%d, "
16755 				"min_gaps=%d, exact_order=%d, lccs=%d, wlccs=%f, atc=%f)",
16756 				i,
16757 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_LCS ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_HIT_COUNT ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_WORD_COUNT ),
16758 				sphinx_get_field_factor_float ( pField, SPH_FIELDF_TF_IDF ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_MIN_IDF ),
16759 				sphinx_get_field_factor_float ( pField, SPH_FIELDF_MAX_IDF ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_SUM_IDF ),
16760 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_HIT_POS ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_BEST_SPAN_POS ),
16761 				iGotExactHit, sphinx_get_field_factor_int ( pField, SPH_FIELDF_MAX_WINDOW_HITS ),
16762 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_GAPS ), iGotExactOrder,
16763 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_LCCS ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_WLCCS ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_ATC ) );
16764 		} else
16765 		{
16766 			iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN,
16767 				"%s{\"field\":%d, \"lcs\":%u, \"hit_count\":%u, \"word_count\":%u, "
16768 				"\"tf_idf\":%f, \"min_idf\":%f, \"max_idf\":%f, \"sum_idf\":%f, "
16769 				"\"min_hit_pos\":%d, \"min_best_span_pos\":%d, \"exact_hit\":%u, \"max_window_hits\":%d, "
16770 				"\"min_gaps\":%d, \"exact_order\":%d, \"lccs\":%d, \"wlccs\":%f, \"atc\":%f}",
16771 				bFields ? ", " : "", i, sphinx_get_field_factor_int ( pField, SPH_FIELDF_LCS ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_HIT_COUNT ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_WORD_COUNT ),
16772 				sphinx_get_field_factor_float ( pField, SPH_FIELDF_TF_IDF ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_MIN_IDF ),
16773 				sphinx_get_field_factor_float ( pField, SPH_FIELDF_MAX_IDF ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_SUM_IDF ),
16774 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_HIT_POS ), sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_BEST_SPAN_POS ),
16775 				iGotExactHit, sphinx_get_field_factor_int ( pField, SPH_FIELDF_MAX_WINDOW_HITS ),
16776 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_MIN_GAPS ), iGotExactOrder,
16777 				sphinx_get_field_factor_int ( pField, SPH_FIELDF_LCCS ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_WLCCS ), sphinx_get_field_factor_float ( pField, SPH_FIELDF_ATC ) );
16778 			bFields = true;
16779 		}
16780 
16781 		dOut.Resize ( iOff+iLen );
16782 	}
16783 
16784 	int iUniqQpos = sphinx_get_doc_factor_int ( pFactors, SPH_DOCF_MAX_UNIQ_QPOS );
16785 	if ( bJson )
16786 	{
16787 		iOff = dOut.GetLength();
16788 		dOut.Resize ( iOff+MAX_STR_LEN );
16789 		iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, "], \"words\":[" );
16790 		dOut.Resize ( iOff+iLen );
16791 	}
16792 	bool bWord = false;
16793 	for ( int i = 0; i<iUniqQpos; i++ )
16794 	{
16795 		const unsigned int * pTerm = sphinx_get_term_factors ( pFactors, i+1 );
16796 		int iKeywordMask = sphinx_get_term_factor_int ( pTerm, SPH_TERMF_KEYWORD_MASK );
16797 		if ( !iKeywordMask )
16798 			continue;
16799 
16800 		iOff = dOut.GetLength();
16801 		dOut.Resize ( iOff+MAX_STR_LEN );
16802 		if ( !bJson )
16803 		{
16804 			iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, ", word%d=(tf=%d, idf=%f)", i,
16805 				sphinx_get_term_factor_int ( pTerm, SPH_TERMF_TF ), sphinx_get_term_factor_float ( pTerm, SPH_TERMF_IDF ) );
16806 		} else
16807 		{
16808 			iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, "%s{\"tf\":%d, \"idf\":%f}", ( bWord ? ", " : "" ),
16809 				sphinx_get_term_factor_int ( pTerm, SPH_TERMF_TF ), sphinx_get_term_factor_float ( pTerm, SPH_TERMF_IDF ) );
16810 			bWord = true;
16811 		}
16812 		dOut.Resize ( iOff+iLen );
16813 	}
16814 
16815 	if ( bJson )
16816 	{
16817 		iOff = dOut.GetLength();
16818 		dOut.Resize ( iOff+MAX_STR_LEN );
16819 		iLen = snprintf ( (char *)dOut.Begin()+iOff, MAX_STR_LEN, "]}" );
16820 		dOut.Resize ( iOff+iLen );
16821 	}
16822 }
16823 
16824 
ReturnZeroCount(const CSphRsetSchema & tSchema,int iAttrsCount,const CSphString & sName,SqlRowBuffer_c & dRows)16825 static void ReturnZeroCount ( const CSphRsetSchema & tSchema, int iAttrsCount, const CSphString & sName, SqlRowBuffer_c & dRows )
16826 {
16827 	for ( int i=0; i<iAttrsCount; i++ )
16828 	{
16829 		const CSphColumnInfo & tCol = tSchema.GetAttr(i);
16830 
16831 		if ( tCol.m_sName==sName ) // @count or its alias
16832 			dRows.PutNumeric<DWORD> ( "%u", 0 );
16833 		else
16834 		{
16835 			// essentially the same as SELECT_DUAL, parse and print constant expressions
16836 			ESphAttr eAttrType;
16837 			CSphString sError;
16838 			ISphExpr * pExpr = sphExprParse ( tCol.m_sName.cstr(), tSchema, &eAttrType, NULL, sError, NULL );
16839 
16840 			if ( !pExpr || !pExpr->IsConst() )
16841 				eAttrType = SPH_ATTR_NONE;
16842 
16843 			CSphMatch tMatch;
16844 			const BYTE * pStr = NULL;
16845 
16846 			switch ( eAttrType )
16847 			{
16848 				case SPH_ATTR_STRINGPTR:
16849 					pExpr->StringEval ( tMatch, &pStr );
16850 					dRows.PutString ( (const char*)pStr );
16851 					SafeDelete ( pStr );
16852 					break;
16853 				case SPH_ATTR_INTEGER:	dRows.PutNumeric<int> ( "%d", pExpr->IntEval ( tMatch ) ); break;
16854 				case SPH_ATTR_BIGINT:	dRows.PutNumeric<SphAttr_t> ( INT64_FMT, pExpr->Int64Eval ( tMatch ) ); break;
16855 				case SPH_ATTR_FLOAT:	dRows.PutNumeric<float> ( "%f", pExpr->Eval ( tMatch ) ); break;
16856 				default:
16857 					dRows.PutNULL();
16858 					break;
16859 			}
16860 
16861 			SafeDelete ( pExpr );
16862 		}
16863 	}
16864 	dRows.Commit();
16865 }
16866 
16867 
SendMysqlSelectResult(SqlRowBuffer_c & dRows,const AggrResult_t & tRes,bool bMoreResultsFollow)16868 void SendMysqlSelectResult ( SqlRowBuffer_c & dRows, const AggrResult_t & tRes, bool bMoreResultsFollow )
16869 {
16870 	if ( !tRes.m_iSuccesses )
16871 	{
16872 		// at this point, SELECT error logging should have been handled, so pass a NULL stmt to logger
16873 		dRows.Error ( NULL, tRes.m_sError.cstr() );
16874 		return;
16875 	}
16876 
16877 	// empty result sets just might carry the full uberschema
16878 	// bummer! lets protect ourselves against that
16879 	int iSchemaAttrsCount = 0;
16880 	int iAttrsCount = 1;
16881 	bool bReturnZeroCount = !tRes.m_sZeroCountName.IsEmpty();
16882 	if ( tRes.m_dMatches.GetLength() || bReturnZeroCount )
16883 	{
16884 		iSchemaAttrsCount = SendGetAttrCount ( tRes.m_tSchema );
16885 		iAttrsCount = iSchemaAttrsCount;
16886 	}
16887 
16888 	// result set header packet. We will attach EOF manually at the end.
16889 	dRows.HeadBegin ( iAttrsCount );
16890 
16891 	// field packets
16892 	if ( !tRes.m_dMatches.GetLength() && !bReturnZeroCount )
16893 	{
16894 		// in case there are no matches, send a dummy schema
16895 		dRows.HeadColumn ( "id", USE_64BIT ? MYSQL_COL_LONGLONG : MYSQL_COL_LONG );
16896 	} else
16897 	{
16898 		for ( int i=0; i<iSchemaAttrsCount; i++ )
16899 		{
16900 			const CSphColumnInfo & tCol = tRes.m_tSchema.GetAttr(i);
16901 			MysqlColumnType_e eType = MYSQL_COL_STRING;
16902 			if ( tCol.m_eAttrType==SPH_ATTR_INTEGER || tCol.m_eAttrType==SPH_ATTR_TIMESTAMP || tCol.m_eAttrType==SPH_ATTR_BOOL )
16903 				eType = MYSQL_COL_LONG;
16904 			if ( tCol.m_eAttrType==SPH_ATTR_FLOAT )
16905 				eType = MYSQL_COL_FLOAT;
16906 			if ( tCol.m_eAttrType==SPH_ATTR_BIGINT )
16907 				eType = MYSQL_COL_LONGLONG;
16908 			if ( tCol.m_eAttrType==SPH_ATTR_STRING || tCol.m_eAttrType==SPH_ATTR_STRINGPTR || tCol.m_eAttrType==SPH_ATTR_FACTORS || tCol.m_eAttrType==SPH_ATTR_FACTORS_JSON )
16909 				eType = MYSQL_COL_STRING;
16910 			dRows.HeadColumn ( tCol.m_sName.cstr(), eType );
16911 		}
16912 	}
16913 
16914 	// EOF packet is sent explicitly due to non-default params.
16915 	BYTE iWarns = ( !tRes.m_sWarning.IsEmpty() ) ? 1 : 0;
16916 	dRows.HeadEnd ( bMoreResultsFollow, iWarns );
16917 
16918 	// FIXME!!! replace that vector relocations by SqlRowBuffer
16919 	CSphVector<BYTE> dTmp;
16920 
16921 	// rows
16922 	for ( int iMatch = tRes.m_iOffset; iMatch < tRes.m_iOffset + tRes.m_iCount; iMatch++ )
16923 	{
16924 		const CSphMatch & tMatch = tRes.m_dMatches [ iMatch ];
16925 
16926 		const CSphRsetSchema & tSchema = tRes.m_tSchema;
16927 		for ( int i=0; i<iSchemaAttrsCount; i++ )
16928 		{
16929 			CSphAttrLocator tLoc = tSchema.GetAttr(i).m_tLocator;
16930 			ESphAttr eAttrType = tSchema.GetAttr(i).m_eAttrType;
16931 
16932 			switch ( eAttrType )
16933 			{
16934 			case SPH_ATTR_INTEGER:
16935 			case SPH_ATTR_TIMESTAMP:
16936 			case SPH_ATTR_BOOL:
16937 			case SPH_ATTR_TOKENCOUNT:
16938 				dRows.PutNumeric<DWORD> ( "%u", (DWORD)tMatch.GetAttr(tLoc) );
16939 				break;
16940 
16941 			case SPH_ATTR_BIGINT:
16942 			{
16943 				const char * sName = tSchema.GetAttr(i).m_sName.cstr();
16944 				// how to get rid of this if?
16945 				if ( sName[0]=='i' && sName[1]=='d' && sName[2]=='\0' )
16946 					dRows.PutNumeric<SphDocID_t> ( DOCID_FMT, tMatch.m_uDocID );
16947 				else
16948 					dRows.PutNumeric<SphAttr_t> ( INT64_FMT, tMatch.GetAttr(tLoc) );
16949 				break;
16950 				}
16951 
16952 			case SPH_ATTR_FLOAT:
16953 				dRows.PutNumeric ( "%f", tMatch.GetAttrFloat(tLoc) );
16954 				break;
16955 
16956 			case SPH_ATTR_INT64SET:
16957 			case SPH_ATTR_UINT32SET:
16958 				{
16959 					int iLenOff = dRows.Length();
16960 					dRows.Reserve ( 4 );
16961 					dRows.IncPtr ( 4 );
16962 
16963 					assert ( tMatch.GetAttr ( tLoc )==0 || tRes.m_dTag2Pools [ tMatch.m_iTag ].m_pMva || ( MVA_DOWNSIZE ( tMatch.GetAttr ( tLoc ) ) & MVA_ARENA_FLAG ) );
16964 					const PoolPtrs_t & tPools = tRes.m_dTag2Pools [ tMatch.m_iTag ];
16965 					const DWORD * pValues = tMatch.GetAttrMVA ( tLoc, tPools.m_pMva, tPools.m_bArenaProhibit );
16966 					if ( pValues )
16967 					{
16968 						DWORD nValues = *pValues++;
16969 						assert ( eAttrType==SPH_ATTR_UINT32SET || ( nValues%2 )==0 );
16970 						if ( eAttrType==SPH_ATTR_UINT32SET )
16971 						{
16972 							while ( nValues-- )
16973 							{
16974 								dRows.Reserve ( SPH_MAX_NUMERIC_STR );
16975 								int iLen = snprintf ( dRows.Get(), SPH_MAX_NUMERIC_STR, nValues>0 ? "%u," : "%u", *pValues++ );
16976 								dRows.IncPtr ( iLen );
16977 							}
16978 						} else
16979 						{
16980 							for ( ; nValues; nValues-=2, pValues+=2 )
16981 							{
16982 								int64_t iVal = MVA_UPSIZE ( pValues );
16983 								dRows.Reserve ( SPH_MAX_NUMERIC_STR );
16984 								int iLen = snprintf ( dRows.Get(), SPH_MAX_NUMERIC_STR, nValues>2 ? INT64_FMT"," : INT64_FMT, iVal );
16985 								dRows.IncPtr ( iLen );
16986 							}
16987 						}
16988 					}
16989 
16990 					// manually pack length, forcibly into exactly 3 bytes
16991 					int iLen = dRows.Length()-iLenOff-4;
16992 					char * pLen = dRows.Off ( iLenOff );
16993 					pLen[0] = (BYTE)0xfd;
16994 					pLen[1] = (BYTE)( iLen & 0xff );
16995 					pLen[2] = (BYTE)( ( iLen>>8 ) & 0xff );
16996 					pLen[3] = (BYTE)( ( iLen>>16 ) & 0xff );
16997 					break;
16998 				}
16999 
17000 			case SPH_ATTR_STRING:
17001 			case SPH_ATTR_JSON:
17002 				{
17003 					const BYTE * pStrings = tRes.m_dTag2Pools [ tMatch.m_iTag ].m_pStrings;
17004 
17005 					// get that string
17006 					const BYTE * pStr = NULL;
17007 					int iLen = 0;
17008 
17009 					DWORD uOffset = (DWORD) tMatch.GetAttr ( tLoc );
17010 					if ( uOffset )
17011 					{
17012 						assert ( pStrings );
17013 						iLen = sphUnpackStr ( pStrings+uOffset, &pStr );
17014 					}
17015 
17016 					if ( eAttrType==SPH_ATTR_JSON )
17017 					{
17018 						// no object at all? return NULL
17019 						if ( !pStr )
17020 						{
17021 							dRows.PutNULL();
17022 							break;
17023 						}
17024 						dTmp.Resize ( 0 );
17025 						sphJsonFormat ( dTmp, pStr );
17026 						pStr = dTmp.Begin();
17027 						iLen = dTmp.GetLength();
17028 						if ( iLen==0 )
17029 						{
17030 							// empty string (no objects) - return NULL
17031 							// (canonical "{}" and "[]" are handled by sphJsonFormat)
17032 							dRows.PutNULL();
17033 							break;
17034 						}
17035 					}
17036 
17037 					// send length
17038 					dRows.Reserve ( iLen+4 );
17039 					char * pOutStr = (char*)MysqlPack ( dRows.Get(), iLen );
17040 
17041 					// send string data
17042 					if ( iLen )
17043 						memcpy ( pOutStr, pStr, iLen );
17044 
17045 					dRows.IncPtr ( pOutStr-dRows.Get()+iLen );
17046 					break;
17047 				}
17048 
17049 			case SPH_ATTR_STRINGPTR:
17050 				{
17051 					int iLen = 0;
17052 					const char* pString = (const char*) tMatch.GetAttr ( tLoc );
17053 					if ( pString )
17054 						iLen = strlen ( pString );
17055 					else
17056 					{
17057 						// stringptr is NULL - send NULL value
17058 						dRows.PutNULL();
17059 						break;
17060 					}
17061 
17062 					// send length
17063 					dRows.Reserve ( iLen+4 );
17064 					char * pOutStr = (char*)MysqlPack ( dRows.Get(), iLen );
17065 
17066 					// send string data
17067 					if ( iLen )
17068 						memcpy ( pOutStr, pString, iLen );
17069 
17070 					dRows.IncPtr ( pOutStr-dRows.Get()+iLen );
17071 					break;
17072 				}
17073 
17074 			case SPH_ATTR_FACTORS:
17075 			case SPH_ATTR_FACTORS_JSON:
17076 				{
17077 					int iLen = 0;
17078 					const BYTE * pStr = NULL;
17079 					const unsigned int * pFactors = (unsigned int*) tMatch.GetAttr ( tLoc );
17080 					if ( pFactors )
17081 					{
17082 						dTmp.Resize ( 0 );
17083 						FormatFactors ( dTmp, pFactors, eAttrType==SPH_ATTR_FACTORS_JSON );
17084 						iLen = dTmp.GetLength();
17085 						pStr = dTmp.Begin();
17086 					}
17087 
17088 					// send length
17089 					dRows.Reserve ( iLen+4 );
17090 					char * pOutStr = (char*)MysqlPack ( dRows.Get(), iLen );
17091 
17092 					// send string data
17093 					if ( iLen )
17094 						memcpy ( pOutStr, pStr, iLen );
17095 
17096 					dRows.IncPtr ( pOutStr-dRows.Get()+iLen );
17097 					break;
17098 				}
17099 
17100 			case SPH_ATTR_JSON_FIELD:
17101 				{
17102 					uint64_t uTypeOffset = tMatch.GetAttr ( tLoc );
17103 					ESphJsonType eJson = ESphJsonType ( uTypeOffset>>32 );
17104 					DWORD uOff = (DWORD)uTypeOffset;
17105 					if ( !uOff || eJson==JSON_NULL )
17106 					{
17107 						// no key found - NULL value
17108 						dRows.PutNULL();
17109 
17110 					} else
17111 					{
17112 						// send string to client
17113 						dTmp.Resize ( 0 );
17114 						const BYTE * pStrings = tRes.m_dTag2Pools [ tMatch.m_iTag ].m_pStrings;
17115 						sphJsonFieldFormat ( dTmp, pStrings+uOff, eJson, false );
17116 
17117 						// send length
17118 						int iLen = dTmp.GetLength();
17119 						dRows.Reserve ( iLen+4 );
17120 						char * pOutStr = (char*)MysqlPack ( dRows.Get(), iLen );
17121 
17122 						// send string data
17123 						if ( iLen )
17124 							memcpy ( pOutStr, dTmp.Begin(), iLen );
17125 
17126 						dRows.IncPtr ( pOutStr-dRows.Get()+iLen );
17127 					}
17128 					break;
17129 				}
17130 
17131 			default:
17132 				char * pDef = dRows.Reserve ( 2 );
17133 				pDef[0] = 1;
17134 				pDef[1] = '-';
17135 				dRows.IncPtr ( 2 );
17136 				break;
17137 			}
17138 		}
17139 		dRows.Commit();
17140 	}
17141 
17142 	if ( bReturnZeroCount )
17143 		ReturnZeroCount ( tRes.m_tSchema, iSchemaAttrsCount, tRes.m_sZeroCountName, dRows );
17144 
17145 	// eof packet
17146 	dRows.Eof ( bMoreResultsFollow, iWarns );
17147 }
17148 
17149 
HandleMysqlWarning(const CSphQueryResultMeta & tLastMeta,SqlRowBuffer_c & dRows,bool bMoreResultsFollow)17150 void HandleMysqlWarning ( const CSphQueryResultMeta & tLastMeta, SqlRowBuffer_c & dRows, bool bMoreResultsFollow )
17151 {
17152 	// can't send simple ok if there are more results to send
17153 	// as it breaks order of multi-result output
17154 	if ( tLastMeta.m_sWarning.IsEmpty() && !bMoreResultsFollow )
17155 	{
17156 		dRows.Ok();
17157 		return;
17158 	}
17159 
17160 	// result set header packet
17161 	dRows.HeadBegin(3);
17162 	dRows.HeadColumn ( "Level" );
17163 	dRows.HeadColumn ( "Code", MYSQL_COL_DECIMAL );
17164 	dRows.HeadColumn ( "Message" );
17165 	dRows.HeadEnd ( bMoreResultsFollow );
17166 
17167 	// row
17168 	dRows.PutString ( "warning" );
17169 	dRows.PutString ( "1000" );
17170 	dRows.PutString ( tLastMeta.m_sWarning.cstr() );
17171 	dRows.Commit();
17172 
17173 	// cleanup
17174 	dRows.Eof ( bMoreResultsFollow );
17175 }
17176 
HandleMysqlMeta(SqlRowBuffer_c & dRows,const SqlStmt_t & tStmt,const CSphQueryResultMeta & tLastMeta,bool bMoreResultsFollow)17177 void HandleMysqlMeta ( SqlRowBuffer_c & dRows, const SqlStmt_t & tStmt, const CSphQueryResultMeta & tLastMeta, bool bMoreResultsFollow )
17178 {
17179 	VectorLike dStatus ( tStmt.m_sStringParam );
17180 
17181 	switch ( tStmt.m_eStmt )
17182 	{
17183 	case STMT_SHOW_STATUS:
17184 		BuildStatus ( dStatus );
17185 		break;
17186 	case STMT_SHOW_META:
17187 		BuildMeta ( dStatus, tLastMeta );
17188 		break;
17189 	case STMT_SHOW_AGENT_STATUS:
17190 		BuildAgentStatus ( dStatus, tStmt.m_sIndex );
17191 		break;
17192 	default:
17193 		assert(0); // only 'show' statements allowed here.
17194 		break;
17195 	}
17196 
17197 	// result set header packet
17198 	dRows.HeadTuplet ( dStatus.szColKey(), dStatus.szColValue() );
17199 
17200 	// send rows
17201 	for ( int iRow=0; iRow<dStatus.GetLength(); iRow+=2 )
17202 		dRows.DataTuplet ( dStatus[iRow+0].cstr(), dStatus[iRow+1].cstr() );
17203 
17204 	// cleanup
17205 	dRows.Eof ( bMoreResultsFollow );
17206 }
17207 
17208 
LocalIndexDoDeleteDocuments(const char * sName,const SqlStmt_t & tStmt,const SphDocID_t * pDocs,int iCount,const ServedIndex_t * pLocked,SearchFailuresLog_c & dErrors,bool bCommit)17209 static int LocalIndexDoDeleteDocuments ( const char * sName, const SqlStmt_t & tStmt, const SphDocID_t * pDocs, int iCount,
17210 											const ServedIndex_t * pLocked, SearchFailuresLog_c & dErrors, bool bCommit )
17211 {
17212 	if ( !pLocked || !pLocked->m_pIndex )
17213 	{
17214 		if ( pLocked )
17215 			pLocked->Unlock();
17216 		dErrors.Submit ( sName, "index not available" );
17217 		return 0;
17218 	}
17219 
17220 	CSphString sError;
17221 	ISphRtIndex * pIndex = static_cast<ISphRtIndex *> ( pLocked->m_pIndex );
17222 	if ( !pLocked->m_bRT || !pLocked->m_bEnabled )
17223 	{
17224 		sError.SetSprintf ( "does not support DELETE (enabled=%d)", pLocked->m_bEnabled );
17225 		dErrors.Submit ( sName, sError.cstr() );
17226 		pLocked->Unlock();
17227 		return 0;
17228 	}
17229 
17230 	SearchHandler_c * pHandler = NULL;
17231 	CSphVector<SphDocID_t> dValues;
17232 	if ( !pDocs ) // needs to be deleted via query
17233 	{
17234 		pHandler = new SearchHandler_c ( 1, true, false ); // handler unlocks index at destructor - no need to do it manually
17235 		pHandler->RunDeletes ( tStmt.m_tQuery, sName, &sError, &dValues );
17236 		pDocs = dValues.Begin();
17237 		iCount = dValues.GetLength();
17238 	}
17239 
17240 	if ( !pIndex->DeleteDocument ( pDocs, iCount, sError ) )
17241 	{
17242 		dErrors.Submit ( sName, sError.cstr() );
17243 		if ( pHandler )
17244 			delete pHandler;
17245 		else
17246 			pLocked->Unlock();
17247 		return 0;
17248 	}
17249 
17250 	int iAffected = 0;
17251 	if ( bCommit )
17252 		pIndex->Commit ( &iAffected );
17253 
17254 	if ( pHandler )
17255 		delete pHandler;
17256 	else
17257 		pLocked->Unlock();
17258 	return iAffected;
17259 }
17260 
17261 
HandleMysqlDelete(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt,const CSphString & sQuery,bool bCommit)17262 void HandleMysqlDelete ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt, const CSphString & sQuery, bool bCommit )
17263 {
17264 	MEMORY ( MEM_SQL_DELETE );
17265 
17266 	CSphString sError;
17267 
17268 	CSphVector<CSphString> dNames;
17269 	ParseIndexList ( tStmt.m_sIndex, dNames );
17270 	if ( !dNames.GetLength() )
17271 	{
17272 		sError.SetSprintf ( "no such index '%s'", tStmt.m_sIndex.cstr() );
17273 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17274 		return;
17275 	}
17276 
17277 	CSphVector<DistributedIndex_t> dDistributed ( dNames.GetLength() );
17278 	const char * sMissedDist = NULL;
17279 	if ( ( sMissedDist = ExtractDistributedIndexes ( dNames, dDistributed ) )!=NULL )
17280 	{
17281 		sError.SetSprintf ( "unknown index '%s' in delete request", sError.cstr() );
17282 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17283 		return;
17284 	}
17285 
17286 	// delete to agents works only with commit=1
17287 	if ( !bCommit && dDistributed.GetLength() )
17288 	{
17289 		ARRAY_FOREACH ( i, dDistributed )
17290 		{
17291 			if ( !dDistributed[i].m_dAgents.GetLength() )
17292 				continue;
17293 
17294 			sError.SetSprintf ( "index '%s': DELETE not working on agents when autocommit=0", tStmt.m_sIndex.cstr() );
17295 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17296 			return;
17297 		}
17298 	}
17299 
17300 	const SphDocID_t * pDocs = NULL;
17301 	int iDocsCount = 0;
17302 	CSphVector<SphDocID_t> dDeleteIds;
17303 
17304 	// now check the short path - if we have clauses 'id=smth' or 'id in (xx,yy)' or 'id in @uservar' - we know
17305 	// all the values list immediatelly and don't have to run the heavy query here.
17306 	const CSphQuery& tQuery = tStmt.m_tQuery; // shortcut
17307 
17308 	if ( tQuery.m_sQuery.IsEmpty() && tQuery.m_dFilters.GetLength()==1 )
17309 	{
17310 		const CSphFilterSettings* pFilter = tQuery.m_dFilters.Begin();
17311 		if ( pFilter->m_bHasEqual && pFilter->m_eType==SPH_FILTER_VALUES
17312 			&& pFilter->m_sAttrName=="@id" && !pFilter->m_bExclude )
17313 		{
17314 			if_const ( sizeof(SphAttr_t)==sizeof(SphDocID_t) ) // avoid copying in this case
17315 			{
17316 				pDocs = (SphDocID_t*)pFilter->GetValueArray();
17317 				iDocsCount = pFilter->GetNumValues();
17318 			} else
17319 			{
17320 				const SphAttr_t* pA = pFilter->GetValueArray();
17321 				for ( int i=0; i<pFilter->GetNumValues(); ++i )
17322 					dDeleteIds.Add ( pA[i] );
17323 				pDocs = dDeleteIds.Begin();
17324 				iDocsCount = dDeleteIds.GetLength();
17325 			}
17326 		}
17327 	}
17328 
17329 	// do delete
17330 	SearchFailuresLog_c dErrors;
17331 	int iAffected = 0;
17332 
17333 	// delete for local indexes
17334 	ARRAY_FOREACH ( iIdx, dNames )
17335 	{
17336 		const char * sName = dNames[iIdx].cstr();
17337 		const ServedIndex_t * pLocal = g_pLocalIndexes->GetRlockedEntry ( sName );
17338 		if ( pLocal )
17339 		{
17340 			iAffected += LocalIndexDoDeleteDocuments ( sName, tStmt, pDocs, iDocsCount, pLocal, dErrors, bCommit );
17341 		} else
17342 		{
17343 			assert ( dDistributed[iIdx].m_dLocal.GetLength() || dDistributed[iIdx].m_dAgents.GetLength() );
17344 			const CSphVector<CSphString> & dDistLocal = dDistributed[iIdx].m_dLocal;
17345 
17346 			ARRAY_FOREACH ( i, dDistLocal )
17347 			{
17348 				const char * sDistLocal = dDistLocal[i].cstr();
17349 				const ServedIndex_t * pDistLocal = g_pLocalIndexes->GetRlockedEntry ( sDistLocal );
17350 				iAffected += LocalIndexDoDeleteDocuments ( sDistLocal, tStmt, pDocs, iDocsCount, pDistLocal, dErrors, bCommit );
17351 			}
17352 		}
17353 
17354 		// delete for remote agents
17355 		if ( dDistributed[iIdx].m_dAgents.GetLength() )
17356 		{
17357 			const DistributedIndex_t & tDist = dDistributed[iIdx];
17358 			CSphVector<AgentConn_t> dAgents;
17359 			tDist.GetAllAgents ( &dAgents );
17360 
17361 			// connect to remote agents and query them
17362 			SphinxqlRequestBuilder_t tReqBuilder ( sQuery, tStmt );
17363 			CSphRemoteAgentsController tDistCtrl ( g_iDistThreads, dAgents, tReqBuilder, tDist.m_iAgentConnectTimeout );
17364 			int iAgentsDone = tDistCtrl.Finish();
17365 			if ( iAgentsDone )
17366 			{
17367 				// FIXME!!! report error & warnings from agents
17368 				int iGot = 0;
17369 				int iWarns = 0;
17370 				SphinxqlReplyParser_t tParser ( &iGot, &iWarns );
17371 				RemoteWaitForAgents ( dAgents, tDist.m_iAgentQueryTimeout, tParser ); // FIXME? profile update time too?
17372 				iAffected += iGot;
17373 			}
17374 		}
17375 	}
17376 
17377 	if ( !dErrors.IsEmpty() )
17378 	{
17379 		CSphStringBuilder sReport;
17380 		dErrors.BuildReport ( sReport );
17381 		tOut.Error ( tStmt.m_sStmt, sReport.cstr() );
17382 		return;
17383 	}
17384 
17385 	tOut.Ok ( iAffected );
17386 }
17387 
17388 
17389 struct SessionVars_t
17390 {
17391 	bool			m_bAutoCommit;
17392 	bool			m_bInTransaction;
17393 	ESphCollation	m_eCollation;
17394 	bool			m_bProfile;
17395 
SessionVars_tSessionVars_t17396 	SessionVars_t ()
17397 		: m_bAutoCommit ( true )
17398 		, m_bInTransaction ( false )
17399 		, m_eCollation ( g_eCollation )
17400 		, m_bProfile ( false )
17401 	{}
17402 };
17403 
17404 // fwd
17405 void HandleMysqlShowProfile ( SqlRowBuffer_c & tOut, const CSphQueryProfile & p, bool bMoreResultsFollow );
17406 static void HandleMysqlShowPlan ( SqlRowBuffer_c & tOut, const CSphQueryProfile & p, bool bMoreResultsFollow );
17407 
HandleMysqlMultiStmt(const CSphVector<SqlStmt_t> & dStmt,CSphQueryResultMeta & tLastMeta,SqlRowBuffer_c & dRows,ThdDesc_t * pThd,const CSphString & sWarning)17408 void HandleMysqlMultiStmt ( const CSphVector<SqlStmt_t> & dStmt, CSphQueryResultMeta & tLastMeta,
17409 	SqlRowBuffer_c & dRows, ThdDesc_t * pThd, const CSphString& sWarning )
17410 {
17411 	// select count
17412 	int iSelect = 0;
17413 	ARRAY_FOREACH ( i, dStmt )
17414 		if ( dStmt[i].m_eStmt==STMT_SELECT )
17415 			iSelect++;
17416 
17417 	CSphQueryResultMeta tPrevMeta = tLastMeta;
17418 
17419 	if ( pThd )
17420 		pThd->m_sCommand = g_dSqlStmts[STMT_SELECT];
17421 
17422 	StatCountCommand ( SEARCHD_COMMAND_SEARCH, iSelect );
17423 
17424 	// setup query for searching and handle set profiler statement
17425 	SearchHandler_c tHandler ( iSelect, true, true );
17426 	SessionVars_t tVars;
17427 	CSphQueryProfile tProfile;
17428 
17429 	iSelect = 0;
17430 	ARRAY_FOREACH ( i, dStmt )
17431 	{
17432 		if ( dStmt[i].m_eStmt==STMT_SELECT )
17433 			tHandler.m_dQueries[iSelect++] = dStmt[i].m_tQuery;
17434 		else if ( dStmt[i].m_eStmt==STMT_SET && dStmt[i].m_eSet==SET_LOCAL )
17435 		{
17436 			CSphString sSetName ( dStmt[i].m_sSetName );
17437 			sSetName.ToLower();
17438 			tVars.m_bProfile = ( sSetName=="profiling" && dStmt[i].m_iSetValue!=0 );
17439 		}
17440 	}
17441 
17442 	// use first meta for faceted search
17443 	bool bUseFirstMeta = ( tHandler.m_dQueries.GetLength()>1 && !tHandler.m_dQueries[0].m_bFacet && tHandler.m_dQueries[1].m_bFacet );
17444 	if ( tVars.m_bProfile )
17445 		tHandler.m_pProfile = &tProfile;
17446 
17447 	// do search
17448 	bool bSearchOK = true;
17449 	if ( iSelect )
17450 	{
17451 		bSearchOK = HandleMysqlSelect ( dRows, tHandler );
17452 
17453 		// save meta for SHOW *
17454 		tLastMeta = bUseFirstMeta ? tHandler.m_dResults[0] : tHandler.m_dResults.Last();
17455 	}
17456 
17457 	if ( !bSearchOK )
17458 		return;
17459 
17460 	// send multi-result set
17461 	iSelect = 0;
17462 	ARRAY_FOREACH ( i, dStmt )
17463 	{
17464 		SqlStmt_e eStmt = dStmt[i].m_eStmt;
17465 
17466 		THD_STATE ( THD_QUERY );
17467 		if ( pThd )
17468 			pThd->m_sCommand = g_dSqlStmts[eStmt];
17469 
17470 		const CSphQueryResultMeta & tMeta = bUseFirstMeta ? tHandler.m_dResults[0] : ( iSelect-1>=0 ? tHandler.m_dResults[iSelect-1] : tPrevMeta );
17471 		bool bMoreResultsFollow = (i+1)<dStmt.GetLength();
17472 
17473 		if ( eStmt==STMT_SELECT )
17474 		{
17475 			AggrResult_t & tRes = tHandler.m_dResults[iSelect++];
17476 			if ( !sWarning.IsEmpty() )
17477 				tRes.m_sWarning = sWarning;
17478 			SendMysqlSelectResult ( dRows, tRes, bMoreResultsFollow );
17479 			// mysql server breaks send on error
17480 			if ( !tRes.m_iSuccesses )
17481 				break;
17482 		} else if ( eStmt==STMT_SHOW_WARNINGS )
17483 			HandleMysqlWarning ( tMeta, dRows, bMoreResultsFollow );
17484 		else if ( eStmt==STMT_SHOW_STATUS || eStmt==STMT_SHOW_META || eStmt==STMT_SHOW_AGENT_STATUS )
17485 			HandleMysqlMeta ( dRows, dStmt[i], tMeta, bMoreResultsFollow ); // FIXME!!! add prediction counters
17486 		else if ( eStmt==STMT_SET ) // TODO implement all set statements and make them handle bMoreResultsFollow flag
17487 			dRows.Ok ( 0, 0, NULL, bMoreResultsFollow );
17488 		else if ( eStmt==STMT_SHOW_PROFILE )
17489 			HandleMysqlShowProfile ( dRows, tProfile, bMoreResultsFollow );
17490 		else if ( eStmt==STMT_SHOW_PLAN )
17491 			HandleMysqlShowPlan ( dRows, tProfile, bMoreResultsFollow );
17492 
17493 		if ( g_bGotSigterm )
17494 		{
17495 			sphLogDebug ( "HandleMultiStmt: got SIGTERM, sending the packet MYSQL_ERR_SERVER_SHUTDOWN" );
17496 			dRows.Error ( NULL, "Server shutdown in progress", MYSQL_ERR_SERVER_SHUTDOWN );
17497 			return;
17498 		}
17499 	}
17500 }
17501 
sphCollationFromName(const CSphString & sName,CSphString * pError)17502 static ESphCollation sphCollationFromName ( const CSphString & sName, CSphString * pError )
17503 {
17504 	assert ( pError );
17505 
17506 	// FIXME! replace with a hash lookup?
17507 	if ( sName=="libc_ci" )
17508 		return SPH_COLLATION_LIBC_CI;
17509 	else if ( sName=="libc_cs" )
17510 		return SPH_COLLATION_LIBC_CS;
17511 	else if ( sName=="utf8_general_ci" )
17512 		return SPH_COLLATION_UTF8_GENERAL_CI;
17513 	else if ( sName=="binary" )
17514 		return SPH_COLLATION_BINARY;
17515 
17516 	pError->SetSprintf ( "Unknown collation: '%s'", sName.cstr() );
17517 	return SPH_COLLATION_DEFAULT;
17518 }
17519 
17520 
UservarAdd(const CSphString & sName,CSphVector<SphAttr_t> & dVal)17521 static void UservarAdd ( const CSphString & sName, CSphVector<SphAttr_t> & dVal )
17522 {
17523 	Uservar_t * pVar = g_hUservars ( sName );
17524 	if ( pVar )
17525 	{
17526 		// variable exists, release previous value
17527 		// actual destruction of the value (aka data) might happen later
17528 		// as the concurrent queries might still be using and holding that data
17529 		// from here, the old value becomes nameless, though
17530 		assert ( pVar->m_eType==USERVAR_INT_SET );
17531 		assert ( pVar->m_pVal );
17532 		pVar->m_pVal->Release();
17533 		pVar->m_pVal = NULL;
17534 	} else
17535 	{
17536 		// create a shiny new variable
17537 		Uservar_t tVar;
17538 		g_hUservars.Add ( tVar, sName );
17539 		pVar = g_hUservars ( sName );
17540 	}
17541 
17542 	// swap in the new value
17543 	assert ( pVar );
17544 	assert ( !pVar->m_pVal );
17545 	pVar->m_eType = USERVAR_INT_SET;
17546 	pVar->m_pVal = new UservarIntSet_c();
17547 	pVar->m_pVal->SwapData ( dVal );
17548 }
17549 
17550 
HandleMysqlSet(SqlRowBuffer_c & tOut,SqlStmt_t & tStmt,SessionVars_t & tVars)17551 void HandleMysqlSet ( SqlRowBuffer_c & tOut, SqlStmt_t & tStmt, SessionVars_t & tVars )
17552 {
17553 	MEMORY ( MEM_SQL_SET );
17554 	CSphString sError;
17555 
17556 	tStmt.m_sSetName.ToLower();
17557 	switch ( tStmt.m_eSet )
17558 	{
17559 	case SET_LOCAL:
17560 		if ( tStmt.m_sSetName=="autocommit" )
17561 		{
17562 			// per-session AUTOCOMMIT
17563 			tVars.m_bAutoCommit = ( tStmt.m_iSetValue!=0 );
17564 			tVars.m_bInTransaction = false;
17565 
17566 			// commit all pending changes
17567 			if ( tVars.m_bAutoCommit )
17568 			{
17569 				ISphRtIndex * pIndex = sphGetCurrentIndexRT();
17570 				if ( pIndex )
17571 					pIndex->Commit();
17572 			}
17573 		} else if ( tStmt.m_sSetName=="collation_connection" )
17574 		{
17575 			// per-session COLLATION_CONNECTION
17576 			CSphString & sVal = tStmt.m_sSetValue;
17577 			sVal.ToLower();
17578 
17579 			tVars.m_eCollation = sphCollationFromName ( sVal, &sError );
17580 			if ( !sError.IsEmpty() )
17581 			{
17582 				tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17583 				return;
17584 			}
17585 		} else if ( tStmt.m_sSetName=="character_set_results"
17586 			|| tStmt.m_sSetName=="sql_auto_is_null"
17587 			|| tStmt.m_sSetName=="sql_safe_updates"
17588 			|| tStmt.m_sSetName=="sql_mode" )
17589 		{
17590 			// per-session CHARACTER_SET_RESULTS et al; just ignore for now
17591 
17592 		} else if ( tStmt.m_sSetName=="profiling" )
17593 		{
17594 			// per-session PROFILING
17595 			tVars.m_bProfile = ( tStmt.m_iSetValue!=0 );
17596 
17597 		} else
17598 		{
17599 			// unknown variable, return error
17600 			sError.SetSprintf ( "Unknown session variable '%s' in SET statement", tStmt.m_sSetName.cstr() );
17601 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17602 			return;
17603 		}
17604 		break;
17605 
17606 	case SET_GLOBAL_UVAR:
17607 	{
17608 		// global user variable
17609 		if ( g_eWorkers!=MPM_THREADS )
17610 		{
17611 			tOut.Error ( tStmt.m_sStmt, "SET GLOBAL currently requires workers=threads" );
17612 			return;
17613 		}
17614 
17615 		// INT_SET type must be sorted
17616 		tStmt.m_dSetValues.Sort();
17617 		SetLocalUserVar ( tStmt.m_sSetName, tStmt.m_dSetValues );
17618 		break;
17619 	}
17620 
17621 	case SET_GLOBAL_SVAR:
17622 		// global server variable
17623 		if ( g_eWorkers!=MPM_THREADS )
17624 		{
17625 			tOut.Error ( tStmt.m_sStmt, "SET GLOBAL currently requires workers=threads" );
17626 			return;
17627 		}
17628 
17629 		if ( tStmt.m_sSetName=="query_log_format" )
17630 		{
17631 			if ( tStmt.m_sSetValue=="plain" )
17632 				g_eLogFormat = LOG_FORMAT_PLAIN;
17633 			else if ( tStmt.m_sSetValue=="sphinxql" )
17634 				g_eLogFormat = LOG_FORMAT_SPHINXQL;
17635 			else
17636 			{
17637 				tOut.Error ( tStmt.m_sStmt, "Unknown query_log_format value (must be plain or sphinxql)" );
17638 				return;
17639 			}
17640 		} else if ( tStmt.m_sSetName=="log_level" )
17641 		{
17642 			if ( tStmt.m_sSetValue=="info" )
17643 				g_eLogLevel = SPH_LOG_INFO;
17644 			else if ( tStmt.m_sSetValue=="debug" )
17645 				g_eLogLevel = SPH_LOG_DEBUG;
17646 			else if ( tStmt.m_sSetValue=="debugv" )
17647 				g_eLogLevel = SPH_LOG_VERBOSE_DEBUG;
17648 			else if ( tStmt.m_sSetValue=="debugvv" )
17649 				g_eLogLevel = SPH_LOG_VERY_VERBOSE_DEBUG;
17650 			else
17651 			{
17652 				tOut.Error ( tStmt.m_sStmt, "Unknown log_level value (must be one of info, debug, debugv, debugvv)" );
17653 				return;
17654 			}
17655 		} else if ( tStmt.m_sSetName=="query_log_min_msec" )
17656 		{
17657 			g_iQueryLogMinMs = tStmt.m_iSetValue;
17658 		} else if ( tStmt.m_sSetName=="log_debug_filter" )
17659 		{
17660 			int iLen = tStmt.m_sSetValue.Length();
17661 			iLen = Min ( iLen, (int)( sizeof(g_sLogFilter)/sizeof(g_sLogFilter[0]) ) );
17662 			memcpy ( g_sLogFilter, tStmt.m_sSetValue.cstr(), iLen );
17663 			g_sLogFilter[iLen] = '\0';
17664 			g_iLogFilterLen = iLen;
17665 		} else
17666 		{
17667 			sError.SetSprintf ( "Unknown system variable '%s'", tStmt.m_sSetName.cstr() );
17668 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17669 			return;
17670 		}
17671 		break;
17672 
17673 	case SET_INDEX_UVAR:
17674 		if ( !SendUserVar ( tStmt.m_sIndex.cstr(), tStmt.m_sSetName.cstr(), tStmt.m_dSetValues, sError ) )
17675 		{
17676 			tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17677 			return;
17678 		}
17679 		break;
17680 
17681 	default:
17682 		sError.SetSprintf ( "INTERNAL ERROR: unhandle SET mode %d", (int)tStmt.m_eSet );
17683 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17684 		return;
17685 	}
17686 
17687 	// it went ok
17688 	tOut.Ok();
17689 }
17690 
17691 
17692 // fwd
17693 void PreCreatePlainIndex ( ServedDesc_t & tServed, const char * sName );
17694 bool PrereadNewIndex ( ServedDesc_t & tIdx, const CSphConfigSection & hIndex, const char * szIndexName );
17695 
17696 
HandleMysqlAttach(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17697 void HandleMysqlAttach ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17698 {
17699 	const CSphString & sFrom = tStmt.m_sIndex;
17700 	const CSphString & sTo = tStmt.m_sStringParam;
17701 	CSphString sError;
17702 
17703 	ServedIndex_t * pFrom = g_pLocalIndexes->GetWlockedEntry ( sFrom );
17704 	const ServedIndex_t * pTo = g_pLocalIndexes->GetWlockedEntry ( sTo );
17705 
17706 	if ( !pFrom || !pFrom->m_bEnabled
17707 		|| !pTo || !pTo->m_bEnabled
17708 		|| pFrom->m_bRT
17709 		|| !pTo->m_bRT )
17710 	{
17711 		if ( !pFrom || !pFrom->m_bEnabled )
17712 			tOut.ErrorEx ( MYSQL_ERR_PARSE_ERROR, "no such index '%s'", sFrom.cstr() );
17713 		else if ( !pTo || !pTo->m_bEnabled )
17714 			tOut.ErrorEx ( MYSQL_ERR_PARSE_ERROR, "no such index '%s'", sTo.cstr() );
17715 		else if ( pFrom->m_bRT )
17716 			tOut.Error ( tStmt.m_sStmt, "1st argument to ATTACH must be a plain index" );
17717 		else if ( pTo->m_bRT )
17718 			tOut.Error ( tStmt.m_sStmt, "2nd argument to ATTACH must be a RT index" );
17719 
17720 		if ( pFrom )
17721 			pFrom->Unlock();
17722 		if ( pTo )
17723 			pTo->Unlock();
17724 		return;
17725 	}
17726 
17727 	ISphRtIndex * pRtTo = (ISphRtIndex*)pTo->m_pIndex;
17728 	bool bOk = pRtTo->AttachDiskIndex ( pFrom->m_pIndex, sError );
17729 	if ( !bOk )
17730 	{
17731 		pFrom->Unlock();
17732 		pTo->Unlock();
17733 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17734 		return;
17735 	}
17736 
17737 	pTo->Unlock();
17738 
17739 	// after a successfull Attach() RT index owns it
17740 	// so we need to create dummy disk index until further notice
17741 	pFrom->m_pIndex = NULL;
17742 	pFrom->m_bEnabled = false;
17743 	PreCreatePlainIndex ( *pFrom, sFrom.cstr() );
17744 	if ( pFrom->m_pIndex )
17745 		pFrom->m_bEnabled = PrereadNewIndex ( *pFrom, g_pCfg.m_tConf["index"][sFrom], sFrom.cstr() );
17746 	pFrom->Unlock();
17747 
17748 	tOut.Ok();
17749 }
17750 
17751 
HandleMysqlFlushRtindex(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17752 void HandleMysqlFlushRtindex ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17753 {
17754 	CSphString sError;
17755 	const ServedIndex_t * pIndex = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
17756 
17757 	if ( !pIndex || !pIndex->m_bEnabled || !pIndex->m_bRT )
17758 	{
17759 		if ( pIndex )
17760 		pIndex->Unlock();
17761 		tOut.Error ( tStmt.m_sStmt, "FLUSH RTINDEX requires an existing RT index" );
17762 		return;
17763 	}
17764 
17765 	ISphRtIndex * pRt = (ISphRtIndex*)pIndex->m_pIndex;
17766 
17767 	pRt->ForceRamFlush();
17768 	pIndex->Unlock();
17769 	tOut.Ok();
17770 }
17771 
17772 
HandleMysqlFlushRamchunk(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17773 void HandleMysqlFlushRamchunk ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17774 {
17775 	CSphString sError;
17776 	const ServedIndex_t * pIndex = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
17777 
17778 	if ( !pIndex || !pIndex->m_bEnabled || !pIndex->m_bRT )
17779 	{
17780 		if ( pIndex )
17781 			pIndex->Unlock();
17782 		tOut.Error ( tStmt.m_sStmt, "FLUSH RAMCHUNK requires an existing RT index" );
17783 		return;
17784 	}
17785 
17786 	ISphRtIndex * pRt = (ISphRtIndex*)pIndex->m_pIndex;
17787 
17788 	pRt->ForceDiskChunk();
17789 	pIndex->Unlock();
17790 	tOut.Ok();
17791 }
17792 
17793 
HandleMysqlFlush(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17794 void HandleMysqlFlush ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17795 {
17796 	CSphString sError;
17797 	int iTag = CommandFlush ( sError );
17798 	if ( !sError.IsEmpty() )
17799 	{
17800 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17801 	} else
17802 	{
17803 		tOut.HeadBegin(1);
17804 		tOut.HeadColumn ( "tag", MYSQL_COL_LONG );
17805 		tOut.HeadEnd();
17806 
17807 		// data packet, var value
17808 		tOut.PutNumeric ( "%d", iTag );
17809 		tOut.Commit();
17810 
17811 		// done
17812 		tOut.Eof();
17813 	}
17814 }
17815 
17816 
HandleMysqlTruncate(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17817 void HandleMysqlTruncate ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17818 {
17819 	// get an exclusive lock
17820 	const ServedIndex_t * pIndex = g_pLocalIndexes->GetWlockedEntry ( tStmt.m_sIndex );
17821 
17822 	if ( !pIndex || !pIndex->m_bEnabled || !pIndex->m_bRT )
17823 	{
17824 		if ( pIndex )
17825 			pIndex->Unlock();
17826 		tOut.Error ( tStmt.m_sStmt, "TRUNCATE RTINDEX requires an existing RT index" );
17827 		return;
17828 	}
17829 
17830 	ISphRtIndex * pRt = (ISphRtIndex*)pIndex->m_pIndex;
17831 
17832 	CSphString sError;
17833 	bool bRes = pRt->Truncate ( sError );
17834 	pIndex->Unlock();
17835 
17836 	if ( !bRes )
17837 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17838 	else
17839 		tOut.Ok();
17840 }
17841 
17842 
HandleMysqlOptimize(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17843 void HandleMysqlOptimize ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17844 {
17845 	// get an exclusive lock
17846 	const ServedIndex_t * pIndex = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
17847 	bool bValid = pIndex && pIndex->m_bEnabled && pIndex->m_bRT;
17848 	if ( pIndex )
17849 		pIndex->Unlock();
17850 
17851 	if ( !bValid )
17852 		tOut.Error ( tStmt.m_sStmt, "OPTIMIZE INDEX requires an existing RT index" );
17853 	else
17854 		tOut.Ok();
17855 
17856 	if ( bValid )
17857 	{
17858 		g_tOptimizeQueueMutex.Lock();
17859 		g_dOptimizeQueue.Add ( tStmt.m_sIndex );
17860 		g_tOptimizeQueueMutex.Unlock();
17861 	}
17862 }
17863 
HandleMysqlSelectSysvar(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17864 void HandleMysqlSelectSysvar ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17865 {
17866 	tOut.HeadBegin ( tStmt.m_tQuery.m_dItems.GetLength() );
17867 	ARRAY_FOREACH ( i, tStmt.m_tQuery.m_dItems )
17868 		tOut.HeadColumn ( tStmt.m_tQuery.m_dItems[i].m_sAlias.cstr(), MYSQL_COL_LONG );
17869 	tOut.HeadEnd();
17870 
17871 	ARRAY_FOREACH ( i, tStmt.m_tQuery.m_dItems )
17872 	{
17873 		const CSphString & sVar = tStmt.m_tQuery.m_dItems[i].m_sExpr;
17874 
17875 		// MySQL Connector/J, really expects an answer here
17876 		if ( sVar=="@@session.auto_increment_increment" )
17877 			tOut.PutString("1");
17878 		else if ( sVar=="@@character_set_client" || sVar=="@@character_set_connection" )
17879 			tOut.PutString("utf8");
17880 
17881 		// MySQL Go connector, really expects an answer here
17882 		else if ( sVar=="@@max_allowed_packet" )
17883 			tOut.PutNumeric ( "%d", g_iMaxPacketSize );
17884 		else
17885 			tOut.PutString("");
17886 	}
17887 
17888 	tOut.Commit();
17889 	tOut.Eof();
17890 }
17891 
17892 
HandleMysqlSelectDual(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)17893 void HandleMysqlSelectDual ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
17894 {
17895 	CSphString sVar = tStmt.m_tQuery.m_sQuery;
17896 	CSphSchema	tSchema;
17897 	ESphAttr eAttrType;
17898 	CSphString sError;
17899 
17900 	ISphExpr * pExpr = sphExprParse ( sVar.cstr(), tSchema, &eAttrType, NULL, sError, NULL );
17901 
17902 	if ( !pExpr )
17903 	{
17904 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
17905 		return;
17906 	}
17907 
17908 	tOut.HeadBegin(1);
17909 	tOut.HeadColumn ( sVar.cstr(), MYSQL_COL_STRING );
17910 	tOut.HeadEnd();
17911 
17912 	CSphMatch tMatch;
17913 	const BYTE * pStr = NULL;
17914 
17915 	switch ( eAttrType )
17916 	{
17917 		case SPH_ATTR_STRINGPTR:
17918 			pExpr->StringEval ( tMatch, &pStr );
17919 			tOut.PutString ( (const char*)pStr );
17920 			SafeDelete ( pStr );
17921 			break;
17922 		case SPH_ATTR_INTEGER:	tOut.PutNumeric<int> ( "%d", pExpr->IntEval ( tMatch ) ); break;
17923 		case SPH_ATTR_BIGINT:	tOut.PutNumeric<SphAttr_t> ( INT64_FMT, pExpr->Int64Eval ( tMatch ) ); break;
17924 		case SPH_ATTR_FLOAT:	tOut.PutNumeric<float> ( "%f", pExpr->Eval ( tMatch ) ); break;
17925 		default:
17926 			tOut.PutNULL();
17927 			break;
17928 	}
17929 
17930 	SafeDelete ( pExpr );
17931 
17932 	// done
17933 	tOut.Commit();
17934 	tOut.Eof();
17935 }
17936 
17937 
HandleMysqlShowCollations(SqlRowBuffer_c & tOut)17938 void HandleMysqlShowCollations ( SqlRowBuffer_c & tOut )
17939 {
17940 	// MySQL Connector/J really expects an answer here
17941 	// field packets
17942 	tOut.HeadBegin(6);
17943 	tOut.HeadColumn ( "Collation" );
17944 	tOut.HeadColumn ( "Charset" );
17945 	tOut.HeadColumn ( "Id", MYSQL_COL_LONGLONG );
17946 	tOut.HeadColumn ( "Default" );
17947 	tOut.HeadColumn ( "Compiled" );
17948 	tOut.HeadColumn ( "Sortlen" );
17949 	tOut.HeadEnd();
17950 
17951 	// data packets
17952 	tOut.PutString ( "utf8_general_ci" );
17953 	tOut.PutString ( "utf8" );
17954 	tOut.PutString ( "33" );
17955 	tOut.PutString ( "Yes" );
17956 	tOut.PutString ( "Yes" );
17957 	tOut.PutString ( "1" );
17958 	tOut.Commit();
17959 
17960 	// done
17961 	tOut.Eof();
17962 	return;
17963 }
17964 
HandleMysqlShowCharacterSet(SqlRowBuffer_c & tOut)17965 void HandleMysqlShowCharacterSet ( SqlRowBuffer_c & tOut )
17966 {
17967 	// MySQL Connector/J really expects an answer here
17968 	// field packets
17969 	tOut.HeadBegin(4);
17970 	tOut.HeadColumn ( "Charset" );
17971 	tOut.HeadColumn ( "Description" );
17972 	tOut.HeadColumn ( "Default collation" );
17973 	tOut.HeadColumn ( "Maxlen" );
17974 	tOut.HeadEnd();
17975 
17976 	// data packets
17977 	tOut.PutString ( "utf8" );
17978 	tOut.PutString ( "UTF-8 Unicode" );
17979 	tOut.PutString ( "utf8_general_ci" );
17980 	tOut.PutString ( "3" );
17981 	tOut.Commit();
17982 
17983 	// done
17984 	tOut.Eof();
17985 }
17986 
sphCollationToName(ESphCollation eColl)17987 const char * sphCollationToName ( ESphCollation eColl )
17988 {
17989 	switch ( eColl )
17990 	{
17991 		case SPH_COLLATION_LIBC_CI:				return "libc_ci";
17992 		case SPH_COLLATION_LIBC_CS:				return "libc_cs";
17993 		case SPH_COLLATION_UTF8_GENERAL_CI:		return "utf8_general_ci";
17994 		case SPH_COLLATION_BINARY:				return "binary";
17995 		default:								return "unknown";
17996 	}
17997 }
17998 
17999 
LogLevelName(ESphLogLevel eLevel)18000 static const char * LogLevelName ( ESphLogLevel eLevel )
18001 {
18002 	switch ( eLevel )
18003 	{
18004 		case SPH_LOG_FATAL:					return "fatal";
18005 		case SPH_LOG_WARNING:				return "warning";
18006 		case SPH_LOG_INFO:					return "info";
18007 		case SPH_LOG_DEBUG:					return "debug";
18008 		case SPH_LOG_VERBOSE_DEBUG:			return "debugv";
18009 		case SPH_LOG_VERY_VERBOSE_DEBUG:	return "debugvv";
18010 		default:							return "unknown";
18011 	}
18012 }
18013 
18014 
HandleMysqlShowVariables(SqlRowBuffer_c & dRows,const SqlStmt_t & tStmt,SessionVars_t & tVars)18015 void HandleMysqlShowVariables ( SqlRowBuffer_c & dRows, const SqlStmt_t & tStmt, SessionVars_t & tVars )
18016 {
18017 	VectorLike dStatus ( tStmt.m_sStringParam );
18018 
18019 	if ( dStatus.MatchAdd ( "autocommit" ) )
18020 		dStatus.Add ( tVars.m_bAutoCommit ? "1" : "0" );
18021 
18022 	if ( dStatus.MatchAdd ( "collation_connection" ) )
18023 		dStatus.Add ( sphCollationToName ( tVars.m_eCollation ) );
18024 
18025 	if ( dStatus.MatchAdd ( "query_log_format" ) )
18026 		dStatus.Add ( g_eLogFormat==LOG_FORMAT_PLAIN ? "plain" : "sphinxql" );
18027 
18028 	if ( dStatus.MatchAdd ( "log_level" ) )
18029 		dStatus.Add ( LogLevelName ( g_eLogLevel ) );
18030 
18031 	if ( dStatus.MatchAdd ( "max_allowed_packet" ) )
18032 		dStatus.Add().SetSprintf ( "%d", g_iMaxPacketSize );
18033 
18034 	// .NET connector requires character_set_* vars
18035 	if ( dStatus.MatchAdd ( "character_set_client" ) )
18036 		dStatus.Add ( "utf8" );
18037 
18038 	if ( dStatus.MatchAdd ( "character_set_connection" ) )
18039 		dStatus.Add ( "utf8" );
18040 
18041 	// result set header packet
18042 	dRows.HeadTuplet ( "Variable_name", "Value" );
18043 
18044 	// send rows
18045 	for ( int iRow=0; iRow<dStatus.GetLength(); iRow+=2 )
18046 		dRows.DataTuplet ( dStatus[iRow+0].cstr(), dStatus[iRow+1].cstr() );
18047 
18048 	// cleanup
18049 	dRows.Eof();
18050 }
18051 
18052 
HandleMysqlShowIndexStatus(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)18053 void HandleMysqlShowIndexStatus ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
18054 {
18055 	CSphString sError;
18056 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
18057 
18058 	if ( !pServed || !pServed->m_bEnabled )
18059 	{
18060 		if ( pServed )
18061 			pServed->Unlock();
18062 		tOut.Error ( tStmt.m_sStmt, "SHOW INDEX STATUS requires an existing index" );
18063 		return;
18064 	}
18065 
18066 	CSphIndex * pIndex = pServed->m_pIndex;
18067 
18068 	tOut.HeadTuplet ( "Variable_name", "Value" );
18069 
18070 	tOut.DataTuplet ( "index_type", pServed->m_bRT ? "rt" : "disk" );
18071 	tOut.DataTuplet ( "indexed_documents", pIndex->GetStats().m_iTotalDocuments );
18072 	tOut.DataTuplet ( "indexed_bytes", pIndex->GetStats().m_iTotalBytes );
18073 
18074 	const int64_t * pFieldLens = pIndex->GetFieldLens();
18075 	if ( pFieldLens )
18076 	{
18077 		int64_t iTotalTokens = 0;
18078 		const CSphVector<CSphColumnInfo> & dFields = pIndex->GetMatchSchema().m_dFields;
18079 		ARRAY_FOREACH ( i, dFields )
18080 		{
18081 			CSphString sKey;
18082 			sKey.SetSprintf ( "field_tokens_%s", dFields[i].m_sName.cstr() );
18083 			tOut.DataTuplet ( sKey.cstr(), pFieldLens[i] );
18084 			iTotalTokens += pFieldLens[i];
18085 		}
18086 		tOut.DataTuplet ( "total_tokens", iTotalTokens );
18087 	}
18088 
18089 	CSphIndexStatus tStatus;
18090 	pIndex->GetStatus ( &tStatus );
18091 	tOut.DataTuplet ( "ram_bytes", tStatus.m_iRamUse );
18092 	tOut.DataTuplet ( "disk_bytes", tStatus.m_iDiskUse );
18093 	if ( pIndex->IsRT() )
18094 	{
18095 		tOut.DataTuplet ( "ram_chunk", tStatus.m_iRamChunkSize );
18096 		tOut.DataTuplet ( "disk_chunks", tStatus.m_iNumChunks );
18097 		tOut.DataTuplet ( "mem_limit", tStatus.m_iMemLimit );
18098 	}
18099 
18100 	pServed->Unlock();
18101 	tOut.Eof();
18102 }
18103 
18104 
DumpKey(CSphStringBuilder & tBuf,const char * sKey,const char * sVal,bool bCond)18105 void DumpKey ( CSphStringBuilder & tBuf, const char * sKey, const char * sVal, bool bCond )
18106 {
18107 	if ( bCond )
18108 		tBuf.Appendf ( "%s = %s\n", sKey, sVal );
18109 }
18110 
18111 
DumpKey(CSphStringBuilder & tBuf,const char * sKey,int iVal,bool bCond)18112 void DumpKey ( CSphStringBuilder & tBuf, const char * sKey, int iVal, bool bCond )
18113 {
18114 	if ( bCond )
18115 		tBuf.Appendf ( "%s = %d\n", sKey, iVal );
18116 }
18117 
18118 
DumpIndexSettings(CSphStringBuilder & tBuf,CSphIndex * pIndex)18119 void DumpIndexSettings ( CSphStringBuilder & tBuf, CSphIndex * pIndex )
18120 {
18121 	const CSphIndexSettings & tSettings = pIndex->GetSettings();
18122 	DumpKey ( tBuf, "docinfo",				"inline",								tSettings.m_eDocinfo==SPH_DOCINFO_INLINE );
18123 	DumpKey ( tBuf, "min_prefix_len",		tSettings.m_iMinPrefixLen,				tSettings.m_iMinPrefixLen!=0 );
18124 	DumpKey ( tBuf, "max_substring_len",	tSettings.m_iMaxSubstringLen,			tSettings.m_iMaxSubstringLen!=0 );
18125 	DumpKey ( tBuf, "index_exact_words",	1,										tSettings.m_bIndexExactWords );
18126 	DumpKey ( tBuf, "html_strip",			1,										tSettings.m_bHtmlStrip );
18127 	DumpKey ( tBuf, "html_index_attrs",		tSettings.m_sHtmlIndexAttrs.cstr(),		!tSettings.m_sHtmlIndexAttrs.IsEmpty() );
18128 	DumpKey ( tBuf, "html_remove_elements", tSettings.m_sHtmlRemoveElements.cstr(), !tSettings.m_sHtmlRemoveElements.IsEmpty() );
18129 	DumpKey ( tBuf, "index_zones",			tSettings.m_sZones.cstr(),				!tSettings.m_sZones.IsEmpty() );
18130 	DumpKey ( tBuf, "index_field_lengths",	1,										tSettings.m_bIndexFieldLens );
18131 
18132 	if ( pIndex->GetTokenizer() )
18133 	{
18134 		const CSphTokenizerSettings & tTokSettings = pIndex->GetTokenizer()->GetSettings();
18135 		DumpKey ( tBuf, "charset_type",		( tTokSettings.m_iType==TOKENIZER_UTF8 || tTokSettings.m_iType==TOKENIZER_NGRAM )
18136 											? "utf-8"
18137 											: "unknown tokenizer (deprecated sbcs?)", true );
18138 		DumpKey ( tBuf, "charset_table",	tTokSettings.m_sCaseFolding.cstr(),		!tTokSettings.m_sCaseFolding.IsEmpty() );
18139 		DumpKey ( tBuf, "min_word_len",		tTokSettings.m_iMinWordLen,				tTokSettings.m_iMinWordLen>1 );
18140 		DumpKey ( tBuf, "ngram_len",		tTokSettings.m_iNgramLen,				tTokSettings.m_iNgramLen && !tTokSettings.m_sNgramChars.IsEmpty() );
18141 		DumpKey ( tBuf, "ngram_chars",		tTokSettings.m_sNgramChars.cstr(),		tTokSettings.m_iNgramLen && !tTokSettings.m_sNgramChars.IsEmpty() );
18142 		DumpKey ( tBuf, "exceptions",		tTokSettings.m_sSynonymsFile.cstr(),	!tTokSettings.m_sSynonymsFile.IsEmpty() );
18143 		DumpKey ( tBuf, "phrase_boundary",	tTokSettings.m_sBoundary.cstr(),		!tTokSettings.m_sBoundary.IsEmpty() );
18144 		DumpKey ( tBuf, "ignore_chars",		tTokSettings.m_sIgnoreChars.cstr(),		!tTokSettings.m_sIgnoreChars.IsEmpty() );
18145 		DumpKey ( tBuf, "blend_chars",		tTokSettings.m_sBlendChars.cstr(),		!tTokSettings.m_sBlendChars.IsEmpty() );
18146 		DumpKey ( tBuf, "blend_mode",		tTokSettings.m_sBlendMode.cstr(),		!tTokSettings.m_sBlendMode.IsEmpty() );
18147 	}
18148 
18149 	if ( pIndex->GetDictionary() )
18150 	{
18151 		const CSphDictSettings & tDictSettings = pIndex->GetDictionary()->GetSettings();
18152 		CSphStringBuilder tWordforms;
18153 		ARRAY_FOREACH ( i, tDictSettings.m_dWordforms )
18154 			tWordforms.Appendf ( " %s", tDictSettings.m_dWordforms[i].cstr () );
18155 		DumpKey ( tBuf, "dict",				"keywords",								tDictSettings.m_bWordDict );
18156 		DumpKey ( tBuf, "morphology",		tDictSettings.m_sMorphology.cstr(),		!tDictSettings.m_sMorphology.IsEmpty() );
18157 		DumpKey ( tBuf, "stopwords",		tDictSettings.m_sStopwords.cstr (),		!tDictSettings.m_sStopwords.IsEmpty() );
18158 		DumpKey ( tBuf, "wordforms",		tWordforms.cstr()+1,					tDictSettings.m_dWordforms.GetLength()>0 );
18159 		DumpKey ( tBuf, "min_stemming_len",	tDictSettings.m_iMinStemmingLen,		tDictSettings.m_iMinStemmingLen>1 );
18160 	}
18161 }
18162 
18163 
HandleMysqlShowIndexSettings(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)18164 void HandleMysqlShowIndexSettings ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
18165 {
18166 	CSphString sError;
18167 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex );
18168 
18169 	int iChunk = tStmt.m_iIntParam;
18170 	CSphIndex * pIndex = pServed ? pServed->m_pIndex : NULL;
18171 
18172 	if ( iChunk>=0 && pServed && pIndex && pIndex->IsRT() )
18173 		pIndex = static_cast<ISphRtIndex *>( pIndex )->GetDiskChunk ( iChunk );
18174 
18175 	if ( !pServed || !pServed->m_bEnabled || !pIndex )
18176 	{
18177 		if ( pServed )
18178 			pServed->Unlock();
18179 		tOut.Error ( tStmt.m_sStmt, "SHOW INDEX SETTINGS requires an existing index" );
18180 		return;
18181 	}
18182 
18183 	tOut.HeadTuplet ( "Variable_name", "Value" );
18184 
18185 	CSphStringBuilder sBuf;
18186 	DumpIndexSettings ( sBuf, pIndex );
18187 	tOut.DataTuplet ( "settings", sBuf.cstr() );
18188 
18189 	pServed->Unlock();
18190 	tOut.Eof();
18191 }
18192 
18193 
HandleMysqlShowProfile(SqlRowBuffer_c & tOut,const CSphQueryProfile & p,bool bMoreResultsFollow)18194 void HandleMysqlShowProfile ( SqlRowBuffer_c & tOut, const CSphQueryProfile & p, bool bMoreResultsFollow )
18195 {
18196 	#define SPH_QUERY_STATE(_name,_desc) _desc,
18197 	static const char * dStates [ SPH_QSTATE_TOTAL ] = { SPH_QUERY_STATES };
18198 	#undef SPH_QUERY_STATES
18199 
18200 	tOut.HeadBegin ( 4 );
18201 	tOut.HeadColumn ( "Status" );
18202 	tOut.HeadColumn ( "Duration" );
18203 	tOut.HeadColumn ( "Switches" );
18204 	tOut.HeadColumn ( "Percent" );
18205 	tOut.HeadEnd ( bMoreResultsFollow );
18206 
18207 	int64_t tmTotal = 0;
18208 	int iCount = 0;
18209 	for ( int i=0; i<SPH_QSTATE_TOTAL; i++ )
18210 	{
18211 		if ( p.m_dSwitches[i]<=0 )
18212 			continue;
18213 		tmTotal += p.m_tmTotal[i];
18214 		iCount += p.m_dSwitches[i];
18215 	}
18216 
18217 	char sTime[32];
18218 	for ( int i=0; i<SPH_QSTATE_TOTAL; i++ )
18219 	{
18220 		if ( p.m_dSwitches[i]<=0 )
18221 			continue;
18222 		snprintf ( sTime, sizeof(sTime), "%d.%06d", int(p.m_tmTotal[i]/1000000), int(p.m_tmTotal[i]%1000000) );
18223 		tOut.PutString ( dStates[i] );
18224 		tOut.PutString ( sTime );
18225 		tOut.PutNumeric ( "%d", p.m_dSwitches[i] );
18226 		tOut.PutNumeric ( "%.2f", 100.0f * p.m_tmTotal[i]/tmTotal );
18227 		tOut.Commit();
18228 	}
18229 	snprintf ( sTime, sizeof(sTime), "%d.%06d", int(tmTotal/1000000), int(tmTotal%1000000) );
18230 	tOut.PutString ( "total" );
18231 	tOut.PutString ( sTime );
18232 	tOut.PutNumeric ( "%d", iCount );
18233 	tOut.PutString ( "0" );
18234 	tOut.Commit();
18235 	tOut.Eof ( bMoreResultsFollow );
18236 }
18237 
18238 
18239 bool TryRename ( const char * sIndex, const char * sPrefix, const char * sFromPostfix, const char * sToPostfix, const char * sAction, bool bFatal, bool bCheckExist=true );
18240 
RenameWithRollback(const ServedIndex_t * pLocal)18241 static bool RenameWithRollback ( const ServedIndex_t * pLocal )
18242 {
18243 	const char * szIndexName = pLocal->m_pIndex->GetName();
18244 	const char * szIndexPath = pLocal->m_sIndexPath.cstr();
18245 
18246 	// current to old
18247 	const int NUM_EXTS_USED = 2;
18248 	const ESphExt dExtsUsed[NUM_EXTS_USED] = { SPH_EXT_SPH, SPH_EXT_SPA };
18249 	const char * szAction = "removing attribute from";
18250 	int iFailed = -1;
18251 	for ( int iExt = 0; iExt < NUM_EXTS_USED && iFailed==-1; iExt++ )
18252 		if ( !TryRename ( szIndexName, szIndexPath, sphGetExt ( SPH_EXT_TYPE_CUR, dExtsUsed[iExt] ), sphGetExt ( SPH_EXT_TYPE_OLD, dExtsUsed[iExt] ), szAction, false ) )
18253 			iFailed = iExt;
18254 
18255 	// failed? rollback
18256 	if ( iFailed!=-1 )
18257 	{
18258 		for ( int iExt = iFailed; iExt>=0; iExt-- )
18259 			TryRename ( szIndexName, szIndexPath, sphGetExt ( SPH_EXT_TYPE_OLD, dExtsUsed[iExt] ), sphGetExt ( SPH_EXT_TYPE_CUR, dExtsUsed[iExt] ), szAction, true );
18260 
18261 		sphWarning ( "adding attribute to index '%s': rename to .old failed; using old index", szIndexName );
18262 		return false;
18263 	}
18264 
18265 	// new to cur
18266 	for ( int iExt = 0; iExt < NUM_EXTS_USED && iFailed==-1; iExt++ )
18267 		if ( !TryRename ( szIndexName, szIndexPath, sphGetExt ( SPH_EXT_TYPE_NEW, dExtsUsed[iExt] ), sphGetExt ( SPH_EXT_TYPE_CUR, dExtsUsed[iExt] ), szAction, false ) )
18268 			iFailed = iExt;
18269 
18270 	// rollback
18271 	if ( iFailed!=-1 )
18272 	{
18273 		for ( int iExt = iFailed; iExt>=0; iExt-- )
18274 			TryRename ( szIndexName, szIndexPath, sphGetExt ( SPH_EXT_TYPE_CUR, dExtsUsed[iExt] ), sphGetExt ( SPH_EXT_TYPE_NEW, dExtsUsed[iExt] ), szAction, true );
18275 
18276 		sphWarning ( "adding attribute to index '%s': .new rename failed; using old index", szIndexName );
18277 
18278 		// rollback again
18279 		for ( int iExt = 0; iExt < NUM_EXTS_USED; iExt++ )
18280 			TryRename ( szIndexName, szIndexPath, sphGetExt ( SPH_EXT_TYPE_OLD, dExtsUsed[iExt] ), sphGetExt ( SPH_EXT_TYPE_CUR, dExtsUsed[iExt] ), szAction, true );
18281 
18282 		return false;
18283 	}
18284 
18285 	// unlink old
18286 	char sFileName[SPH_MAX_FILENAME_LEN];
18287 	for ( int iExt = 0; iExt < NUM_EXTS_USED; iExt++ )
18288 	{
18289 		snprintf ( sFileName, sizeof(sFileName), "%s%s", szIndexPath, sphGetExt ( SPH_EXT_TYPE_OLD, dExtsUsed[iExt] ) );
18290 		if ( ::unlink ( sFileName ) && errno!=ENOENT )
18291 			sphWarning ( "unlink failed (file '%s', error '%s'", sFileName, strerror(errno) );
18292 	}
18293 
18294 	return true;
18295 }
18296 
18297 
ModifyIndexAttrs(const ServedIndex_t * pLocal,bool bAdd,CSphString & sAttrName,ESphAttr eAttrType,int iPos,CSphString & sError)18298 static void ModifyIndexAttrs ( const ServedIndex_t * pLocal, bool bAdd, CSphString & sAttrName, ESphAttr eAttrType, int iPos, CSphString & sError )
18299 {
18300 	if ( !pLocal->m_pIndex->CreateModifiedFiles ( bAdd, sAttrName, eAttrType, iPos, sError ) )
18301 		return;
18302 
18303 	if ( pLocal->m_pIndex->IsRT() )
18304 	{
18305 		pLocal->m_pIndex->AddRemoveAttribute ( bAdd, sAttrName, eAttrType, iPos, sError );
18306 	} else
18307 	{
18308 		if ( RenameWithRollback ( pLocal ) )
18309 			pLocal->m_pIndex->AddRemoveAttribute ( bAdd, sAttrName, eAttrType, iPos, sError );
18310 	}
18311 }
18312 
18313 
AddAttrToIndex(const SqlStmt_t & tStmt,const ServedIndex_t * pLocal,CSphString & sError)18314 static void AddAttrToIndex ( const SqlStmt_t & tStmt, const ServedIndex_t * pLocal, CSphString & sError )
18315 {
18316 	CSphString sAttrToAdd = tStmt.m_sAlterAttr;
18317 	sAttrToAdd.ToLower();
18318 
18319 	if ( pLocal->m_pIndex->GetMatchSchema().GetAttr ( sAttrToAdd.cstr() ) )
18320 	{
18321 		sError.SetSprintf ( "'%s' attribute already in schema", sAttrToAdd.cstr() );
18322 		return;
18323 	}
18324 
18325 	int iPos = pLocal->m_pIndex->GetMatchSchema().GetAttrsCount();
18326 	if ( pLocal->m_pIndex->GetSettings().m_bIndexFieldLens )
18327 		iPos -= pLocal->m_pIndex->GetMatchSchema().m_dFields.GetLength();
18328 
18329 	ModifyIndexAttrs ( pLocal, true, sAttrToAdd, tStmt.m_eAlterColType, iPos, sError );
18330 }
18331 
18332 
RemoveAttrFromIndex(const SqlStmt_t & tStmt,const ServedIndex_t * pLocal,CSphString & sError)18333 static void RemoveAttrFromIndex ( const SqlStmt_t & tStmt, const ServedIndex_t * pLocal, CSphString & sError )
18334 {
18335 	CSphString sAttrToRemove = tStmt.m_sAlterAttr;
18336 	sAttrToRemove.ToLower();
18337 
18338 	if ( !pLocal->m_pIndex->GetMatchSchema().GetAttr ( sAttrToRemove.cstr() ) )
18339 	{
18340 		sError.SetSprintf ( "attribute '%s' does not exist", sAttrToRemove.cstr() );
18341 		return;
18342 	}
18343 
18344 	if ( pLocal->m_pIndex->GetMatchSchema().GetAttrsCount()==1 )
18345 	{
18346 		sError.SetSprintf ( "unable to remove last attribute '%s'", sAttrToRemove.cstr() );
18347 		return;
18348 	}
18349 
18350 	ModifyIndexAttrs ( pLocal, false, sAttrToRemove, SPH_ATTR_NONE, -1, sError );
18351 }
18352 
18353 
HandleMysqlAlter(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt,bool bAdd)18354 static void HandleMysqlAlter ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt, bool bAdd )
18355 {
18356 	MEMORY ( MEM_SQL_ALTER );
18357 
18358 	SearchFailuresLog_c dErrors;
18359 	CSphString sError;
18360 
18361 	if ( bAdd && tStmt.m_eAlterColType==SPH_ATTR_NONE )
18362 	{
18363 		sError.SetSprintf ( "unsupported attribute type '%d'", tStmt.m_eAlterColType );
18364 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18365 		return;
18366 	}
18367 
18368 	CSphVector<CSphString> dNames;
18369 	ParseIndexList ( tStmt.m_sIndex, dNames );
18370 	if ( !dNames.GetLength() )
18371 	{
18372 		sError.SetSprintf ( "no such index '%s'", tStmt.m_sIndex.cstr() );
18373 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18374 		return;
18375 	}
18376 
18377 	bool bHaveDist = false;
18378 	ARRAY_FOREACH_COND ( i, dNames, !bHaveDist )
18379 		if ( !g_pLocalIndexes->Exists ( dNames[i] ) )
18380 		{
18381 			g_tDistLock.Lock();
18382 			bHaveDist = !!g_hDistIndexes ( dNames[i] );
18383 			g_tDistLock.Unlock();
18384 		}
18385 
18386 	if ( bHaveDist )
18387 	{
18388 		sError.SetSprintf ( "ALTER is only supported on local (not distributed) indexes" );
18389 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18390 		return;
18391 	}
18392 
18393 	ARRAY_FOREACH ( iIdx, dNames )
18394 	{
18395 		const char * sName = dNames[iIdx].cstr();
18396 		const ServedIndex_t * pLocal = g_pLocalIndexes->GetWlockedEntry ( sName );
18397 		if ( pLocal )
18398 		{
18399 			if ( !pLocal->m_bEnabled )
18400 			{
18401 				dErrors.Submit ( sName, "does not support ALTER (enabled=false)" );
18402 				pLocal->Unlock();
18403 				continue;
18404 			}
18405 
18406 			if ( !pLocal->m_bAlterEnabled )
18407 			{
18408 				dErrors.Submit ( sName, "ALTER disabled for this index" );
18409 				pLocal->Unlock();
18410 				continue;
18411 			}
18412 
18413 			if ( pLocal->m_pIndex->m_bId32to64 )
18414 			{
18415 				dErrors.Submit ( sName, "index is 32bit; executable is 64bit: ALTER disabled" );
18416 				pLocal->Unlock();
18417 				continue;
18418 			}
18419 
18420 			if ( pLocal->m_pIndex->GetSettings().m_eDocinfo==SPH_DOCINFO_INLINE )
18421 			{
18422 				dErrors.Submit ( sName, "docinfo is inline: ALTER disabled" );
18423 				pLocal->Unlock();
18424 				continue;
18425 			}
18426 
18427 			CSphString sAddError;
18428 
18429 			if ( bAdd )
18430 				AddAttrToIndex ( tStmt, pLocal, sAddError );
18431 			else
18432 				RemoveAttrFromIndex ( tStmt, pLocal, sAddError );
18433 
18434 			if ( sAddError.Length() )
18435 				dErrors.Submit ( sName, sAddError.cstr() );
18436 
18437 			pLocal->Unlock();
18438 		} else
18439 		{
18440 			dErrors.Submit ( sName, "unknown local index in ALTER request" );
18441 		}
18442 	}
18443 
18444 	if ( !dErrors.IsEmpty() )
18445 	{
18446 		CSphStringBuilder sReport;
18447 		dErrors.BuildReport ( sReport );
18448 		tOut.Error ( tStmt.m_sStmt, sReport.cstr() );
18449 		return;
18450 	} else
18451 		tOut.Ok();
18452 }
18453 
18454 
HandleMysqlReconfigure(SqlRowBuffer_c & tOut,const SqlStmt_t & tStmt)18455 static void HandleMysqlReconfigure ( SqlRowBuffer_c & tOut, const SqlStmt_t & tStmt )
18456 {
18457 	MEMORY ( MEM_SQL_ALTER );
18458 
18459 	const char * sName = tStmt.m_sIndex.cstr();
18460 	CSphString sError;
18461 	CSphReconfigureSettings tSettings;
18462 	CSphReconfigureSetup tSetup;
18463 
18464 	CSphConfigParser tCfg;
18465 	if ( !tCfg.ReParse ( g_sConfigFile.cstr () ) )
18466 	{
18467 		sError.SetSprintf ( "failed to parse config file '%s'; using previous settings", g_sConfigFile.cstr () );
18468 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18469 		return;
18470 	}
18471 
18472 	if ( !tCfg.m_tConf.Exists ( "index" ) )
18473 	{
18474 		sError.SetSprintf ( "failed to find any index at config file '%s'; using previous settings", g_sConfigFile.cstr () );
18475 		tOut.Error ( tStmt.m_sStmt, sError.cstr () );
18476 		return;
18477 	}
18478 
18479 	const CSphConfig & hConf = tCfg.m_tConf;
18480 	if ( !hConf["index"].Exists ( sName ) )
18481 	{
18482 		sError.SetSprintf ( "failed to find index '%s' at config file '%s'; using previous settings", sName, g_sConfigFile.cstr () );
18483 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18484 		return;
18485 	}
18486 
18487 	const CSphConfigSection & hIndex = hConf["index"][sName];
18488 	sphConfTokenizer ( hIndex, tSettings.m_tTokenizer );
18489 
18490 	sphConfDictionary ( hIndex, tSettings.m_tDict );
18491 
18492 	if ( !sphConfIndex ( hIndex, tSettings.m_tIndex, sError ) )
18493 	{
18494 		sError.SetSprintf ( "'%s' failed to parse index settings, error '%s'", sName, sError.cstr() );
18495 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18496 		return;
18497 	}
18498 
18499 	const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( tStmt.m_sIndex.cstr() );
18500 	if ( !pServed || !pServed->m_bEnabled || !pServed->m_pIndex->IsRT() )
18501 	{
18502 		if ( pServed )
18503 			sError.SetSprintf ( "'%s' does not support ALTER (enabled=%d, RT=%d)", sName, pServed->m_bEnabled, pServed->m_pIndex->IsRT() );
18504 		else
18505 			sError.SetSprintf ( "ALTER is only supported on local (not distributed) indexes" );
18506 
18507 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18508 
18509 		if ( pServed )
18510 			pServed->Unlock();
18511 
18512 		return;
18513 	}
18514 
18515 	bool bSame = ( (const ISphRtIndex *)pServed->m_pIndex )->IsSameSettings ( tSettings, tSetup, sError );
18516 
18517 	pServed->Unlock();
18518 
18519 	// replace going with exclusive lock
18520 	if ( !bSame && sError.IsEmpty() )
18521 	{
18522 		const ServedIndex_t * pServed = g_pLocalIndexes->GetWlockedEntry ( tStmt.m_sIndex.cstr() );
18523 		( (ISphRtIndex *)pServed->m_pIndex )->Reconfigure ( tSetup );
18524 		pServed->Unlock();
18525 	}
18526 
18527 	if ( sError.IsEmpty() )
18528 		tOut.Ok();
18529 	else
18530 		tOut.Error ( tStmt.m_sStmt, sError.cstr() );
18531 }
18532 
18533 
HandleMysqlShowPlan(SqlRowBuffer_c & tOut,const CSphQueryProfile & p,bool bMoreResultsFollow)18534 static void HandleMysqlShowPlan ( SqlRowBuffer_c & tOut, const CSphQueryProfile & p, bool bMoreResultsFollow )
18535 {
18536 	tOut.HeadBegin ( 2 );
18537 	tOut.HeadColumn ( "Variable" );
18538 	tOut.HeadColumn ( "Value" );
18539 	tOut.HeadEnd ( bMoreResultsFollow );
18540 
18541 	tOut.PutString ( "transformed_tree" );
18542 	tOut.PutString ( p.m_sTransformedTree.cstr() );
18543 	tOut.Commit();
18544 
18545 	tOut.Eof ( bMoreResultsFollow );
18546 }
18547 
18548 //////////////////////////////////////////////////////////////////////////
18549 
18550 class CSphinxqlSession : public ISphNoncopyable
18551 {
18552 	CSphString &		m_sError;
18553 
18554 public:
18555 	CSphQueryResultMeta m_tLastMeta;
18556 	SessionVars_t		m_tVars;
18557 
18558 	CSphQueryProfile	m_tProfile;
18559 	CSphQueryProfile	m_tLastProfile;
18560 
18561 public:
CSphinxqlSession(CSphString & sError)18562 	explicit CSphinxqlSession ( CSphString & sError )
18563 		: m_sError ( sError )
18564 	{
18565 	}
18566 
18567 public:
18568 	// just execute one sphinxql statement
18569 	//
18570 	// IMPORTANT! this does NOT start or stop profiling, as there a few external
18571 	// things (client net reads and writes) that we want to profile, too
18572 	//
18573 	// returns true if the current profile should be kept (default)
18574 	// returns false if profile should be discarded (eg. SHOW PROFILE case)
Execute(const CSphString & sQuery,NetOutputBuffer_c & tOutput,BYTE & uPacketID,ThdDesc_t * pThd=NULL)18575 	bool Execute ( const CSphString & sQuery, NetOutputBuffer_c & tOutput, BYTE & uPacketID, ThdDesc_t * pThd=NULL )
18576 	{
18577 		// set on query guard
18578 		CrashQuery_t tCrashQuery;
18579 		tCrashQuery.m_pQuery = (const BYTE *)sQuery.cstr();
18580 		tCrashQuery.m_iSize = sQuery.Length();
18581 		tCrashQuery.m_bMySQL = true;
18582 		SphCrashLogger_c::SetLastQuery ( tCrashQuery );
18583 
18584 		// parse SQL query
18585 		if ( m_tVars.m_bProfile )
18586 			m_tProfile.Switch ( SPH_QSTATE_SQL_PARSE );
18587 
18588 		CSphVector<SqlStmt_t> dStmt;
18589 		bool bParsedOK = ParseSqlQuery ( sQuery.cstr(), tCrashQuery.m_iSize, dStmt, m_sError, m_tVars.m_eCollation );
18590 
18591 		if ( m_tVars.m_bProfile )
18592 			m_tProfile.Switch ( SPH_QSTATE_UNKNOWN );
18593 
18594 		SqlStmt_e eStmt = STMT_PARSE_ERROR;
18595 		if ( bParsedOK )
18596 		{
18597 			eStmt = dStmt[0].m_eStmt;
18598 			dStmt[0].m_sStmt = sQuery.cstr();
18599 		}
18600 
18601 		SqlStmt_t * pStmt = dStmt.Begin();
18602 		assert ( !bParsedOK || pStmt );
18603 
18604 		if ( pThd )
18605 			pThd->m_sCommand = g_dSqlStmts[eStmt];
18606 		THD_STATE ( THD_QUERY );
18607 
18608 		SqlRowBuffer_c tOut ( &uPacketID, &tOutput );
18609 
18610 		// handle multi SQL query
18611 		if ( bParsedOK && dStmt.GetLength()>1 )
18612 		{
18613 			m_sError = "";
18614 			HandleMysqlMultiStmt ( dStmt, m_tLastMeta, tOut, pThd, m_sError );
18615 			return true; // FIXME? how does this work with profiling?
18616 		}
18617 
18618 		// handle SQL query
18619 		switch ( eStmt )
18620 		{
18621 		case STMT_PARSE_ERROR:
18622 			m_tLastMeta = CSphQueryResultMeta();
18623 			m_tLastMeta.m_sError = m_sError;
18624 			m_tLastMeta.m_sWarning = "";
18625 			tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18626 			return true;
18627 
18628 		case STMT_SELECT:
18629 			{
18630 				MEMORY ( MEM_SQL_SELECT );
18631 
18632 				StatCountCommand ( SEARCHD_COMMAND_SEARCH );
18633 				SearchHandler_c tHandler ( 1, true, true );
18634 				tHandler.m_dQueries[0] = dStmt.Begin()->m_tQuery;
18635 				if ( m_tVars.m_bProfile )
18636 					tHandler.m_pProfile = &m_tProfile;
18637 
18638 				if ( HandleMysqlSelect ( tOut, tHandler ) )
18639 				{
18640 					// query just completed ok; reset out error message
18641 					m_sError = "";
18642 					AggrResult_t & tLast = tHandler.m_dResults.Last();
18643 					SendMysqlSelectResult ( tOut, tLast, false );
18644 				}
18645 
18646 				// save meta for SHOW META (profile is saved elsewhere)
18647 				m_tLastMeta = tHandler.m_dResults.Last();
18648 				return true;
18649 			}
18650 		case STMT_SHOW_WARNINGS:
18651 			HandleMysqlWarning ( m_tLastMeta, tOut, false );
18652 			return true;
18653 
18654 		case STMT_SHOW_STATUS:
18655 		case STMT_SHOW_META:
18656 		case STMT_SHOW_AGENT_STATUS:
18657 			if ( eStmt==STMT_SHOW_STATUS )
18658 			{
18659 				StatCountCommand ( SEARCHD_COMMAND_STATUS );
18660 			}
18661 			HandleMysqlMeta ( tOut, *pStmt, m_tLastMeta, false );
18662 			return true;
18663 
18664 		case STMT_INSERT:
18665 		case STMT_REPLACE:
18666 			m_tLastMeta = CSphQueryResultMeta();
18667 			m_tLastMeta.m_sError = m_sError;
18668 			m_tLastMeta.m_sWarning = "";
18669 			HandleMysqlInsert ( tOut, *pStmt, eStmt==STMT_REPLACE,
18670 				m_tVars.m_bAutoCommit && !m_tVars.m_bInTransaction, m_tLastMeta.m_sWarning );
18671 			return true;
18672 
18673 		case STMT_DELETE:
18674 			m_tLastMeta = CSphQueryResultMeta();
18675 			m_tLastMeta.m_sError = m_sError;
18676 			m_tLastMeta.m_sWarning = "";
18677 			StatCountCommand ( SEARCHD_COMMAND_DELETE );
18678 			HandleMysqlDelete ( tOut, *pStmt, sQuery, m_tVars.m_bAutoCommit && !m_tVars.m_bInTransaction );
18679 			return true;
18680 
18681 		case STMT_SET:
18682 			HandleMysqlSet ( tOut, *pStmt, m_tVars );
18683 			return false;
18684 
18685 		case STMT_BEGIN:
18686 			{
18687 				MEMORY ( MEM_SQL_BEGIN );
18688 
18689 				m_tVars.m_bInTransaction = true;
18690 				ISphRtIndex * pIndex = sphGetCurrentIndexRT();
18691 				if ( pIndex )
18692 					pIndex->Commit();
18693 				tOut.Ok();
18694 				return true;
18695 			}
18696 		case STMT_COMMIT:
18697 		case STMT_ROLLBACK:
18698 			{
18699 				MEMORY ( MEM_SQL_COMMIT );
18700 
18701 				m_tVars.m_bInTransaction = false;
18702 				ISphRtIndex * pIndex = sphGetCurrentIndexRT();
18703 				if ( pIndex )
18704 				{
18705 					if ( eStmt==STMT_COMMIT )
18706 						pIndex->Commit();
18707 					else
18708 						pIndex->RollBack();
18709 				}
18710 				tOut.Ok();
18711 				return true;
18712 			}
18713 		case STMT_CALL:
18714 			// IMPORTANT! if you add a new builtin here, do also add it
18715 			// in the comment to STMT_CALL line in SqlStmt_e declaration,
18716 			// the one that lists expansions for doc/check.pl
18717 			pStmt->m_sCallProc.ToUpper();
18718 			if ( pStmt->m_sCallProc=="SNIPPETS" )
18719 			{
18720 				StatCountCommand ( SEARCHD_COMMAND_EXCERPT );
18721 				HandleMysqlCallSnippets ( tOut, *pStmt );
18722 			} else if ( pStmt->m_sCallProc=="KEYWORDS" )
18723 			{
18724 				StatCountCommand ( SEARCHD_COMMAND_KEYWORDS );
18725 				HandleMysqlCallKeywords ( tOut, *pStmt );
18726 			} else
18727 			{
18728 				m_sError.SetSprintf ( "no such builtin procedure %s", pStmt->m_sCallProc.cstr() );
18729 				tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18730 			}
18731 			return true;
18732 
18733 		case STMT_DESCRIBE:
18734 			HandleMysqlDescribe ( tOut, *pStmt );
18735 			return true;
18736 
18737 		case STMT_SHOW_TABLES:
18738 			HandleMysqlShowTables ( tOut, *pStmt );
18739 			return true;
18740 
18741 		case STMT_UPDATE:
18742 			m_tLastMeta = CSphQueryResultMeta();
18743 			m_tLastMeta.m_sError = m_sError;
18744 			m_tLastMeta.m_sWarning = "";
18745 			StatCountCommand ( SEARCHD_COMMAND_UPDATE );
18746 			HandleMysqlUpdate ( tOut, *pStmt, sQuery, m_tVars.m_bAutoCommit && !m_tVars.m_bInTransaction, m_tLastMeta.m_sWarning );
18747 			return true;
18748 
18749 		case STMT_DUMMY:
18750 			tOut.Ok();
18751 			return true;
18752 
18753 		case STMT_CREATE_FUNCTION:
18754 			if ( !sphPluginCreate ( pStmt->m_sUdfLib.cstr(), PLUGIN_FUNCTION, pStmt->m_sUdfName.cstr(), pStmt->m_eUdfType, m_sError ) )
18755 				tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18756 			else
18757 				tOut.Ok();
18758 			g_tmSphinxqlState = sphMicroTimer();
18759 			return true;
18760 
18761 		case STMT_DROP_FUNCTION:
18762 			if ( !sphPluginDrop ( PLUGIN_FUNCTION, pStmt->m_sUdfName.cstr(), m_sError ) )
18763 				tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18764 			else
18765 				tOut.Ok();
18766 			g_tmSphinxqlState = sphMicroTimer();
18767 			return true;
18768 
18769 		case STMT_CREATE_PLUGIN:
18770 		case STMT_DROP_PLUGIN:
18771 			{
18772 				// convert plugin type string to enum
18773 				PluginType_e eType = sphPluginGetType ( pStmt->m_sStringParam );
18774 				if ( eType==PLUGIN_TOTAL )
18775 				{
18776 					tOut.Error ( "unknown plugin type '%s'", pStmt->m_sStringParam.cstr() );
18777 					break;
18778 				}
18779 
18780 				// action!
18781 				bool bRes;
18782 				if ( eStmt==STMT_CREATE_PLUGIN )
18783 					bRes = sphPluginCreate ( pStmt->m_sUdfLib.cstr(), eType, pStmt->m_sUdfName.cstr(), SPH_ATTR_NONE, m_sError );
18784 				else
18785 					bRes = sphPluginDrop ( eType, pStmt->m_sUdfName.cstr(), m_sError );
18786 
18787 				// report
18788 				if ( !bRes )
18789 					tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18790 				else
18791 					tOut.Ok();
18792 				g_tmSphinxqlState = sphMicroTimer();
18793 				return true;
18794 			}
18795 
18796 
18797 		case STMT_ATTACH_INDEX:
18798 			HandleMysqlAttach ( tOut, *pStmt );
18799 			return true;
18800 
18801 		case STMT_FLUSH_RTINDEX:
18802 			HandleMysqlFlushRtindex ( tOut, *pStmt );
18803 			return true;
18804 
18805 		case STMT_FLUSH_RAMCHUNK:
18806 			HandleMysqlFlushRamchunk ( tOut, *pStmt );
18807 			return true;
18808 
18809 		case STMT_SHOW_VARIABLES:
18810 			HandleMysqlShowVariables ( tOut, *pStmt, m_tVars );
18811 			return true;
18812 
18813 		case STMT_TRUNCATE_RTINDEX:
18814 			HandleMysqlTruncate ( tOut, *pStmt );
18815 			return true;
18816 
18817 		case STMT_OPTIMIZE_INDEX:
18818 			HandleMysqlOptimize ( tOut, *pStmt );
18819 			return true;
18820 
18821 		case STMT_SELECT_SYSVAR:
18822 			HandleMysqlSelectSysvar ( tOut, *pStmt );
18823 			return true;
18824 
18825 		case STMT_SHOW_COLLATION:
18826 			HandleMysqlShowCollations ( tOut );
18827 			return true;
18828 
18829 		case STMT_SHOW_CHARACTER_SET:
18830 			HandleMysqlShowCharacterSet ( tOut );
18831 			return true;
18832 
18833 		case STMT_SHOW_INDEX_STATUS:
18834 			HandleMysqlShowIndexStatus ( tOut, *pStmt );
18835 			return true;
18836 
18837 		case STMT_SHOW_INDEX_SETTINGS:
18838 			HandleMysqlShowIndexSettings ( tOut, *pStmt );
18839 			return true;
18840 
18841 		case STMT_SHOW_PROFILE:
18842 			HandleMysqlShowProfile ( tOut, m_tLastProfile, false );
18843 			return false; // do not profile this call, keep last query profile
18844 
18845 		case STMT_ALTER_ADD:
18846 			HandleMysqlAlter ( tOut, *pStmt, true );
18847 			return true;
18848 
18849 		case STMT_ALTER_DROP:
18850 			HandleMysqlAlter ( tOut, *pStmt, false );
18851 			return true;
18852 
18853 		case STMT_SHOW_PLAN:
18854 			HandleMysqlShowPlan ( tOut, m_tLastProfile, false );
18855 			return false; // do not profile this call, keep last query profile
18856 
18857 		case STMT_SELECT_DUAL:
18858 			HandleMysqlSelectDual ( tOut, *pStmt );
18859 			return true;
18860 
18861 		case STMT_SHOW_DATABASES:
18862 			HandleMysqlShowDatabases ( tOut, *pStmt );
18863 			return true;
18864 
18865 		case STMT_SHOW_PLUGINS:
18866 			HandleMysqlShowPlugins ( tOut, *pStmt );
18867 			return true;
18868 
18869 		case STMT_SHOW_THREADS:
18870 			HandleMysqlShowThreads ( tOut, *pStmt );
18871 			return true;
18872 
18873 		case STMT_ALTER_RECONFIGURE:
18874 			HandleMysqlReconfigure ( tOut, *pStmt );
18875 			return true;
18876 
18877 		case STMT_FLUSH_INDEX:
18878 			HandleMysqlFlush ( tOut, *pStmt );
18879 			return true;
18880 
18881 		default:
18882 			m_sError.SetSprintf ( "internal error: unhandled statement type (value=%d)", eStmt );
18883 			tOut.Error ( sQuery.cstr(), m_sError.cstr() );
18884 			return true;
18885 		} // switch
18886 		return true; // for cases that break early
18887 	}
18888 };
18889 
18890 
18891 /// sphinxql command over API
HandleCommandSphinxql(int iSock,int iVer,InputBuffer_c & tReq)18892 void HandleCommandSphinxql ( int iSock, int iVer, InputBuffer_c & tReq )
18893 {
18894 	if ( !CheckCommandVersion ( iVer, VER_COMMAND_SPHINXQL, tReq ) )
18895 		return;
18896 
18897 	// parse request
18898 	CSphString sCommand = tReq.GetString ();
18899 
18900 	NetOutputBuffer_c tOut ( iSock );
18901 	BYTE uDummy = 0;
18902 	CSphString sError;
18903 
18904 	// todo: move upper, if the session variables are also necessary in API access mode.
18905 	CSphinxqlSession tSession ( sError );
18906 
18907 	tOut.Flush();
18908 	tOut.SendWord ( SEARCHD_OK );
18909 	tOut.SendWord ( VER_COMMAND_SPHINXQL );
18910 
18911 	// assume that the whole answer could fit in output buffer without flush.
18912 	// Otherwise the error will be fired.
18913 	// SEARCHD_ERROR + strlen (32) + the message
18914 	tOut.FreezeBlock ( "\x01\x00\x20\x00\x00\x00The output buffer is overloaded.", 38 );
18915 	tSession.Execute ( sCommand, tOut, uDummy );
18916 	tOut.Flush ( true );
18917 }
18918 
18919 
StatCountCommand(int iCmd,int iCount)18920 void StatCountCommand ( int iCmd, int iCount )
18921 {
18922 	if ( g_pStats && iCmd>=0 && iCmd<SEARCHD_COMMAND_TOTAL )
18923 	{
18924 		g_tStatsMutex.Lock();
18925 		g_pStats->m_iCommandCount[iCmd] += iCount;
18926 		g_tStatsMutex.Unlock();
18927 	}
18928 }
18929 
18930 
HandleClientMySQL(int iSock,const char * sClientIP,ThdDesc_t * pThd)18931 static void HandleClientMySQL ( int iSock, const char * sClientIP, ThdDesc_t * pThd )
18932 {
18933 	MEMORY ( MEM_SQL_HANDLE );
18934 	THD_STATE ( THD_HANDSHAKE );
18935 
18936 	const int INTERACTIVE_TIMEOUT = 900;
18937 	NetInputBuffer_c tIn ( iSock );
18938 	NetOutputBuffer_c tOut ( iSock ); // OPTIMIZE? looks like buffer size matters a lot..
18939 	int64_t iCID = ( pThd ? pThd->m_iConnID : g_iConnID );
18940 
18941 	if ( sphSockSend ( iSock, g_sMysqlHandshake, g_iMysqlHandshake )!=g_iMysqlHandshake )
18942 	{
18943 		int iErrno = sphSockGetErrno ();
18944 		sphWarning ( "failed to send server version (client=%s(" INT64_FMT "), error: %d '%s')", sClientIP, iCID, iErrno, sphSockError ( iErrno ) );
18945 		return;
18946 	}
18947 
18948 	bool bAuthed = false;
18949 	BYTE uPacketID = 1;
18950 
18951 	CSphString sError;
18952 	CSphinxqlSession tSession ( sError ); // session variables and state
18953 
18954 	CSphString sQuery; // to keep data alive for SphCrashQuery_c
18955 	for ( ;; )
18956 	{
18957 		// set off query guard
18958 		CrashQuery_t tCrashQuery;
18959 		tCrashQuery.m_bMySQL = true;
18960 		SphCrashLogger_c::SetLastQuery ( tCrashQuery );
18961 
18962 		// get next packet
18963 		// we want interruptible calls here, so that shutdowns could be honored
18964 		THD_STATE ( THD_NET_IDLE );
18965 		if ( !tIn.ReadFrom ( 4, INTERACTIVE_TIMEOUT, true ) )
18966 		{
18967 			sphLogDebugv ( "conn %s(" INT64_FMT "): bailing on failed MySQL header (sockerr=%s)", sClientIP, iCID, sphSockError() );
18968 			break;
18969 		}
18970 
18971 		// setup per-query profiling
18972 		assert ( !tOut.m_pProfile ); // at the loop start, must be NULL, even when profiling is enabeld
18973 		bool bProfile = tSession.m_tVars.m_bProfile; // the current statement might change it
18974 		if ( bProfile )
18975 		{
18976 			tSession.m_tProfile.Start ( SPH_QSTATE_NET_READ );
18977 			tOut.m_pProfile = &tSession.m_tProfile;
18978 		}
18979 
18980 		// keep getting that packet
18981 		THD_STATE ( THD_NET_READ );
18982 		const int MAX_PACKET_LEN = 0xffffffL; // 16777215 bytes, max low level packet size
18983 		DWORD uPacketHeader = tIn.GetLSBDword ();
18984 		int iPacketLen = ( uPacketHeader & MAX_PACKET_LEN );
18985 		if ( !tIn.ReadFrom ( iPacketLen, INTERACTIVE_TIMEOUT, true ) )
18986 		{
18987 			sphWarning ( "failed to receive MySQL request body (client=%s(" INT64_FMT "), exp=%d, error='%s')",
18988 				sClientIP, iCID, iPacketLen, sphSockError() );
18989 			break;
18990 		}
18991 
18992 		if ( bProfile )
18993 			tSession.m_tProfile.Switch ( SPH_QSTATE_UNKNOWN );
18994 
18995 		// handle it!
18996 		uPacketID = 1 + (BYTE)( uPacketHeader>>24 ); // client will expect this id
18997 
18998 		// handle big packets
18999 		if ( iPacketLen==MAX_PACKET_LEN )
19000 		{
19001 			NetInputBuffer_c tIn2 ( iSock );
19002 			int iAddonLen = -1;
19003 			do
19004 			{
19005 				if ( !tIn2.ReadFrom ( 4, INTERACTIVE_TIMEOUT, true ) )
19006 				{
19007 					sphLogDebugv ( "conn %s(" INT64_FMT "): bailing on failed MySQL header2 (sockerr=%s)",
19008 						sClientIP, iCID, sphSockError() );
19009 					break;
19010 				}
19011 
19012 				DWORD uAddon = tIn2.GetLSBDword();
19013 				uPacketID = 1 + (BYTE)( uAddon>>24 );
19014 				iAddonLen = ( uAddon & MAX_PACKET_LEN );
19015 				if ( !tIn.ReadFrom ( iAddonLen, INTERACTIVE_TIMEOUT, true, true ) )
19016 				{
19017 					sphWarning ( "failed to receive MySQL request body2 (client=%s(" INT64_FMT "), exp=%d, error='%s')",
19018 						sClientIP, iCID, iAddonLen, sphSockError() );
19019 					iAddonLen = -1;
19020 					break;
19021 				}
19022 				iPacketLen += iAddonLen;
19023 			} while ( iAddonLen==MAX_PACKET_LEN );
19024 			if ( iAddonLen<0 )
19025 				break;
19026 			if ( iPacketLen<0 || iPacketLen>g_iMaxPacketSize )
19027 			{
19028 				sphWarning ( "ill-formed client request (length=%d out of bounds)", iPacketLen );
19029 				break;
19030 			}
19031 		}
19032 
19033 		// handle auth packet
19034 		if ( !bAuthed )
19035 		{
19036 			THD_STATE ( THD_NET_WRITE );
19037 			bAuthed = true;
19038 			SendMysqlOkPacket ( tOut, uPacketID );
19039 			if ( !tOut.Flush() )
19040 				break;
19041 			continue;
19042 		}
19043 
19044 		// get command, handle special packets
19045 		const BYTE uMysqlCmd = tIn.GetByte ();
19046 		if ( uMysqlCmd==MYSQL_COM_QUIT )
19047 			break;
19048 
19049 		bool bKeepProfile = true;
19050 		switch ( uMysqlCmd )
19051 		{
19052 			case MYSQL_COM_PING:
19053 			case MYSQL_COM_INIT_DB:
19054 				// client wants a pong
19055 				SendMysqlOkPacket ( tOut, uPacketID );
19056 				break;
19057 
19058 			case MYSQL_COM_SET_OPTION:
19059 				// bMulti = ( tIn.GetWord()==MYSQL_OPTION_MULTI_STATEMENTS_ON ); // that's how we could double check and validate multi query
19060 				// server reporting success in response to COM_SET_OPTION and COM_DEBUG
19061 				SendMysqlEofPacket ( tOut, uPacketID, 0 );
19062 				break;
19063 
19064 			case MYSQL_COM_QUERY:
19065 				// handle query packet
19066 				assert ( uMysqlCmd==MYSQL_COM_QUERY );
19067 				sQuery = tIn.GetRawString ( iPacketLen-1 ); // OPTIMIZE? could be huge; avoid copying?
19068 				assert ( !tIn.GetError() );
19069 				if ( pThd )
19070 				{
19071 					THD_STATE ( THD_QUERY );
19072 					pThd->SetThreadInfo ( "%s", sQuery.cstr() ); // OPTIMIZE? could be huge; avoid copying?
19073 				}
19074 				bKeepProfile = tSession.Execute ( sQuery, tOut, uPacketID, pThd );
19075 				break;
19076 
19077 			default:
19078 				// default case, unknown command
19079 				sError.SetSprintf ( "unknown command (code=%d)", uMysqlCmd );
19080 				SendMysqlErrorPacket ( tOut, uPacketID, NULL, sError.cstr(), MYSQL_ERR_UNKNOWN_COM_ERROR );
19081 				break;
19082 		}
19083 
19084 		// send the response packet
19085 		THD_STATE ( THD_NET_WRITE );
19086 		if ( !tOut.Flush() )
19087 			break;
19088 
19089 		// finalize query profile
19090 		if ( bProfile )
19091 			tSession.m_tProfile.Stop();
19092 		if ( uMysqlCmd==MYSQL_COM_QUERY && bKeepProfile )
19093 			tSession.m_tLastProfile = tSession.m_tProfile;
19094 		tOut.m_pProfile = NULL;
19095 	} // for (;;)
19096 
19097 	// set off query guard
19098 	SphCrashLogger_c::SetLastQuery ( CrashQuery_t() );
19099 }
19100 
19101 //////////////////////////////////////////////////////////////////////////
19102 // HANDLE-BY-LISTENER
19103 //////////////////////////////////////////////////////////////////////////
19104 
HandleClient(ProtocolType_e eProto,int iSock,const char * sClientIP,ThdDesc_t * pThd)19105 void HandleClient ( ProtocolType_e eProto, int iSock, const char * sClientIP, ThdDesc_t * pThd )
19106 {
19107 	switch ( eProto )
19108 	{
19109 		case PROTO_SPHINX:		HandleClientSphinx ( iSock, sClientIP, pThd ); break;
19110 		case PROTO_MYSQL41:		HandleClientMySQL ( iSock, sClientIP, pThd ); break;
19111 		default:				assert ( 0 && "unhandled protocol type" ); break;
19112 	}
19113 }
19114 
19115 /////////////////////////////////////////////////////////////////////////////
19116 // INDEX ROTATION
19117 /////////////////////////////////////////////////////////////////////////////
19118 
TryRename(const char * sIndex,const char * sPrefix,const char * sFromPostfix,const char * sToPostfix,const char * sAction,bool bFatal,bool bCheckExist)19119 bool TryRename ( const char * sIndex, const char * sPrefix, const char * sFromPostfix, const char * sToPostfix, const char * sAction, bool bFatal, bool bCheckExist )
19120 {
19121 	char sFrom [ SPH_MAX_FILENAME_LEN ];
19122 	char sTo [ SPH_MAX_FILENAME_LEN ];
19123 
19124 	snprintf ( sFrom, sizeof(sFrom), "%s%s", sPrefix, sFromPostfix );
19125 	snprintf ( sTo, sizeof(sTo), "%s%s", sPrefix, sToPostfix );
19126 
19127 #if USE_WINDOWS
19128 	::unlink ( sTo );
19129 #endif
19130 
19131 	// if there is no file we have nothing to do
19132 	if ( !bCheckExist && !sphIsReadable ( sFrom ) )
19133 		return true;
19134 
19135 	if ( rename ( sFrom, sTo ) )
19136 	{
19137 		if ( bFatal )
19138 		{
19139 			sphFatal ( "%s index '%s': rollback rename '%s' to '%s' failed: %s",
19140 				sAction, sIndex, sFrom, sTo, strerror(errno) );
19141 		} else
19142 		{
19143 			sphWarning ( "%s index '%s': rename '%s' to '%s' failed: %s",
19144 				sAction, sIndex, sFrom, sTo, strerror(errno) );
19145 		}
19146 		return false;
19147 	}
19148 
19149 	return true;
19150 }
19151 
19152 
HasFiles(const char * sPath,const char ** dExts)19153 bool HasFiles ( const char * sPath, const char ** dExts )
19154 {
19155 	char sFile [ SPH_MAX_FILENAME_LEN ];
19156 
19157 	for ( int i=0; i<sphGetExtCount(); i++ )
19158 	{
19159 		snprintf ( sFile, sizeof(sFile), "%s%s", sPath, dExts[i] );
19160 		if ( !sphIsReadable ( sFile ) )
19161 			return false;
19162 	}
19163 
19164 	return true;
19165 }
19166 
CheckIndexReenable(const char * sIndexBase,bool bOnlyNew,bool & bGotNew,bool & bReEnable)19167 void CheckIndexReenable ( const char * sIndexBase, bool bOnlyNew, bool & bGotNew, bool & bReEnable )
19168 {
19169 	bReEnable = false;
19170 	char sHeaderName [ SPH_MAX_FILENAME_LEN ];
19171 
19172 	snprintf ( sHeaderName, sizeof(sHeaderName), "%s%s", sIndexBase, sphGetExt ( SPH_EXT_TYPE_NEW, SPH_EXT_SPH ) );
19173 	bGotNew = sphIsReadable ( sHeaderName );
19174 
19175 	if ( bGotNew || !bOnlyNew )
19176 		return;
19177 
19178 	snprintf ( sHeaderName, sizeof(sHeaderName), "%s%s", sIndexBase, sphGetExt( SPH_EXT_TYPE_CUR, SPH_EXT_SPH ) );
19179 	bReEnable = sphIsReadable ( sHeaderName );
19180 }
19181 
19182 
19183 /// returns true if any version of the index (old or new one) has been preread
RotateIndexGreedy(ServedDesc_t & tIndex,const char * sIndex)19184 bool RotateIndexGreedy ( ServedDesc_t & tIndex, const char * sIndex )
19185 {
19186 	sphLogDebug ( "RotateIndexGreedy for '%s' invoked", sIndex );
19187 	char sFile [ SPH_MAX_FILENAME_LEN ];
19188 	const char * sPath = tIndex.m_sIndexPath.cstr();
19189 	const char * sAction = "rotating";
19190 
19191 	bool bGotNewFiles = false;
19192 	bool bReEnable = false;
19193 	CheckIndexReenable ( sPath, tIndex.m_bOnlyNew, bGotNewFiles, bReEnable );
19194 
19195 	CSphString sError;
19196 	snprintf ( sFile, sizeof( sFile ), "%s%s", sPath, sphGetExt ( bReEnable ? SPH_EXT_TYPE_CUR : SPH_EXT_TYPE_NEW, SPH_EXT_SPH ) );
19197 	DWORD uVersion = ReadVersion ( sFile, sError );
19198 
19199 	if ( !sError.IsEmpty() )
19200 	{
19201 		// no files - no rotation
19202 		return false;
19203 	}
19204 
19205 	for ( int i=0; i<sphGetExtCount ( uVersion ); i++ )
19206 	{
19207 		snprintf ( sFile, sizeof( sFile ), "%s%s", sPath, sphGetExts ( bReEnable ? SPH_EXT_TYPE_CUR : SPH_EXT_TYPE_NEW, uVersion )[i] );
19208 		if ( !sphIsReadable ( sFile ) )
19209 		{
19210 			if ( tIndex.m_bOnlyNew )
19211 				sphWarning ( "rotating index '%s': '%s' unreadable: %s; NOT SERVING", sIndex, sFile, strerror ( errno ) );
19212 			else
19213 				sphWarning ( "rotating index '%s': '%s' unreadable: %s; using old index", sIndex, sFile, strerror ( errno ) );
19214 			return false;
19215 		}
19216 	}
19217 
19218 	if ( bReEnable )
19219 	{
19220 		sphLogDebug ( "RotateIndexGreedy: re-enabling index" );
19221 	} else
19222 	{
19223 		sphLogDebug ( "RotateIndexGreedy: new index is readable" );
19224 	}
19225 
19226 	bool bNoMVP = true;
19227 	if ( !tIndex.m_bOnlyNew )
19228 	{
19229 		// rename current to old
19230 		for ( int i=0; i<sphGetExtCount ( uVersion ); i++ )
19231 		{
19232 			snprintf ( sFile, sizeof(sFile), "%s%s", sPath, sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[i] );
19233 			if ( !sphIsReadable ( sFile ) || TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[i], sphGetExts ( SPH_EXT_TYPE_OLD, uVersion )[i], sAction, false ) )
19234 				continue;
19235 
19236 			// rollback
19237 			for ( int j=0; j<i; j++ )
19238 				TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_OLD, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sAction, true );
19239 
19240 			sphWarning ( "rotating index '%s': rename to .old failed; using old index", sIndex );
19241 			return false;
19242 		}
19243 
19244 		// holding the persistent MVA updates (.mvp).
19245 		for ( ;; )
19246 		{
19247 			char sBuf [ SPH_MAX_FILENAME_LEN ];
19248 			snprintf ( sBuf, sizeof(sBuf), "%s%s", sPath, sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ) );
19249 
19250 			CSphString sFakeError;
19251 			CSphAutofile fdTest ( sBuf, SPH_O_READ, sFakeError );
19252 			bNoMVP = ( fdTest.GetFD()<0 );
19253 			fdTest.Close();
19254 			if ( bNoMVP )
19255 				break; ///< no file, nothing to hold
19256 
19257 			if ( TryRename ( sIndex, sPath, sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ), sphGetExt ( SPH_EXT_TYPE_OLD, SPH_EXT_MVP ), sAction, false, false ) )
19258 				break;
19259 
19260 			// rollback
19261 			for ( int j=0; j<sphGetExtCount ( uVersion ); j++ )
19262 				TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_OLD, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sAction, true );
19263 
19264 			break;
19265 		}
19266 		sphLogDebug ( "RotateIndexGreedy: Current index renamed to .old" );
19267 	}
19268 
19269 	// rename new to current
19270 	if ( !bReEnable )
19271 	{
19272 		for ( int i=0; i<sphGetExtCount ( uVersion ); i++ )
19273 		{
19274 			if ( TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_NEW, uVersion )[i], sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[i], sAction, false ) )
19275 				continue;
19276 
19277 			// rollback new ones we already renamed
19278 			for ( int j=0; j<i; j++ )
19279 				TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_NEW, uVersion )[j], sAction, true );
19280 
19281 			// rollback old ones
19282 			if ( !tIndex.m_bOnlyNew )
19283 			{
19284 				for ( int j=0; j<sphGetExtCount ( uVersion ); j++ )
19285 					TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_OLD, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sAction, true );
19286 
19287 				if ( !bNoMVP )
19288 					TryRename ( sIndex, sPath, sphGetExt ( SPH_EXT_TYPE_OLD, SPH_EXT_MVP ), sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ), sAction, true );
19289 			}
19290 
19291 			return false;
19292 		}
19293 		sphLogDebug ( "RotateIndexGreedy: New renamed to current" );
19294 	}
19295 
19296 	bool bPreread = false;
19297 
19298 	// try to use new index
19299 	CSphString sWarning;
19300 	ISphTokenizer * pTokenizer = tIndex.m_pIndex->LeakTokenizer (); // FIXME! disable support of that old indexes and remove this bullshit
19301 	CSphDict * pDictionary = tIndex.m_pIndex->LeakDictionary ();
19302 	tIndex.m_pIndex->SetGlobalIDFPath ( tIndex.m_sGlobalIDFPath );
19303 
19304 	if ( !tIndex.m_pIndex->Prealloc ( tIndex.m_bMlock, g_bStripPath, sWarning ) || !tIndex.m_pIndex->Preread() )
19305 	{
19306 		if ( tIndex.m_bOnlyNew )
19307 		{
19308 			sphWarning ( "rotating index '%s': .new preload failed: %s; NOT SERVING", sIndex, tIndex.m_pIndex->GetLastError().cstr() );
19309 			return false;
19310 
19311 		} else
19312 		{
19313 			sphWarning ( "rotating index '%s': .new preload failed: %s", sIndex, tIndex.m_pIndex->GetLastError().cstr() );
19314 
19315 			// try to recover
19316 			for ( int j=0; j<sphGetExtCount ( uVersion ); j++ )
19317 			{
19318 				TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_NEW, uVersion )[j], sAction, true );
19319 				TryRename ( sIndex, sPath, sphGetExts ( SPH_EXT_TYPE_OLD, uVersion )[j], sphGetExts ( SPH_EXT_TYPE_CUR, uVersion )[j], sAction, true );
19320 			}
19321 			TryRename ( sIndex, sPath, sphGetExt ( SPH_EXT_TYPE_OLD, SPH_EXT_MVP ), sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ), sAction, false, false );
19322 			sphLogDebug ( "RotateIndexGreedy: has recovered" );
19323 
19324 			if ( !tIndex.m_pIndex->Prealloc ( tIndex.m_bMlock, g_bStripPath, sWarning ) || !tIndex.m_pIndex->Preread() )
19325 			{
19326 				sphWarning ( "rotating index '%s': .new preload failed; ROLLBACK FAILED; INDEX UNUSABLE", sIndex );
19327 				tIndex.m_bEnabled = false;
19328 
19329 			} else
19330 			{
19331 				tIndex.m_bEnabled = true;
19332 				bPreread = true;
19333 				sphWarning ( "rotating index '%s': .new preload failed; using old index", sIndex );
19334 			}
19335 
19336 			if ( !sWarning.IsEmpty() )
19337 				sphWarning ( "rotating index '%s': %s", sIndex, sWarning.cstr() );
19338 
19339 			if ( !tIndex.m_pIndex->GetTokenizer () )
19340 				tIndex.m_pIndex->SetTokenizer ( pTokenizer );
19341 			else
19342 				SafeDelete ( pTokenizer );
19343 
19344 			if ( !tIndex.m_pIndex->GetDictionary () )
19345 				tIndex.m_pIndex->SetDictionary ( pDictionary );
19346 			else
19347 				SafeDelete ( pDictionary );
19348 		}
19349 
19350 		return bPreread;
19351 
19352 	} else
19353 	{
19354 		bPreread = true;
19355 
19356 		if ( !sWarning.IsEmpty() )
19357 			sphWarning ( "rotating index '%s': %s", sIndex, sWarning.cstr() );
19358 	}
19359 
19360 	if ( !tIndex.m_pIndex->GetTokenizer () )
19361 		tIndex.m_pIndex->SetTokenizer ( pTokenizer );
19362 	else
19363 		SafeDelete ( pTokenizer );
19364 
19365 	if ( !tIndex.m_pIndex->GetDictionary () )
19366 		tIndex.m_pIndex->SetDictionary ( pDictionary );
19367 	else
19368 		SafeDelete ( pDictionary );
19369 
19370 	// unlink .old
19371 	if ( !tIndex.m_bOnlyNew )
19372 	{
19373 		snprintf ( sFile, sizeof(sFile), "%s.old", sPath );
19374 		sphUnlinkIndex ( sFile, false );
19375 	}
19376 
19377 	sphLogDebug ( "RotateIndexGreedy: the old index unlinked" );
19378 
19379 	// uff. all done
19380 	tIndex.m_bEnabled = true;
19381 	tIndex.m_bOnlyNew = false;
19382 	sphInfo ( "rotating index '%s': success", sIndex );
19383 	return bPreread;
19384 }
19385 
19386 /////////////////////////////////////////////////////////////////////////////
19387 // MAIN LOOP
19388 /////////////////////////////////////////////////////////////////////////////
19389 
19390 #if USE_WINDOWS
19391 
CreatePipe(bool,int)19392 int CreatePipe ( bool, int )	{ return -1; }
PipeAndFork(bool,int)19393 int PipeAndFork ( bool, int )	{ return -1; }
19394 
19395 #else
19396 
19397 // open new pipe to be able to receive notifications from children
19398 // adds read-end fd to g_dPipes; returns write-end fd for child
CreatePipe(bool bFatal,int iHandler)19399 int CreatePipe ( bool bFatal, int iHandler )
19400 {
19401 	assert ( g_bHeadDaemon );
19402 	int dPipe[2] = { -1, -1 };
19403 
19404 	for ( ;; )
19405 	{
19406 		if ( pipe(dPipe) )
19407 		{
19408 			if ( bFatal )
19409 				sphFatal ( "pipe() failed (error=%s)", strerror(errno) );
19410 			else
19411 				sphWarning ( "pipe() failed (error=%s)", strerror(errno) );
19412 			break;
19413 		}
19414 
19415 		if ( fcntl ( dPipe[0], F_SETFL, O_NONBLOCK ) )
19416 		{
19417 			sphWarning ( "fcntl(O_NONBLOCK) on pipe failed (error=%s)", strerror(errno) );
19418 			SafeClose ( dPipe[0] );
19419 			SafeClose ( dPipe[1] );
19420 			break;
19421 		}
19422 
19423 		PipeInfo_t tAdd;
19424 		tAdd.m_iFD = dPipe[0];
19425 		tAdd.m_iHandler = iHandler;
19426 		g_dPipes.Add ( tAdd );
19427 		break;
19428 	}
19429 
19430 	return dPipe[1];
19431 }
19432 
19433 
19434 /// create new worker child
19435 /// creates a pipe to it, forks, and does some post-fork work
19436 //
19437 /// in child, returns write-end pipe fd (might be -1!) and sets g_bHeadDaemon to false
19438 /// in parent, returns -1 and leaves g_bHeadDaemon unaffected
PipeAndFork(bool bFatal,int iHandler)19439 int PipeAndFork ( bool bFatal, int iHandler )
19440 {
19441 	int iChildPipe = CreatePipe ( bFatal, iHandler );
19442 	int iFork = fork();
19443 	switch ( iFork )
19444 	{
19445 		// fork() failed
19446 		case -1:
19447 			sphFatal ( "fork() failed (reason: %s)", strerror(errno) );
19448 
19449 		// child process, handle client
19450 		case 0:
19451 			g_bHeadDaemon = false;
19452 			g_bGotSighup = 0; // just in case.. of a race
19453 			g_bGotSigterm = 0;
19454 			sphSetProcessInfo ( false );
19455 			ARRAY_FOREACH ( i, g_dPipes )
19456 				SafeClose ( g_dPipes[i].m_iFD );
19457 			break;
19458 
19459 		// parent process, continue accept()ing
19460 		default:
19461 			g_dChildren.Add ( iFork );
19462 			SafeClose ( iChildPipe );
19463 			break;
19464 	}
19465 	return iChildPipe;
19466 }
19467 
19468 #endif // !USE_WINDOWS
19469 
DumpMemStat()19470 void DumpMemStat ()
19471 {
19472 #if SPH_ALLOCS_PROFILER
19473 	sphMemStatDump ( g_iLogFile );
19474 #endif
19475 }
19476 
19477 /// check and report if there were any leaks since last call
CheckLeaks()19478 void CheckLeaks ()
19479 {
19480 #if SPH_DEBUG_LEAKS
19481 	static int iHeadAllocs = sphAllocsCount ();
19482 	static int iHeadCheckpoint = sphAllocsLastID ();
19483 
19484 	if ( g_dThd.GetLength()==0 && !g_iRotateCount && iHeadAllocs!=sphAllocsCount() )
19485 	{
19486 		sphSeek ( g_iLogFile, 0, SEEK_END );
19487 		sphAllocsDump ( g_iLogFile, iHeadCheckpoint );
19488 
19489 		iHeadAllocs = sphAllocsCount ();
19490 		iHeadCheckpoint = sphAllocsLastID ();
19491 	}
19492 #endif
19493 
19494 #if SPH_ALLOCS_PROFILER
19495 	int iAllocLogPeriod = 60 * 1000000;
19496 	static int64_t tmLastLog = -iAllocLogPeriod*10;
19497 
19498 	const int iAllocCount = sphAllocsCount();
19499 	const float fMemTotal = (float)sphAllocBytes();
19500 
19501 	if ( iAllocLogPeriod>0 && tmLastLog+iAllocLogPeriod<sphMicroTimer() )
19502 	{
19503 		tmLastLog = sphMicroTimer ();
19504 		const int iThdsCount = g_dThd.GetLength ();
19505 		const float fMB = 1024.0f*1024.0f;
19506 		sphInfo ( "--- allocs-count=%d, mem-total=%.4f Mb, active-threads=%d", iAllocCount, fMemTotal/fMB, iThdsCount );
19507 		DumpMemStat ();
19508 	}
19509 #endif
19510 }
19511 
CheckServedEntry(const ServedIndex_t * pEntry,const char * sIndex)19512 static bool CheckServedEntry ( const ServedIndex_t * pEntry, const char * sIndex )
19513 {
19514 	if ( !pEntry )
19515 	{
19516 		sphWarning ( "rotating index '%s': INTERNAL ERROR, index went AWOL", sIndex );
19517 		return false;
19518 	}
19519 
19520 	if ( pEntry->m_bToDelete || !pEntry->m_pIndex )
19521 	{
19522 		if ( pEntry->m_bToDelete )
19523 			sphWarning ( "rotating index '%s': INTERNAL ERROR, entry marked for deletion", sIndex );
19524 
19525 		if ( !pEntry->m_pIndex )
19526 			sphWarning ( "rotating index '%s': INTERNAL ERROR, entry does not have an index", sIndex );
19527 
19528 		return false;
19529 	}
19530 
19531 	return true;
19532 }
19533 
19534 
SetEnableOndiskAttributes(const ServedDesc_t & tDesc,CSphIndex * pIndex)19535 static void SetEnableOndiskAttributes ( const ServedDesc_t & tDesc, CSphIndex * pIndex )
19536 {
19537 	if ( tDesc.m_bOnDiskAttrs || g_bOnDiskAttrs || tDesc.m_bOnDiskPools || g_bOnDiskPools )
19538 		pIndex->SetEnableOndiskAttributes ( tDesc.m_bOnDiskPools || g_bOnDiskPools );
19539 }
19540 
19541 
19542 #define SPH_RT_AUTO_FLUSH_CHECK_PERIOD ( 5000000 )
19543 
RtFlushThreadFunc(void *)19544 static void RtFlushThreadFunc ( void * )
19545 {
19546 	int64_t tmNextCheck = sphMicroTimer() + SPH_RT_AUTO_FLUSH_CHECK_PERIOD;
19547 	while ( !g_bShutdown )
19548 	{
19549 		// stand still till save time
19550 		if ( tmNextCheck>sphMicroTimer() )
19551 		{
19552 			sphSleepMsec ( 50 );
19553 			continue;
19554 		}
19555 
19556 		// collecting available rt indexes at save time
19557 		CSphVector<CSphString> dRtIndexes;
19558 		for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
19559 			if ( it.Get().m_bRT )
19560 				dRtIndexes.Add ( it.GetKey() );
19561 
19562 		// do check+save
19563 		ARRAY_FOREACH_COND ( i, dRtIndexes, !g_bShutdown )
19564 		{
19565 			const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( dRtIndexes[i] );
19566 			if ( !pServed )
19567 				continue;
19568 
19569 			if ( !pServed->m_bEnabled )
19570 			{
19571 				pServed->Unlock();
19572 				continue;
19573 			}
19574 
19575 			ISphRtIndex * pRT = (ISphRtIndex *)pServed->m_pIndex;
19576 			pRT->CheckRamFlush();
19577 
19578 			pServed->Unlock();
19579 		}
19580 
19581 		tmNextCheck = sphMicroTimer() + SPH_RT_AUTO_FLUSH_CHECK_PERIOD;
19582 	}
19583 }
19584 
19585 
RotateIndexMT(const CSphString & sIndex)19586 static void RotateIndexMT ( const CSphString & sIndex )
19587 {
19588 	assert ( g_eWorkers==MPM_THREADS );
19589 	//////////////////
19590 	// load new index
19591 	//////////////////
19592 
19593 	// create new index, copy some settings from existing one
19594 	const ServedIndex_t * pRotating = g_pLocalIndexes->GetRlockedEntry ( sIndex );
19595 	if ( !CheckServedEntry ( pRotating, sIndex.cstr() ) )
19596 	{
19597 		if ( pRotating )
19598 			pRotating->Unlock();
19599 		return;
19600 	}
19601 
19602 	sphInfo ( "rotating index '%s': started", sIndex.cstr() );
19603 	const char * sAction = "rotating";
19604 
19605 	ServedDesc_t tNewIndex;
19606 	tNewIndex.m_bOnlyNew = pRotating->m_bOnlyNew;
19607 
19608 	tNewIndex.m_pIndex = sphCreateIndexPhrase ( sIndex.cstr(), NULL );
19609 	tNewIndex.m_pIndex->m_bExpandKeywords = pRotating->m_bExpand;
19610 	tNewIndex.m_pIndex->m_iExpansionLimit = g_iExpansionLimit;
19611 	tNewIndex.m_pIndex->SetPreopen ( pRotating->m_bPreopen || g_bPreopenIndexes );
19612 	tNewIndex.m_pIndex->SetGlobalIDFPath ( pRotating->m_sGlobalIDFPath );
19613 	tNewIndex.m_bOnDiskAttrs = pRotating->m_bOnDiskAttrs;
19614 	tNewIndex.m_bOnDiskPools = pRotating->m_bOnDiskPools;
19615 	SetEnableOndiskAttributes ( tNewIndex, tNewIndex.m_pIndex );
19616 
19617 	CSphString sIndexPath = pRotating->m_sIndexPath.cstr();
19618 	// don't need to hold the existing index any more now
19619 	pRotating->Unlock();
19620 	pRotating = NULL;
19621 
19622 	bool bGotNew = false;
19623 	bool bReEnable = false;
19624 	CheckIndexReenable ( sIndexPath.cstr(), tNewIndex.m_bOnlyNew, bGotNew, bReEnable );
19625 
19626 	if ( bReEnable )
19627 	{
19628 		tNewIndex.m_pIndex->SetBase ( sIndexPath.cstr() );
19629 		bReEnable = true;
19630 	} else
19631 	{
19632 		// rebase new index
19633 		char sNewPath[SPH_MAX_FILENAME_LEN];
19634 		snprintf ( sNewPath, sizeof( sNewPath ), "%s.new", sIndexPath.cstr() );
19635 		tNewIndex.m_pIndex->SetBase ( sNewPath );
19636 	}
19637 
19638 	// prealloc enough RAM and lock new index
19639 	sphLogDebug ( "prealloc enough RAM and lock new index" );
19640 	CSphString sWarn, sError;
19641 	if ( !tNewIndex.m_pIndex->Prealloc ( tNewIndex.m_bMlock, g_bStripPath, sWarn ) )
19642 	{
19643 		sphWarning ( "rotating index '%s': prealloc: %s; using old index", sIndex.cstr(), tNewIndex.m_pIndex->GetLastError().cstr() );
19644 		return;
19645 	}
19646 
19647 	if ( !tNewIndex.m_pIndex->Lock() )
19648 	{
19649 		sphWarning ( "rotating index '%s': lock: %s; using old index", sIndex.cstr (), tNewIndex.m_pIndex->GetLastError().cstr() );
19650 		return;
19651 	}
19652 
19653 	// fixup settings if needed
19654 	sphLogDebug ( "fixup settings if needed" );
19655 	g_tRotateConfigMutex.Lock ();
19656 	if ( tNewIndex.m_bOnlyNew && g_pCfg.m_tConf ( "index" ) && g_pCfg.m_tConf["index"]( sIndex.cstr() ) )
19657 	{
19658 		if ( !sphFixupIndexSettings ( tNewIndex.m_pIndex, g_pCfg.m_tConf["index"][sIndex.cstr()], sError ) )
19659 		{
19660 			sphWarning ( "rotating index '%s': fixup: %s; using old index", sIndex.cstr(), sError.cstr() );
19661 			g_tRotateConfigMutex.Unlock ();
19662 			return;
19663 		}
19664 	}
19665 	g_tRotateConfigMutex.Unlock();
19666 
19667 	if ( !tNewIndex.m_pIndex->Preread() )
19668 	{
19669 		sphWarning ( "rotating index '%s': preread failed: %s; using old index", sIndex.cstr(), tNewIndex.m_pIndex->GetLastError().cstr() );
19670 		return;
19671 	}
19672 
19673 	CSphIndexStatus tStatus;
19674 	tNewIndex.m_pIndex->GetStatus ( &tStatus );
19675 	tNewIndex.m_iMass = CalculateMass ( tStatus );
19676 
19677 	//////////////////////
19678 	// activate new index
19679 	//////////////////////
19680 
19681 	sphLogDebug ( "activate new index" );
19682 
19683 	ServedIndex_t * pServed = g_pLocalIndexes->GetWlockedEntry ( sIndex );
19684 	if ( !CheckServedEntry ( pServed, sIndex.cstr() ) )
19685 	{
19686 		if ( pServed )
19687 			pServed->Unlock();
19688 		return;
19689 	}
19690 
19691 	CSphIndex * pOld = pServed->m_pIndex;
19692 	CSphIndex * pNew = tNewIndex.m_pIndex;
19693 
19694 	// rename files
19695 	// FIXME! factor out a common function w/ non-threaded rotation code
19696 	char sOld [ SPH_MAX_FILENAME_LEN ];
19697 	snprintf ( sOld, sizeof(sOld), "%s.old", pServed->m_sIndexPath.cstr() );
19698 	char sCurTest [ SPH_MAX_FILENAME_LEN ];
19699 	snprintf ( sCurTest, sizeof(sCurTest), "%s.sph", pServed->m_sIndexPath.cstr() );
19700 
19701 	if ( !pServed->m_bOnlyNew && sphIsReadable ( sCurTest ) && !pOld->Rename ( sOld ) )
19702 	{
19703 		// FIXME! rollback inside Rename() call potentially fail
19704 		sphWarning ( "rotating index '%s': cur to old rename failed: %s", sIndex.cstr(), pOld->GetLastError().cstr() );
19705 
19706 	} else
19707 	{
19708 		// FIXME! at this point there's no cur lock file; ie. potential race
19709 		sphLogDebug ( "no cur lock file; ie. potential race" );
19710 		if ( !bReEnable && !pNew->Rename ( pServed->m_sIndexPath.cstr() ) )
19711 		{
19712 			sphWarning ( "rotating index '%s': new to cur rename failed: %s", sIndex.cstr(), pNew->GetLastError().cstr() );
19713 			if ( !pServed->m_bOnlyNew && !pOld->Rename ( pServed->m_sIndexPath.cstr() ) )
19714 			{
19715 				sphWarning ( "rotating index '%s': old to cur rename failed: %s; INDEX UNUSABLE", sIndex.cstr(), pOld->GetLastError().cstr() );
19716 				pServed->m_bEnabled = false;
19717 			}
19718 		} else
19719 		{
19720 			// all went fine; swap them
19721 			sphLogDebug ( "all went fine; swap them" );
19722 
19723 			tNewIndex.m_pIndex->m_iTID = pServed->m_pIndex->m_iTID;
19724 			if ( g_pBinlog )
19725 				g_pBinlog->NotifyIndexFlush ( sIndex.cstr(), pServed->m_pIndex->m_iTID, false );
19726 
19727 			if ( !tNewIndex.m_pIndex->GetTokenizer() )
19728 				tNewIndex.m_pIndex->SetTokenizer ( pServed->m_pIndex->LeakTokenizer() );
19729 
19730 			if ( !tNewIndex.m_pIndex->GetDictionary() )
19731 				tNewIndex.m_pIndex->SetDictionary ( pServed->m_pIndex->LeakDictionary() );
19732 
19733 			Swap ( pServed->m_pIndex, tNewIndex.m_pIndex );
19734 			pServed->m_bEnabled = true;
19735 
19736 			// rename current MVP to old one to unlink it
19737 			if ( !bReEnable )
19738 				TryRename ( sIndex.cstr(), pServed->m_sIndexPath.cstr(), sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ), sphGetExt ( SPH_EXT_TYPE_OLD, SPH_EXT_MVP ), sAction, false, false );
19739 
19740 			// unlink .old
19741 			sphLogDebug ( "unlink .old" );
19742 			if ( !pServed->m_bOnlyNew )
19743 			{
19744 				sphUnlinkIndex ( sOld, false );
19745 			}
19746 
19747 			pServed->m_bOnlyNew = false;
19748 			sphInfo ( "rotating index '%s': success", sIndex.cstr() );
19749 		}
19750 	}
19751 
19752 	pServed->Unlock();
19753 }
19754 
RotationThreadFunc(void *)19755 void RotationThreadFunc ( void * )
19756 {
19757 	assert ( g_eWorkers==MPM_THREADS );
19758 	while ( !g_bShutdown )
19759 	{
19760 		// check if we have work to do
19761 		if ( !g_iRotateCount )
19762 		{
19763 			sphSleepMsec ( 50 );
19764 			continue;
19765 		}
19766 		g_tRotateQueueMutex.Lock();
19767 		if ( !g_dRotateQueue.GetLength() )
19768 		{
19769 			g_tRotateQueueMutex.Unlock();
19770 			sphSleepMsec ( 50 );
19771 			continue;
19772 		}
19773 
19774 		CSphString sIndex = g_dRotateQueue.Pop();
19775 		g_sPrereading = sIndex.cstr();
19776 		g_tRotateQueueMutex.Unlock();
19777 
19778 		RotateIndexMT ( sIndex );
19779 
19780 		g_tRotateQueueMutex.Lock();
19781 		if ( !g_dRotateQueue.GetLength() )
19782 		{
19783 			g_iRotateCount.Dec();
19784 			sphInfo ( "rotating index: all indexes done" );
19785 		}
19786 		g_sPrereading = NULL;
19787 		g_tRotateQueueMutex.Unlock();
19788 	}
19789 }
19790 
19791 
IndexRotationDone()19792 void IndexRotationDone ()
19793 {
19794 #if !USE_WINDOWS
19795 	if ( g_iRotationThrottle && g_eWorkers==MPM_PREFORK )
19796 	{
19797 		ARRAY_FOREACH ( i, g_dChildren )
19798 			g_dHupChildren.Add ( g_dChildren[i] );
19799 	} else
19800 	{
19801 		// forcibly restart children serving persistent connections and/or preforked ones
19802 		// FIXME! check how both signals are handled in case of FORK and PREFORK
19803 		ARRAY_FOREACH ( i, g_dChildren )
19804 			kill ( g_dChildren[i], SIGHUP );
19805 	}
19806 #endif
19807 
19808 	g_iRotateCount.Dec();
19809 	g_bInvokeRotationService = true;
19810 	sphInfo ( "rotating finished" );
19811 }
19812 
19813 
SeamlessTryToForkPrereader()19814 void SeamlessTryToForkPrereader ()
19815 {
19816 	sphLogDebug ( "Invoked SeamlessTryToForkPrereader" );
19817 
19818 	// next in line
19819 	const char * sPrereading = g_dRotating.Pop ();
19820 	if ( !sPrereading || !g_pLocalIndexes->Exists ( sPrereading ) )
19821 	{
19822 		sphWarning ( "INTERNAL ERROR: preread attempt on unknown index '%s'", sPrereading ? sPrereading : "(NULL)" );
19823 		return;
19824 	}
19825 	const ServedIndex_t & tServed = g_pLocalIndexes->GetUnlockedEntry ( sPrereading );
19826 
19827 	// alloc buffer index (once per run)
19828 	if ( !g_pPrereading )
19829 		g_pPrereading = sphCreateIndexPhrase ( sPrereading, NULL );
19830 	else
19831 		g_pPrereading->SetName ( sPrereading );
19832 
19833 	g_pPrereading->m_bExpandKeywords = tServed.m_bExpand;
19834 	g_pPrereading->m_iExpansionLimit = g_iExpansionLimit;
19835 	g_pPrereading->SetPreopen ( tServed.m_bPreopen || g_bPreopenIndexes );
19836 	g_pPrereading->SetGlobalIDFPath ( tServed.m_sGlobalIDFPath );
19837 	SetEnableOndiskAttributes ( tServed, tServed.m_pIndex );
19838 
19839 	bool bGotNewFiles = false;
19840 	bool bReEnable = false;
19841 	CheckIndexReenable ( tServed.m_sIndexPath.cstr(), tServed.m_bOnlyNew, bGotNewFiles, bReEnable );
19842 
19843 	// rebase buffer index
19844 	char sNewPath [ SPH_MAX_FILENAME_LEN ];
19845 	if ( bReEnable )
19846 		snprintf ( sNewPath, sizeof(sNewPath), "%s", tServed.m_sIndexPath.cstr() );
19847 	else
19848 		snprintf ( sNewPath, sizeof(sNewPath), "%s.new", tServed.m_sIndexPath.cstr() );
19849 	g_pPrereading->SetBase ( sNewPath );
19850 
19851 	// prealloc enough RAM and lock new index
19852 	sphLogDebug ( "prealloc enough RAM and lock new index" );
19853 	CSphString sWarn, sError;
19854 	if ( !g_pPrereading->Prealloc ( tServed.m_bMlock, g_bStripPath, sWarn ) )
19855 	{
19856 		sphWarning ( "rotating index '%s': prealloc: %s; using old index", sPrereading, g_pPrereading->GetLastError().cstr() );
19857 		if ( !sWarn.IsEmpty() )
19858 			sphWarning ( "rotating index: %s", sWarn.cstr() );
19859 		return;
19860 	}
19861 	if ( !sWarn.IsEmpty() )
19862 		sphWarning ( "rotating index: %s: %s", sPrereading, sWarn.cstr() );
19863 
19864 	if ( !g_pPrereading->Lock() )
19865 	{
19866 		sphWarning ( "rotating index '%s': lock: %s; using old index", sPrereading, g_pPrereading->GetLastError().cstr() );
19867 		g_pPrereading->Dealloc ();
19868 		return;
19869 	}
19870 
19871 	if ( tServed.m_bOnlyNew && g_pCfg.m_tConf.Exists ( "index" ) && g_pCfg.m_tConf["index"].Exists ( sPrereading ) )
19872 		if ( !sphFixupIndexSettings ( g_pPrereading, g_pCfg.m_tConf["index"][sPrereading], sError ) )
19873 		{
19874 			sphWarning ( "rotating index '%s': fixup: %s; using old index", sPrereading, sError.cstr() );
19875 			return;
19876 		}
19877 
19878 	// fork async reader
19879 	sphLogDebug ( "fork async reader" );
19880 	g_sPrereading = sPrereading;
19881 	int iPipeFD = PipeAndFork ( true, SPH_PIPE_PREREAD );
19882 
19883 	// in parent, wait for prereader process to finish
19884 	if ( g_bHeadDaemon )
19885 		return;
19886 
19887 	// in child, do preread
19888 	bool bRes = g_pPrereading->Preread ();
19889 	if ( !bRes )
19890 		sphWarning ( "rotating index '%s': preread failed: %s; using old index", g_sPrereading, g_pPrereading->GetLastError().cstr() );
19891 	// report and exit
19892 	DWORD uTmp = SPH_PIPE_PREREAD;
19893 	sphWrite ( iPipeFD, &uTmp, sizeof(DWORD) ); // FIXME? add buffering/checks?
19894 
19895 	uTmp = bRes;
19896 	sphWrite ( iPipeFD, &uTmp, sizeof(DWORD) );
19897 
19898 	::close ( iPipeFD );
19899 	sphLogDebug ( "SeamlessTryToForkPrereader: finishing the fork and invoking exit ( 0 )" );
19900 	exit ( 0 );
19901 }
19902 
19903 
SeamlessForkPrereader()19904 void SeamlessForkPrereader ()
19905 {
19906 	sphLogDebug ( "Invoked SeamlessForkPrereader" );
19907 	// sanity checks
19908 	if ( g_sPrereading )
19909 	{
19910 		sphWarning ( "INTERNAL ERROR: preread attempt before previous completion" );
19911 		return;
19912 	}
19913 
19914 	// try candidates one by one
19915 	while ( g_dRotating.GetLength() && !g_sPrereading )
19916 		SeamlessTryToForkPrereader ();
19917 
19918 	// if there's no more candidates, and nothing in the works, we're done
19919 	if ( !g_sPrereading && !g_dRotating.GetLength() )
19920 		IndexRotationDone ();
19921 }
19922 
19923 //////////////////////////////////////////////////////////////////////////
19924 // SPHINXQL STATE
19925 //////////////////////////////////////////////////////////////////////////
19926 
19927 struct NamedRefVectorPair_t
19928 {
19929 	CSphString			m_sName;
19930 	UservarIntSet_c *	m_pVal;
19931 };
19932 
19933 
19934 /// SphinxQL state writer thread
19935 /// periodically flushes changes of uservars, UDFs
SphinxqlStateThreadFunc(void *)19936 static void SphinxqlStateThreadFunc ( void * )
19937 {
19938 	assert ( !g_sSphinxqlState.IsEmpty() );
19939 	CSphString sNewState;
19940 	sNewState.SetSprintf ( "%s.new", g_sSphinxqlState.cstr() );
19941 
19942 	char dBuf[512];
19943 	const int iMaxString = 80;
19944 	assert ( (int)sizeof(dBuf) > iMaxString );
19945 
19946 	CSphString sError;
19947 	CSphWriter tWriter;
19948 
19949 	int64_t tmLast = g_tmSphinxqlState;
19950 	while ( !g_bShutdown )
19951 	{
19952 		// stand still till save time
19953 		if ( tmLast==g_tmSphinxqlState )
19954 		{
19955 			sphSleepMsec ( QLSTATE_FLUSH_MSEC );
19956 			continue;
19957 		}
19958 
19959 		// close and truncate the .new file
19960 		tWriter.CloseFile ( true );
19961 		if ( !tWriter.OpenFile ( sNewState, sError ) )
19962 		{
19963 			sphWarning ( "sphinxql_state flush failed: %s", sError.cstr() );
19964 			sphSleepMsec ( QLSTATE_FLUSH_MSEC );
19965 			continue;
19966 		}
19967 
19968 		/////////////
19969 		// save UDFs
19970 		/////////////
19971 
19972 		sphPluginSaveState ( tWriter );
19973 
19974 		/////////////////
19975 		// save uservars
19976 		/////////////////
19977 
19978 		tmLast = g_tmSphinxqlState;
19979 		CSphVector<NamedRefVectorPair_t> dUservars;
19980 		dUservars.Reserve ( g_hUservars.GetLength() );
19981 		g_tUservarsMutex.Lock();
19982 		g_hUservars.IterateStart();
19983 		while ( g_hUservars.IterateNext() )
19984 		{
19985 			if ( !g_hUservars.IterateGet().m_pVal->GetLength() )
19986 				continue;
19987 
19988 			NamedRefVectorPair_t & tPair = dUservars.Add();
19989 			tPair.m_sName = g_hUservars.IterateGetKey();
19990 			tPair.m_pVal = g_hUservars.IterateGet().m_pVal;
19991 			tPair.m_pVal->AddRef();
19992 		}
19993 		g_tUservarsMutex.Unlock();
19994 
19995 		dUservars.Sort ( bind ( &NamedRefVectorPair_t::m_sName ) );
19996 
19997 		// reinitiate store process on new variables added
19998 		ARRAY_FOREACH_COND ( i, dUservars, tmLast==g_tmSphinxqlState )
19999 		{
20000 			const CSphVector<SphAttr_t> & dVals = *dUservars[i].m_pVal;
20001 			int iLen = snprintf ( dBuf, sizeof ( dBuf ), "SET GLOBAL %s = ( " INT64_FMT, dUservars[i].m_sName.cstr(), dVals[0] );
20002 			for ( int j=1; j<dVals.GetLength(); j++ )
20003 			{
20004 				iLen += snprintf ( dBuf+iLen, sizeof ( dBuf ), ", " INT64_FMT, dVals[j] );
20005 
20006 				if ( iLen>=iMaxString && j<dVals.GetLength()-1 )
20007 				{
20008 					iLen += snprintf ( dBuf+iLen, sizeof ( dBuf ), " \\\n" );
20009 					tWriter.PutBytes ( dBuf, iLen );
20010 					iLen = 0;
20011 				}
20012 			}
20013 
20014 			if ( iLen )
20015 				tWriter.PutBytes ( dBuf, iLen );
20016 
20017 			char sTail[] = " );\n";
20018 			tWriter.PutBytes ( sTail, sizeof ( sTail )-1 );
20019 		}
20020 
20021 		// release all locked uservars
20022 		ARRAY_FOREACH ( i, dUservars )
20023 			dUservars[i].m_pVal->Release();
20024 
20025 		/////////////////////////////////
20026 		// writing done, flip the burger
20027 		/////////////////////////////////
20028 
20029 		tWriter.CloseFile();
20030 		if ( ::rename ( sNewState.cstr(), g_sSphinxqlState.cstr() )==0 )
20031 		{
20032 			::unlink ( sNewState.cstr() );
20033 		} else
20034 		{
20035 			sphWarning ( "sphinxql_state flush: rename %s to %s failed: %s",
20036 				sNewState.cstr(), g_sSphinxqlState.cstr(), strerror(errno) );
20037 		}
20038 	}
20039 }
20040 
20041 
20042 /// process a single line from sphinxql state/startup script
SphinxqlStateLine(CSphVector<char> & dLine,CSphString * sError)20043 static bool SphinxqlStateLine ( CSphVector<char> & dLine, CSphString * sError )
20044 {
20045 	assert ( sError );
20046 	if ( !dLine.GetLength() )
20047 		return true;
20048 
20049 	// parser expects CSphString buffer with gap bytes at the end
20050 	if ( dLine.Last()==';' )
20051 		dLine.Pop();
20052 	dLine.Add ( '\0' );
20053 	dLine.Add ( '\0' );
20054 	dLine.Add ( '\0' );
20055 
20056 	CSphVector<SqlStmt_t> dStmt;
20057 	bool bParsedOK = ParseSqlQuery ( dLine.Begin(), dLine.GetLength(), dStmt, *sError, SPH_COLLATION_DEFAULT );
20058 	if ( !bParsedOK )
20059 		return false;
20060 
20061 	bool bOk = true;
20062 	ARRAY_FOREACH ( i, dStmt )
20063 	{
20064 		SqlStmt_t & tStmt = dStmt[i];
20065 		if ( tStmt.m_eStmt==STMT_SET && tStmt.m_eSet==SET_GLOBAL_UVAR )
20066 		{
20067 			// just ignore uservars in non-threads modes
20068 			if ( g_eWorkers==MPM_THREADS )
20069 			{
20070 				tStmt.m_dSetValues.Sort();
20071 				UservarAdd ( tStmt.m_sSetName, tStmt.m_dSetValues );
20072 			}
20073 		} else if ( tStmt.m_eStmt==STMT_CREATE_FUNCTION )
20074 		{
20075 			bOk &= sphPluginCreate ( tStmt.m_sUdfLib.cstr(), PLUGIN_FUNCTION, tStmt.m_sUdfName.cstr(), tStmt.m_eUdfType, *sError );
20076 
20077 		} else if ( tStmt.m_eStmt==STMT_CREATE_PLUGIN )
20078 		{
20079 			bOk &= sphPluginCreate ( tStmt.m_sUdfLib.cstr(), sphPluginGetType ( tStmt.m_sStringParam ),
20080 				tStmt.m_sUdfName.cstr(), SPH_ATTR_NONE, *sError );
20081 		} else
20082 		{
20083 			bOk = false;
20084 			sError->SetSprintf ( "unsupported statement (must be one of SET GLOBAL, CREATE FUNCTION, CREATE PLUGIN)" );
20085 		}
20086 	}
20087 
20088 	return bOk;
20089 }
20090 
20091 
20092 /// uservars table reader
SphinxqlStateRead(const CSphString & sName)20093 static void SphinxqlStateRead ( const CSphString & sName )
20094 {
20095 	if ( sName.IsEmpty() )
20096 		return;
20097 
20098 	CSphString sError;
20099 	CSphAutoreader tReader;
20100 	if ( !tReader.Open ( sName, sError ) )
20101 		return;
20102 
20103 	const int iReadBlock = 32*1024;
20104 	const int iGapLen = 2;
20105 	CSphVector<char> dLine;
20106 	dLine.Reserve ( iReadBlock + iGapLen );
20107 
20108 	bool bEscaped = false;
20109 	int iLines = 0;
20110 	for ( ;; )
20111 	{
20112 		const BYTE * pData = NULL;
20113 		int iRead = tReader.GetBytesZerocopy ( &pData, iReadBlock );
20114 		// all uservars got read
20115 		if ( iRead<=0 )
20116 			break;
20117 
20118 		// read escaped line
20119 		dLine.Reserve ( dLine.GetLength() + iRead + iGapLen );
20120 		const BYTE * s = pData;
20121 		const BYTE * pEnd = pData+iRead;
20122 		while ( s<pEnd )
20123 		{
20124 			// goto next line for escaped string
20125 			if ( *s=='\\' || ( bEscaped && ( *s=='\n' || *s=='\r' ) ) )
20126 			{
20127 				s++;
20128 				while ( s<pEnd && ( *s=='\n' || *s=='\r' ) )
20129 				{
20130 					iLines += ( *s=='\n' );
20131 					s++;
20132 				}
20133 				bEscaped = ( s>=pEnd );
20134 				continue;
20135 			}
20136 
20137 			bEscaped = false;
20138 			if ( *s=='\n' || *s=='\r' )
20139 			{
20140 				if ( !SphinxqlStateLine ( dLine, &sError ) )
20141 					sphWarning ( "sphinxql_state: parse error at line %d: %s", 1+iLines, sError.cstr() );
20142 
20143 				dLine.Resize ( 0 );
20144 				s++;
20145 				while ( s<pEnd && ( *s=='\n' || *s=='\r' ) )
20146 				{
20147 					iLines += ( *s=='\n' );
20148 					s++;
20149 				}
20150 				continue;
20151 			}
20152 
20153 			dLine.Add ( *s );
20154 			s++;
20155 		}
20156 	}
20157 
20158 	if ( !SphinxqlStateLine ( dLine, &sError ) )
20159 		sphWarning ( "sphinxql_state: parse error at line %d: %s", 1+iLines, sError.cstr() );
20160 }
20161 
20162 //////////////////////////////////////////////////////////////////////////
20163 
OptimizeThreadFunc(void *)20164 void OptimizeThreadFunc ( void * )
20165 {
20166 	while ( !g_bShutdown )
20167 	{
20168 		// stand still till optimize time
20169 		if ( !g_dOptimizeQueue.GetLength() )
20170 		{
20171 			sphSleepMsec ( 50 );
20172 			continue;
20173 		}
20174 
20175 		CSphString sIndex;
20176 		g_tOptimizeQueueMutex.Lock();
20177 		if ( g_dOptimizeQueue.GetLength() )
20178 		{
20179 			sIndex = g_dOptimizeQueue[0];
20180 			g_dOptimizeQueue.Remove(0);
20181 		}
20182 		g_tOptimizeQueueMutex.Unlock();
20183 
20184 		const ServedIndex_t * pServed = g_pLocalIndexes->GetRlockedEntry ( sIndex );
20185 		if ( !pServed )
20186 		{
20187 			continue;
20188 		}
20189 		if ( !pServed->m_pIndex || !pServed->m_bEnabled )
20190 		{
20191 			pServed->Unlock();
20192 			continue;
20193 		}
20194 
20195 		// FIXME: MVA update would wait w-lock here for a very long time
20196 		assert ( pServed->m_bRT );
20197 		static_cast<ISphRtIndex *>( pServed->m_pIndex )->Optimize ( &g_bShutdown, &g_tRtThrottle );
20198 
20199 		pServed->Unlock();
20200 	}
20201 }
20202 
20203 
20204 /// simple wrapper to simplify reading from pipes
20205 struct PipeReader_t
20206 {
PipeReader_tPipeReader_t20207 	explicit PipeReader_t ( int iFD )
20208 		: m_iFD ( iFD )
20209 		, m_bError ( false )
20210 	{
20211 #if !USE_WINDOWS
20212 		if ( fcntl ( iFD, F_SETFL, 0 )<0 )
20213 			sphWarning ( "fcntl(0) on pipe failed (error=%s)", strerror(errno) );
20214 #endif
20215 	}
20216 
~PipeReader_tPipeReader_t20217 	~PipeReader_t ()
20218 	{
20219 		SafeClose ( m_iFD );
20220 	}
20221 
GetFDPipeReader_t20222 	int GetFD () const
20223 	{
20224 		return m_iFD;
20225 	}
20226 
IsErrorPipeReader_t20227 	bool IsError () const
20228 	{
20229 		return m_bError;
20230 	}
20231 
GetIntPipeReader_t20232 	int GetInt ()
20233 	{
20234 		int iTmp;
20235 		if ( !GetBytes ( &iTmp, sizeof(iTmp) ) )
20236 			iTmp = 0;
20237 		return iTmp;
20238 	}
20239 
GetStringPipeReader_t20240 	CSphString GetString ()
20241 	{
20242 		int iLen = GetInt ();
20243 		CSphString sRes;
20244 		sRes.Reserve ( iLen );
20245 		if ( !GetBytes ( const_cast<char*> ( sRes.cstr() ), iLen ) )
20246 			sRes = "";
20247 		return sRes;
20248 	}
20249 
20250 protected:
GetBytesPipeReader_t20251 	bool GetBytes ( void * pBuf, int iCount )
20252 	{
20253 		if ( m_bError )
20254 			return false;
20255 
20256 		if ( m_iFD<0 )
20257 		{
20258 			m_bError = true;
20259 			sphWarning ( "invalid pipe fd" );
20260 			return false;
20261 		}
20262 
20263 		for ( ;; )
20264 		{
20265 			int iRes = ::read ( m_iFD, pBuf, iCount );
20266 			if ( iRes<0 && errno==EINTR )
20267 				continue;
20268 
20269 			if ( iRes!=iCount )
20270 			{
20271 				m_bError = true;
20272 				sphWarning ( "pipe read failed (exp=%d, res=%d, error=%s)",
20273 					iCount, iRes, iRes>0 ? "(none)" : strerror(errno) );
20274 				return false;
20275 			}
20276 			return true;
20277 		}
20278 	}
20279 
20280 protected:
20281 	int			m_iFD;
20282 	bool		m_bError;
20283 };
20284 
20285 
20286 /// handle pipe notifications from prereading
HandlePipePreread(PipeReader_t & tPipe,bool bFailure)20287 void HandlePipePreread ( PipeReader_t & tPipe, bool bFailure )
20288 {
20289 	if ( bFailure )
20290 	{
20291 		// clean up previous one and launch next one
20292 		g_sPrereading = NULL;
20293 
20294 		// in any case, buffer index should now be deallocated
20295 		g_pPrereading->Dealloc ();
20296 		g_pPrereading->Unlock ();
20297 
20298 		// work next one
20299 		SeamlessForkPrereader ();
20300 		return;
20301 	}
20302 
20303 	assert ( g_iRotateCount && g_bSeamlessRotate && g_sPrereading );
20304 
20305 	// whatever the outcome, we will be done with this one
20306 	const char * sPrereading = g_sPrereading;
20307 	g_sPrereading = NULL;
20308 
20309 	// notice that this will block!
20310 	int iRes = tPipe.GetInt();
20311 	if ( !tPipe.IsError() && iRes )
20312 	{
20313 		// if preread was successful, exchange served index and prereader buffer index
20314 		ServedIndex_t & tServed = g_pLocalIndexes->GetUnlockedEntry ( sPrereading );
20315 		CSphIndex * pOld = tServed.m_pIndex;
20316 		CSphIndex * pNew = g_pPrereading;
20317 
20318 		char sOld [ SPH_MAX_FILENAME_LEN ];
20319 		snprintf ( sOld, sizeof(sOld), "%s.old", tServed.m_sIndexPath.cstr() );
20320 		char sCurTest [ SPH_MAX_FILENAME_LEN ];
20321 		snprintf ( sCurTest, sizeof(sCurTest), "%s.sph", tServed.m_sIndexPath.cstr() );
20322 
20323 		bool bGotNewFiles = false;
20324 		bool bReEnable = false;
20325 		CheckIndexReenable ( tServed.m_sIndexPath.cstr(), tServed.m_bOnlyNew, bGotNewFiles, bReEnable );
20326 
20327 		if ( !tServed.m_bOnlyNew && sphIsReadable ( sCurTest ) && !pOld->Rename ( sOld ) )
20328 		{
20329 			// FIXME! rollback inside Rename() call potentially fail
20330 			sphWarning ( "rotating index '%s': cur to old rename failed: %s", sPrereading, pOld->GetLastError().cstr() );
20331 
20332 		} else
20333 		{
20334 			// FIXME! at this point there's no cur lock file; ie. potential race
20335 			if ( !bReEnable && !pNew->Rename ( tServed.m_sIndexPath.cstr() ) )
20336 			{
20337 				sphWarning ( "rotating index '%s': new to cur rename failed: %s", sPrereading, pNew->GetLastError().cstr() );
20338 				if ( !tServed.m_bOnlyNew && !pOld->Rename ( tServed.m_sIndexPath.cstr() ) )
20339 				{
20340 					sphWarning ( "rotating index '%s': old to cur rename failed: %s; INDEX UNUSABLE", sPrereading, pOld->GetLastError().cstr() );
20341 					tServed.m_bEnabled = false;
20342 				}
20343 			} else
20344 			{
20345 				// all went fine; swap them
20346 				g_pPrereading->m_iTID = tServed.m_pIndex->m_iTID;
20347 
20348 				if ( !g_pPrereading->GetTokenizer () )
20349 					g_pPrereading->SetTokenizer ( tServed.m_pIndex->LeakTokenizer () );
20350 
20351 				if ( !g_pPrereading->GetDictionary () )
20352 					g_pPrereading->SetDictionary ( tServed.m_pIndex->LeakDictionary () );
20353 
20354 				Swap ( tServed.m_pIndex, g_pPrereading );
20355 				tServed.m_bEnabled = true;
20356 
20357 				const char * sAction = "rotating";
20358 
20359 				// rename current MVP to old one to unlink it
20360 				if ( !bReEnable )
20361 					TryRename ( sPrereading, tServed.m_sIndexPath.cstr(), sphGetExt ( SPH_EXT_TYPE_CUR, SPH_EXT_MVP ), sphGetExt ( SPH_EXT_TYPE_OLD, SPH_EXT_MVP ), sAction, false, false );
20362 
20363 				// unlink .old
20364 				if ( !tServed.m_bOnlyNew )
20365 				{
20366 					sphUnlinkIndex ( sOld, false );
20367 				}
20368 
20369 				tServed.m_bOnlyNew = false;
20370 				sphInfo ( "rotating index '%s': success", sPrereading );
20371 			}
20372 		}
20373 
20374 	} else
20375 	{
20376 		if ( tPipe.IsError() )
20377 			sphWarning ( "rotating index '%s': pipe read failed", sPrereading );
20378 		else
20379 			sphWarning ( "rotating index '%s': preread failure reported", sPrereading );
20380 	}
20381 
20382 	// in any case, buffer index should now be deallocated
20383 	g_pPrereading->Dealloc ();
20384 	g_pPrereading->Unlock ();
20385 
20386 	// work next one
20387 	SeamlessForkPrereader ();
20388 }
20389 
20390 
20391 /// check if there are any notifications from the children and handle them
CheckPipes()20392 void CheckPipes ()
20393 {
20394 	ARRAY_FOREACH ( i, g_dPipes )
20395 	{
20396 		// try to get status code
20397 		DWORD uStatus;
20398 		int iRes = ::read ( g_dPipes[i].m_iFD, &uStatus, sizeof(DWORD) );
20399 
20400 		// no data yet?
20401 		if ( iRes==-1 && errno==EAGAIN )
20402 			continue;
20403 
20404 		// either if there's eof, or error, or valid data - this pipe is over
20405 		PipeReader_t tPipe ( g_dPipes[i].m_iFD );
20406 		int iHandler = g_dPipes[i].m_iHandler;
20407 		g_dPipes.Remove ( i-- );
20408 
20409 		// check for eof/error
20410 		bool bFailure = false;
20411 		if ( iRes!=sizeof(DWORD) )
20412 		{
20413 			bFailure = true;
20414 
20415 			if ( iHandler<0 )
20416 				continue; // no handler; we're not expecting anything
20417 
20418 			if ( iRes!=0 || iHandler>=0 )
20419 				sphWarning ( "pipe status read failed (handler=%d)", iHandler );
20420 		}
20421 
20422 		// check for handler/status mismatch
20423 		if ( !bFailure && ( iHandler>=0 && (int)uStatus!=iHandler ) )
20424 		{
20425 			bFailure = true;
20426 			sphWarning ( "INTERNAL ERROR: pipe status mismatch (handler=%d, status=%d)", iHandler, uStatus );
20427 		}
20428 
20429 		// check for handler promotion (ie: we did not expect anything particular, but something happened anyway)
20430 		if ( !bFailure && iHandler<0 )
20431 			iHandler = (int)uStatus;
20432 
20433 		// run the proper handler
20434 		switch ( iHandler )
20435 		{
20436 			case SPH_PIPE_PREREAD:			HandlePipePreread ( tPipe, bFailure ); break;
20437 			default:						if ( !bFailure ) sphWarning ( "INTERNAL ERROR: unknown pipe handler (handler=%d, status=%d)", iHandler, uStatus ); break;
20438 		}
20439 	}
20440 }
20441 
ConfigureTemplateIndex(ServedDesc_t & tIdx,const CSphConfigSection & hIndex)20442 void ConfigureTemplateIndex ( ServedDesc_t & tIdx, const CSphConfigSection & hIndex )
20443 {
20444 	tIdx.m_bExpand = ( hIndex.GetInt ( "expand_keywords", 0 )!=0 );
20445 }
20446 
ConfigureLocalIndex(ServedDesc_t & tIdx,const CSphConfigSection & hIndex)20447 void ConfigureLocalIndex ( ServedDesc_t & tIdx, const CSphConfigSection & hIndex )
20448 {
20449 	tIdx.m_bMlock = ( hIndex.GetInt ( "mlock", 0 )!=0 ) && !g_bOptNoLock;
20450 	tIdx.m_bExpand = ( hIndex.GetInt ( "expand_keywords", 0 )!=0 );
20451 	tIdx.m_bPreopen = ( hIndex.GetInt ( "preopen", 0 )!=0 );
20452 	tIdx.m_sGlobalIDFPath = hIndex.GetStr ( "global_idf" );
20453 	tIdx.m_bOnDiskAttrs = ( hIndex.GetInt ( "ondisk_attrs", 0 )==1 );
20454 	tIdx.m_bOnDiskPools = ( strcmp ( hIndex.GetStr ( "ondisk_attrs", "" ), "pool" )==0 );
20455 }
20456 
20457 
20458 /// this gets called for every new physical index
20459 /// that is, local and RT indexes, but not distributed once
PrereadNewIndex(ServedDesc_t & tIdx,const CSphConfigSection & hIndex,const char * szIndexName)20460 bool PrereadNewIndex ( ServedDesc_t & tIdx, const CSphConfigSection & hIndex, const char * szIndexName )
20461 {
20462 	CSphString sWarning;
20463 
20464 	bool bOk = tIdx.m_pIndex->Prealloc ( tIdx.m_bMlock, g_bStripPath, sWarning );
20465 	if ( bOk )
20466 		bOk = tIdx.m_pIndex->Preread();
20467 	if ( !bOk )
20468 	{
20469 		sphWarning ( "index '%s': preload: %s; NOT SERVING", szIndexName, tIdx.m_pIndex->GetLastError().cstr() );
20470 		return false;
20471 	}
20472 
20473 	if ( !sWarning.IsEmpty() )
20474 		sphWarning ( "index '%s': %s", szIndexName, sWarning.cstr() );
20475 
20476 	// tricky bit
20477 	// fixup was initially intended for (very old) index formats that did not store dict/tokenizer settings
20478 	// however currently it also ends up configuring dict/tokenizer for fresh RT indexes!
20479 	// (and for existing RT indexes, settings get loaded during the Prealloc() call)
20480 	CSphString sError;
20481 	if ( !sphFixupIndexSettings ( tIdx.m_pIndex, hIndex, sError ) )
20482 	{
20483 		sphWarning ( "index '%s': %s - NOT SERVING", szIndexName, sError.cstr() );
20484 		return false;
20485 	}
20486 
20487 	// try to lock it
20488 	if ( !g_bOptNoLock && !tIdx.m_pIndex->Lock() )
20489 	{
20490 		sphWarning ( "index '%s': lock: %s; NOT SERVING", szIndexName, tIdx.m_pIndex->GetLastError().cstr() );
20491 		return false;
20492 	}
20493 
20494 	CSphIndexStatus tStatus;
20495 	tIdx.m_pIndex->GetStatus ( &tStatus );
20496 	tIdx.m_iMass = CalculateMass ( tStatus );
20497 	return true;
20498 }
20499 
ValidateAgentDesc(MetaAgentDesc_t & tAgent,const CSphVariant * pLine,const char * szIndexName)20500 bool ValidateAgentDesc ( MetaAgentDesc_t & tAgent, const CSphVariant * pLine, const char * szIndexName )
20501 {
20502 	AgentDesc_t * pAgent = tAgent.LastAgent();
20503 	assert ( pAgent );
20504 
20505 	// lookup address (if needed)
20506 	if ( pAgent->m_iFamily==AF_INET )
20507 	{
20508 		if ( pAgent->m_sHost.IsEmpty() )
20509 		{
20510 			sphWarning ( "index '%s': agent '%s': invalid host name 'empty' - SKIPPING AGENT",
20511 						szIndexName, pLine->cstr() );
20512 			return false;
20513 		}
20514 
20515 		pAgent->m_uAddr = sphGetAddress ( pAgent->m_sHost.cstr() );
20516 		if ( pAgent->m_uAddr==0 )
20517 		{
20518 			sphWarning ( "index '%s': agent '%s': failed to lookup host name '%s' (error=%s) - SKIPPING AGENT",
20519 				szIndexName, pLine->cstr(), pAgent->m_sHost.cstr(), sphSockError() );
20520 			return false;
20521 		}
20522 	}
20523 
20524 	// hash for dashboard
20525 	CSphString sHashKey = pAgent->GetName();
20526 
20527 	// allocate stats slot
20528 	// let us cheat and also allocate the dashboard slot under the same lock
20529 	if ( g_pStats )
20530 	{
20531 		g_tStatsMutex.Lock();
20532 		pAgent->m_iStatsIndex = g_pStats->m_dAgentStats.AllocItem();
20533 		if ( pAgent->m_iStatsIndex<0 )
20534 			sphWarning ( "index '%s': agent '%s': failed to allocate slot for stats%s",
20535 				szIndexName, pLine->cstr(), ( tAgent.IsHA() ? ", HA might be wrong" : "" ) );
20536 
20537 		if ( g_pStats->m_hDashBoard.Exists ( sHashKey ) )
20538 		{
20539 			pAgent->m_iDashIndex = g_pStats->m_hDashBoard[sHashKey];
20540 			g_pStats->m_dDashboard.m_dItemStats[pAgent->m_iDashIndex].m_iRefCount++;
20541 		} else
20542 		{
20543 			pAgent->m_iDashIndex = g_pStats->m_dDashboard.AllocItem();
20544 			if ( pAgent->m_iDashIndex<0 )
20545 			{
20546 				sphWarning ( "index '%s': agent '%s': failed to allocate slot for stat-dashboard%s",
20547 				szIndexName, pLine->cstr(), ( tAgent.IsHA() ? ", HA might be wrong" : "" ) );
20548 			} else
20549 			{
20550 				g_pStats->m_dDashboard.m_dItemStats[pAgent->m_iDashIndex].Init ( pAgent );
20551 				g_pStats->m_hDashBoard.Add ( pAgent->m_iDashIndex, sHashKey );
20552 			}
20553 		}
20554 
20555 		g_tStatsMutex.Unlock();
20556 	}
20557 
20558 	// for now just convert all 'host mirrors' (i.e. agents without indices) into 'index mirrors'
20559 	if ( tAgent.GetLength()>1 && !pAgent->m_sIndexes.IsEmpty() )
20560 	{
20561 		for ( int i=tAgent.GetLength()-2; i>=0; --i )
20562 		{
20563 			AgentDesc_t * pMyAgent = tAgent.GetAgent(i);
20564 			if ( pMyAgent->m_sIndexes.IsEmpty() )
20565 				pMyAgent->m_sIndexes = pAgent->m_sIndexes;
20566 			else
20567 				break;
20568 		}
20569 	}
20570 	return true;
20571 }
20572 
20573 #define sphStrMatchStatic(_str, _cstr) ( strncmp ( _str, _cstr, sizeof(_str)-1 )==0 )
20574 
20575 
ParseStrategyHA(const char * sName,HAStrategies_e & eStrategy)20576 static bool ParseStrategyHA ( const char * sName, HAStrategies_e & eStrategy )
20577 {
20578 	if ( sphStrMatchStatic ( "random", sName ) )
20579 		eStrategy = HA_RANDOM;
20580 	else if ( sphStrMatchStatic ( "roundrobin", sName ) )
20581 		eStrategy = HA_ROUNDROBIN;
20582 	else if ( sphStrMatchStatic ( "nodeads", sName ) )
20583 		eStrategy = HA_AVOIDDEAD;
20584 	else if ( sphStrMatchStatic ( "noerrors", sName ) )
20585 		eStrategy = HA_AVOIDERRORS;
20586 	else
20587 		return false;
20588 
20589 	return true;
20590 }
20591 
IsAgentDelimiter(char c)20592 static bool IsAgentDelimiter ( char c )
20593 {
20594 	return c=='|' || c=='[' || c==']';
20595 }
20596 
20597 struct AgentOptions_t
20598 {
20599 	bool m_bBlackhole;
20600 	bool m_bPersistent;
20601 	HAStrategies_e m_eStrategy;
20602 };
20603 
20604 enum AgentParse_e { apInHost, apInPort, apStartIndexList, apIndexList, apOptions, apDone };
20605 
ConfigureAgent(MetaAgentDesc_t & tAgent,const CSphVariant * pAgent,const char * szIndexName,AgentOptions_t tDesc)20606 bool ConfigureAgent ( MetaAgentDesc_t & tAgent, const CSphVariant * pAgent, const char * szIndexName, AgentOptions_t tDesc )
20607 {
20608 	AgentDesc_t * pCurrent = tAgent.NewAgent();
20609 
20610 	// extract host name or path
20611 	const char * p = pAgent->cstr();
20612 	while ( *p && isspace ( *p ) )
20613 		p++;
20614 	AgentParse_e eState = apDone;
20615 	// might be agent options at head
20616 	if ( *p )
20617 	{
20618 		if ( *p=='[' )
20619 		{
20620 			eState = apOptions;
20621 			p += 1;
20622 		} else
20623 			eState = apInHost;
20624 	}
20625 	const char * pAnchor = p;
20626 
20627 	while ( eState!=apDone )
20628 	{
20629 		switch ( eState )
20630 		{
20631 		case apInHost:
20632 			{
20633 				if ( !*p )
20634 				{
20635 					eState = apDone;
20636 					break;
20637 				}
20638 
20639 				if ( sphIsAlpha(*p) || *p=='.' || *p=='-' || *p=='/' )
20640 					break;
20641 
20642 				if ( p==pAnchor )
20643 				{
20644 					sphWarning ( "index '%s': agent '%s': host name or path expected - SKIPPING AGENT",
20645 						szIndexName, pAnchor );
20646 					return false;
20647 				}
20648 				if ( *p!=':' )
20649 				{
20650 					sphWarning ( "index '%s': agent '%s': colon expected near '%s' - SKIPPING AGENT",
20651 						szIndexName, pAgent->cstr(), p );
20652 					return false;
20653 				}
20654 				CSphString sSub = pAgent->strval().SubString ( pAnchor-pAgent->cstr(), p-pAnchor );
20655 				if ( sSub.cstr()[0]=='/' )
20656 				{
20657 #if USE_WINDOWS
20658 					sphWarning ( "index '%s': agent '%s': UNIX sockets are not supported on Windows - SKIPPING AGENT",
20659 						szIndexName, pAgent->cstr() );
20660 					return false;
20661 #else
20662 					if ( strlen ( sSub.cstr() ) + 1 > sizeof(((struct sockaddr_un *)0)->sun_path) )
20663 					{
20664 						sphWarning ( "index '%s': agent '%s': UNIX socket path is too long - SKIPPING AGENT",
20665 							szIndexName, pAgent->cstr() );
20666 						return false;
20667 					}
20668 
20669 					pCurrent->m_iFamily = AF_UNIX;
20670 					pCurrent->m_sPath = sSub;
20671 					p--;
20672 #endif
20673 				} else
20674 				{
20675 					pCurrent->m_iFamily = AF_INET;
20676 					pCurrent->m_sHost = sSub;
20677 				}
20678 				eState = apInPort;
20679 				pAnchor = p+1;
20680 				break;
20681 			}
20682 		case apInPort:
20683 			{
20684 				if ( isdigit(*p) )
20685 					break;
20686 
20687 #if !USE_WINDOWS
20688 				if ( pCurrent->m_iFamily!=AF_UNIX )
20689 				{
20690 #endif
20691 					if ( p==pAnchor )
20692 					{
20693 						sphWarning ( "index '%s': agent '%s': port number expected near '%s' - SKIPPING AGENT",
20694 							szIndexName, pAgent->cstr(), p );
20695 						return false;
20696 					}
20697 					pCurrent->m_iPort = atoi ( pAnchor );
20698 
20699 					if ( !IsPortInRange ( pCurrent->m_iPort ) )
20700 					{
20701 						sphWarning ( "index '%s': agent '%s': invalid port number near '%s' - SKIPPING AGENT",
20702 							szIndexName, pAgent->cstr(), p );
20703 						return false;
20704 					}
20705 #if !USE_WINDOWS
20706 				}
20707 #endif
20708 
20709 				if ( IsAgentDelimiter ( *p ) )
20710 				{
20711 					eState = ( *p=='|' ? apInHost : apOptions );
20712 					pAnchor = p+1;
20713 					if ( !ValidateAgentDesc ( tAgent, pAgent, szIndexName ) )
20714 						return false;
20715 					pCurrent = tAgent.NewAgent();
20716 					break;
20717 				}
20718 
20719 				if ( *p!=':' )
20720 				{
20721 					sphWarning ( "index '%s': agent '%s': colon expected near '%s' - SKIPPING AGENT",
20722 						szIndexName, pAgent->cstr(), p );
20723 					return false;
20724 				}
20725 
20726 				eState = apStartIndexList;
20727 				pAnchor = p+1;
20728 				break;
20729 			}
20730 		case apStartIndexList:
20731 			if ( isspace ( *p ) )
20732 				break;
20733 
20734 			pAnchor = p;
20735 			eState = apIndexList;
20736 			// no break;
20737 		case apIndexList:
20738 			{
20739 				if ( sphIsAlpha(*p) || isspace(*p) || *p==',' )
20740 					break;
20741 
20742 				CSphString sIndexes = pAgent->strval().SubString ( pAnchor-pAgent->cstr(), p-pAnchor );
20743 
20744 				if ( *p && !IsAgentDelimiter ( *p ) )
20745 				{
20746 					sphWarning ( "index '%s': agent '%s': index list expected near '%s' - SKIPPING AGENT",
20747 						szIndexName, pAgent->cstr(), p );
20748 					return false;
20749 				}
20750 				pCurrent->m_sIndexes = sIndexes;
20751 
20752 				if ( IsAgentDelimiter ( *p ) )
20753 				{
20754 					if ( *p=='|' )
20755 					{
20756 						eState = apInHost;
20757 						if ( !ValidateAgentDesc ( tAgent, pAgent, szIndexName ) )
20758 							return false;
20759 						pCurrent = tAgent.NewAgent();
20760 					} else
20761 						eState = apOptions;
20762 
20763 					pAnchor = p+1;
20764 					break;
20765 				} else
20766 					eState = apDone;
20767 			}
20768 			break;
20769 
20770 		case apOptions:
20771 			{
20772 				const char * sOptName = NULL;
20773 				const char * sOptValue = NULL;
20774 				bool bGotEq = false;
20775 				while ( *p )
20776 				{
20777 					bool bNextOpt = ( *p==',' );
20778 					bool bNextAgent = IsAgentDelimiter ( *p );
20779 					bGotEq |= ( *p=='=' );
20780 
20781 					if ( bNextOpt || bNextAgent )
20782 					{
20783 						if ( sOptName && sOptValue )
20784 						{
20785 							bool bParsed = false;
20786 							if ( sphStrMatchStatic ( "conn", sOptName ) )
20787 							{
20788 								if ( sphStrMatchStatic ( "pconn", sOptValue ) || sphStrMatchStatic ( "persistent", sOptValue ) )
20789 								{
20790 									tDesc.m_bPersistent = true;
20791 									bParsed = true;
20792 								}
20793 							} else if ( sphStrMatchStatic ( "ha_strategy", sOptName ) )
20794 							{
20795 								bParsed = ParseStrategyHA ( sOptValue, tDesc.m_eStrategy );
20796 							} else if ( sphStrMatchStatic ( "blackhole", sOptName ) )
20797 							{
20798 								tDesc.m_bBlackhole = ( atoi ( sOptValue )!=0 );
20799 								bParsed = true;
20800 							}
20801 
20802 							if ( !bParsed )
20803 							{
20804 								CSphString sInvalid;
20805 								sInvalid.SetBinary ( sOptName, p-sOptName );
20806 								sphWarning ( "index '%s': agent '%s': unknown agent option '%s' ", szIndexName, pAgent->cstr(), sInvalid.cstr() );
20807 							}
20808 						}
20809 
20810 						sOptName = sOptValue = NULL;
20811 						bGotEq = false;
20812 						if ( bNextAgent )
20813 							break;
20814 					}
20815 
20816 					if ( sphIsAlpha ( *p ) )
20817 					{
20818 						if ( !sOptName )
20819 							sOptName = p;
20820 						else if ( bGotEq && !sOptValue )
20821 							sOptValue = p;
20822 					}
20823 
20824 					p++;
20825 				}
20826 
20827 				if ( IsAgentDelimiter ( *p ) )
20828 				{
20829 					eState = apInHost;
20830 					pAnchor = p+1;
20831 				} else
20832 					eState = apDone;
20833 			}
20834 			break;
20835 
20836 		case apDone:
20837 		default:
20838 			break;
20839 		} // switch (eState)
20840 		p++;
20841 	} // while (eState!=apDone)
20842 
20843 	bool bRes = ValidateAgentDesc ( tAgent, pAgent, szIndexName );
20844 	tAgent.QueuePings();
20845 	if ( tDesc.m_bPersistent )
20846 		tAgent.SetPersistent();
20847 	if ( tDesc.m_bBlackhole )
20848 		tAgent.SetBlackhole();
20849 	tAgent.SetStrategy ( tDesc.m_eStrategy );
20850 
20851 	return bRes;
20852 }
20853 
20854 #undef sphStrMatchStatic
20855 
ConfigureDistributedIndex(DistributedIndex_t * pIdx,const char * szIndexName,const CSphConfigSection & hIndex)20856 static void ConfigureDistributedIndex ( DistributedIndex_t * pIdx, const char * szIndexName, const CSphConfigSection & hIndex )
20857 {
20858 	assert ( hIndex("type") && hIndex["type"]=="distributed" );
20859 	assert ( pIdx!=NULL );
20860 
20861 	DistributedIndex_t & tIdx = *pIdx;
20862 
20863 	bool bSetHA = false;
20864 	// configure ha_strategy
20865 	if ( hIndex("ha_strategy") )
20866 	{
20867 		bSetHA = ParseStrategyHA ( hIndex["ha_strategy"].cstr(), tIdx.m_eHaStrategy );
20868 		if ( !bSetHA )
20869 			sphWarning ( "index '%s': ha_strategy (%s) is unknown for me, will use random", szIndexName, hIndex["ha_strategy"].cstr() );
20870 	}
20871 
20872 	bool bEnablePersistentConns = ( g_eWorkers==MPM_THREADS && g_iPersistentPoolSize );
20873 	if ( hIndex ( "agent_persistent" ) && !bEnablePersistentConns )
20874 	{
20875 			sphWarning ( "index '%s': agent_persistent used, but no persistent_connections_limit defined. Fall back to non-persistent agent", szIndexName );
20876 			bEnablePersistentConns = false;
20877 	}
20878 
20879 	// add local agents
20880 	CSphVector<CSphString> dLocs;
20881 	for ( CSphVariant * pLocal = hIndex("local"); pLocal; pLocal = pLocal->m_pNext )
20882 	{
20883 		dLocs.Resize(0);
20884 		sphSplit ( dLocs, pLocal->cstr(), " \t," );
20885 		ARRAY_FOREACH ( i, dLocs )
20886 		{
20887 			if ( !g_pLocalIndexes->Exists ( dLocs[i] ) )
20888 			{
20889 				sphWarning ( "index '%s': no such local index '%s', SKIPPED", szIndexName, dLocs[i].cstr() );
20890 				continue;
20891 			}
20892 			tIdx.m_dLocal.Add ( dLocs[i] );
20893 		}
20894 	}
20895 
20896 	AgentOptions_t tAgentOptions;
20897 	tAgentOptions.m_bBlackhole = false;
20898 	tAgentOptions.m_bPersistent = false;
20899 	tAgentOptions.m_eStrategy = tIdx.m_eHaStrategy;
20900 	// add remote agents
20901 	for ( CSphVariant * pAgent = hIndex("agent"); pAgent; pAgent = pAgent->m_pNext )
20902 	{
20903 		MetaAgentDesc_t& tAgent = tIdx.m_dAgents.Add();
20904 		if ( !ConfigureAgent ( tAgent, pAgent, szIndexName, tAgentOptions ) )
20905 			tIdx.m_dAgents.Pop();
20906 	}
20907 
20908 	// for now work with client persistent connections only on per-thread basis,
20909 	// to avoid locks, etc.
20910 	tAgentOptions.m_bBlackhole = false;
20911 	tAgentOptions.m_bPersistent = bEnablePersistentConns;
20912 	for ( CSphVariant * pAgent = hIndex("agent_persistent"); pAgent; pAgent = pAgent->m_pNext )
20913 	{
20914 		MetaAgentDesc_t& tAgent = tIdx.m_dAgents.Add ();
20915 		if ( !ConfigureAgent ( tAgent, pAgent, szIndexName, tAgentOptions ) )
20916 			tIdx.m_dAgents.Pop();
20917 	}
20918 
20919 	tAgentOptions.m_bBlackhole = true;
20920 	tAgentOptions.m_bPersistent = false;
20921 	for ( CSphVariant * pAgent = hIndex("agent_blackhole"); pAgent; pAgent = pAgent->m_pNext )
20922 	{
20923 		MetaAgentDesc_t& tAgent = tIdx.m_dAgents.Add ();
20924 		if ( !ConfigureAgent ( tAgent, pAgent, szIndexName, tAgentOptions ) )
20925 			tIdx.m_dAgents.Pop();
20926 	}
20927 
20928 	// configure options
20929 	if ( hIndex("agent_connect_timeout") )
20930 	{
20931 		if ( hIndex["agent_connect_timeout"].intval()<=0 )
20932 			sphWarning ( "index '%s': agent_connect_timeout must be positive, ignored", szIndexName );
20933 		else
20934 			tIdx.m_iAgentConnectTimeout = hIndex["agent_connect_timeout"].intval();
20935 	}
20936 
20937 	tIdx.m_bDivideRemoteRanges = hIndex.GetInt ( "divide_remote_ranges", 0 )!=0;
20938 
20939 	if ( hIndex("agent_query_timeout") )
20940 	{
20941 		if ( hIndex["agent_query_timeout"].intval()<=0 )
20942 			sphWarning ( "index '%s': agent_query_timeout must be positive, ignored", szIndexName );
20943 		else
20944 			tIdx.m_iAgentQueryTimeout = hIndex["agent_query_timeout"].intval();
20945 	}
20946 
20947 	bool bHaveHA = ARRAY_ANY ( bHaveHA, tIdx.m_dAgents, tIdx.m_dAgents[_any].IsHA() );
20948 
20949 	// configure ha_strategy
20950 	if ( bSetHA && !bHaveHA )
20951 		sphWarning ( "index '%s': ha_strategy defined, but no ha agents in the index", szIndexName );
20952 
20953 	tIdx.ShareHACounters();
20954 }
20955 
20956 
FreeAgentStats(DistributedIndex_t & tIndex)20957 void FreeAgentStats ( DistributedIndex_t & tIndex )
20958 {
20959 	if ( !g_pStats )
20960 		return;
20961 
20962 	g_tStatsMutex.Lock();
20963 	ARRAY_FOREACH ( i, tIndex.m_dAgents )
20964 		ARRAY_FOREACH ( j, tIndex.m_dAgents[i].GetAgents() )
20965 		{
20966 			const AgentDesc_t& dAgent = tIndex.m_dAgents[i].GetAgents()[j];
20967 			g_pStats->m_dAgentStats.FreeItem ( dAgent.m_iStatsIndex );
20968 
20969 			// now free the dashboard hosts also
20970 			HostDashboard_t& dDash = g_pStats->m_dDashboard.m_dItemStats[dAgent.m_iDashIndex];
20971 			dDash.m_iRefCount--;
20972 			assert ( dDash.m_iRefCount>=0 );
20973 			if ( dDash.m_iRefCount<=0 ) // no more agents use this host. Delete record.
20974 			{
20975 				g_pStats->m_dDashboard.FreeItem ( dAgent.m_iDashIndex );
20976 				g_pStats->m_hDashBoard.Delete ( dAgent.GetName() );
20977 			}
20978 		}
20979 	tIndex.RemoveHACounters();
20980 	g_tStatsMutex.Unlock();
20981 }
20982 
PreCreateTemplateIndex(ServedDesc_t & tServed,const CSphConfigSection &)20983 void PreCreateTemplateIndex ( ServedDesc_t & tServed, const CSphConfigSection & )
20984 {
20985 	tServed.m_pIndex = sphCreateIndexTemplate ( );
20986 	tServed.m_pIndex->m_bExpandKeywords = tServed.m_bExpand;
20987 	tServed.m_pIndex->m_iExpansionLimit = g_iExpansionLimit;
20988 	tServed.m_bEnabled = false;
20989 }
20990 
PreCreatePlainIndex(ServedDesc_t & tServed,const char * sName)20991 void PreCreatePlainIndex ( ServedDesc_t & tServed, const char * sName )
20992 {
20993 	tServed.m_pIndex = sphCreateIndexPhrase ( sName, tServed.m_sIndexPath.cstr() );
20994 	tServed.m_pIndex->m_bExpandKeywords = tServed.m_bExpand;
20995 	tServed.m_pIndex->m_iExpansionLimit = g_iExpansionLimit;
20996 	tServed.m_pIndex->SetPreopen ( tServed.m_bPreopen || g_bPreopenIndexes );
20997 	tServed.m_pIndex->SetGlobalIDFPath ( tServed.m_sGlobalIDFPath );
20998 	SetEnableOndiskAttributes ( tServed, tServed.m_pIndex );
20999 	tServed.m_bEnabled = false;
21000 }
21001 
21002 
AddIndex(const char * szIndexName,const CSphConfigSection & hIndex)21003 ESphAddIndex AddIndex ( const char * szIndexName, const CSphConfigSection & hIndex )
21004 {
21005 	if ( hIndex("type") && hIndex["type"]=="distributed" )
21006 	{
21007 		///////////////////////////////
21008 		// configure distributed index
21009 		///////////////////////////////
21010 
21011 		DistributedIndex_t tIdx;
21012 		ConfigureDistributedIndex ( &tIdx, szIndexName, hIndex );
21013 
21014 		// finally, check and add distributed index to global table
21015 		if ( tIdx.m_dAgents.GetLength()==0 && tIdx.m_dLocal.GetLength()==0 )
21016 		{
21017 			FreeAgentStats ( tIdx );
21018 			sphWarning ( "index '%s': no valid local/remote indexes in distributed index - NOT SERVING", szIndexName );
21019 			return ADD_ERROR;
21020 
21021 		} else
21022 		{
21023 			g_tDistLock.Lock ();
21024 			if ( !g_hDistIndexes.Add ( tIdx, szIndexName ) )
21025 			{
21026 				g_tDistLock.Unlock ();
21027 				FreeAgentStats ( tIdx );
21028 				sphWarning ( "index '%s': duplicate name - NOT SERVING", szIndexName );
21029 				return ADD_ERROR;
21030 			}
21031 			g_tDistLock.Unlock ();
21032 		}
21033 
21034 		return ADD_DISTR;
21035 
21036 	} else if ( hIndex("type") && hIndex["type"]=="rt" )
21037 	{
21038 		////////////////////////////
21039 		// configure realtime index
21040 		////////////////////////////
21041 
21042 		if ( g_eWorkers!=MPM_THREADS )
21043 		{
21044 			sphWarning ( "index '%s': RT index requires workers=threads - NOT SERVING", szIndexName );
21045 			return ADD_ERROR;
21046 		}
21047 
21048 		CSphString sError;
21049 		CSphSchema tSchema ( szIndexName );
21050 		if ( !sphRTSchemaConfigure ( hIndex, &tSchema, &sError ) )
21051 		{
21052 			sphWarning ( "index '%s': %s - NOT SERVING", szIndexName, sError.cstr() );
21053 			return ADD_ERROR;
21054 		}
21055 
21056 		if ( !sError.IsEmpty() )
21057 			sphWarning ( "index '%s': %s", szIndexName, sError.cstr() );
21058 
21059 		// path
21060 		if ( !hIndex("path") )
21061 		{
21062 			sphWarning ( "index '%s': path must be specified - NOT SERVING", szIndexName );
21063 			return ADD_ERROR;
21064 		}
21065 
21066 		// pick config settings
21067 		// they should be overriden later by Preload() if needed
21068 		CSphIndexSettings tSettings;
21069 		if ( !sphConfIndex ( hIndex, tSettings, sError ) )
21070 		{
21071 			sphWarning ( "ERROR: index '%s': %s - NOT SERVING", szIndexName, sError.cstr() );
21072 			return ADD_ERROR;
21073 		}
21074 
21075 		int iIndexSP = hIndex.GetInt ( "index_sp" );
21076 		const char * sIndexZones = hIndex.GetStr ( "index_zones", "" );
21077 		bool bHasStripEnabled ( hIndex.GetInt ( "html_strip" )!=0 );
21078 		if ( ( iIndexSP!=0 || ( *sIndexZones ) ) && !bHasStripEnabled )
21079 		{
21080 			// SENTENCE indexing w\o stripper is valid combination
21081 			if ( *sIndexZones )
21082 			{
21083 				sphWarning ( "ERROR: index '%s': has index_sp=%d, index_zones='%s' but disabled html_strip - NOT SERVING",
21084 					szIndexName, iIndexSP, sIndexZones );
21085 				return ADD_ERROR;
21086 			} else
21087 			{
21088 				sphWarning ( "index '%s': has index_sp=%d but disabled html_strip - PARAGRAPH unavailable",
21089 					szIndexName, iIndexSP );
21090 			}
21091 		}
21092 
21093 		// RAM chunk size
21094 		int64_t iRamSize = hIndex.GetSize64 ( "rt_mem_limit", 128*1024*1024 );
21095 		if ( iRamSize<128*1024 )
21096 		{
21097 			sphWarning ( "index '%s': rt_mem_limit extremely low, using 128K instead", szIndexName );
21098 			iRamSize = 128*1024;
21099 		} else if ( iRamSize<8*1024*1024 )
21100 			sphWarning ( "index '%s': rt_mem_limit very low (under 8 MB)", szIndexName );
21101 
21102 		// upgrading schema to store field lengths
21103 		if ( tSettings.m_bIndexFieldLens )
21104 			if ( !AddFieldLens ( tSchema, false, sError ) )
21105 				{
21106 					sphWarning ( "index '%s': failed to create field lengths attributes: %s", szIndexName, sError.cstr() );
21107 					return ADD_ERROR;
21108 				}
21109 
21110 		// index
21111 		ServedDesc_t tIdx;
21112 		bool bWordDict = strcmp ( hIndex.GetStr ( "dict", "keywords" ), "keywords" )==0;
21113 		if ( !bWordDict )
21114 			sphWarning ( "dict=crc deprecated, use dict=keywords instead" );
21115 		if ( bWordDict && ( tSettings.m_dPrefixFields.GetLength() || tSettings.m_dInfixFields.GetLength() ) )
21116 			sphWarning ( "WARNING: index '%s': prefix_fields and infix_fields has no effect with dict=keywords, ignoring\n", szIndexName );
21117 		tIdx.m_pIndex = sphCreateIndexRT ( tSchema, szIndexName, iRamSize, hIndex["path"].cstr(), bWordDict );
21118 		tIdx.m_bEnabled = false;
21119 		tIdx.m_sIndexPath = hIndex["path"].strval();
21120 		tIdx.m_bRT = true;
21121 
21122 		ConfigureLocalIndex ( tIdx, hIndex );
21123 		tIdx.m_pIndex->m_bExpandKeywords = tIdx.m_bExpand;
21124 		tIdx.m_pIndex->m_iExpansionLimit = g_iExpansionLimit;
21125 		tIdx.m_pIndex->SetPreopen ( tIdx.m_bPreopen || g_bPreopenIndexes );
21126 		tIdx.m_pIndex->SetGlobalIDFPath ( tIdx.m_sGlobalIDFPath );
21127 		SetEnableOndiskAttributes ( tIdx, tIdx.m_pIndex );
21128 
21129 		tIdx.m_pIndex->Setup ( tSettings );
21130 		tIdx.m_pIndex->SetCacheSize ( g_iMaxCachedDocs, g_iMaxCachedHits );
21131 
21132 		CSphIndexStatus tStatus;
21133 		tIdx.m_pIndex->GetStatus ( &tStatus );
21134 		tIdx.m_iMass = CalculateMass ( tStatus );
21135 
21136 		// hash it
21137 		if ( !g_pLocalIndexes->Add ( tIdx, szIndexName ) )
21138 		{
21139 			sphWarning ( "INTERNAL ERROR: index '%s': hash add failed - NOT SERVING", szIndexName );
21140 			return ADD_ERROR;
21141 		}
21142 
21143 		// leak pointer, so it's destructor won't delete it
21144 		tIdx.m_pIndex = NULL;
21145 
21146 		return ADD_RT;
21147 
21148 	} else if ( !hIndex("type") || hIndex["type"]=="plain" )
21149 	{
21150 		/////////////////////////
21151 		// configure local index
21152 		/////////////////////////
21153 
21154 		ServedDesc_t tIdx;
21155 
21156 		// check path
21157 		if ( !hIndex.Exists ( "path" ) )
21158 		{
21159 			sphWarning ( "index '%s': key 'path' not found - NOT SERVING", szIndexName );
21160 			return ADD_ERROR;
21161 		}
21162 
21163 		// check name
21164 		if ( g_pLocalIndexes->Exists ( szIndexName ) )
21165 		{
21166 			sphWarning ( "index '%s': duplicate name - NOT SERVING", szIndexName );
21167 			return ADD_ERROR;
21168 		}
21169 
21170 		// configure memlocking, star
21171 		ConfigureLocalIndex ( tIdx, hIndex );
21172 
21173 		// try to create index
21174 		tIdx.m_sIndexPath = hIndex["path"].strval();
21175 		PreCreatePlainIndex ( tIdx, szIndexName );
21176 		tIdx.m_pIndex->SetCacheSize ( g_iMaxCachedDocs, g_iMaxCachedHits );
21177 		CSphIndexStatus tStatus;
21178 		tIdx.m_pIndex->GetStatus ( &tStatus );
21179 		tIdx.m_iMass = CalculateMass ( tStatus );
21180 
21181 		// done
21182 		if ( !g_pLocalIndexes->Add ( tIdx, szIndexName ) )
21183 		{
21184 			sphWarning ( "INTERNAL ERROR: index '%s': hash add failed - NOT SERVING", szIndexName );
21185 			return ADD_ERROR;
21186 		}
21187 
21188 		// leak pointer, so it's destructor won't delete it
21189 		tIdx.m_pIndex = NULL;
21190 
21191 		return ADD_LOCAL;
21192 
21193 	} else if ( hIndex["type"]=="template" )
21194 	{
21195 		/////////////////////////
21196 		// configure template index - just tokenizer and common settings
21197 		/////////////////////////
21198 
21199 		ServedDesc_t tIdx;
21200 
21201 		// check name
21202 		if ( g_pTemplateIndexes->Exists ( szIndexName ) )
21203 		{
21204 			sphWarning ( "index '%s': duplicate name - NOT SERVING", szIndexName );
21205 			return ADD_ERROR;
21206 		}
21207 
21208 		// configure memlocking, star
21209 		ConfigureTemplateIndex ( tIdx, hIndex );
21210 
21211 		// try to create index
21212 		PreCreateTemplateIndex ( tIdx, hIndex );
21213 		tIdx.m_bEnabled = true;
21214 
21215 		CSphIndexSettings s;
21216 		CSphString sError;
21217 		if ( !sphConfIndex ( hIndex, s, sError ) )
21218 		{
21219 			sphWarning ( "failed to configure index %s: %s", szIndexName, sError.cstr() );
21220 			return ADD_ERROR;
21221 		}
21222 		tIdx.m_pIndex->Setup(s);
21223 
21224 		if ( !sphFixupIndexSettings ( tIdx.m_pIndex, hIndex, sError ) )
21225 		{
21226 			sphWarning ( "index '%s': %s - NOT SERVING", szIndexName, sError.cstr() );
21227 			return ADD_ERROR;
21228 		}
21229 
21230 		CSphIndexStatus tStatus;
21231 		tIdx.m_pIndex->GetStatus ( &tStatus );
21232 		tIdx.m_iMass = CalculateMass ( tStatus );
21233 
21234 		// done
21235 		if ( !g_pTemplateIndexes->Add ( tIdx, szIndexName ) )
21236 		{
21237 			sphWarning ( "INTERNAL ERROR: index '%s': hash add failed - NOT SERVING", szIndexName );
21238 			return ADD_ERROR;
21239 		}
21240 
21241 		// leak pointer, so it's destructor won't delete it
21242 		tIdx.m_pIndex = NULL;
21243 
21244 		return ADD_TMPL;
21245 
21246 	} else
21247 	{
21248 		sphWarning ( "index '%s': unknown type '%s' - NOT SERVING", szIndexName, hIndex["type"].cstr() );
21249 		return ADD_ERROR;
21250 	}
21251 }
21252 
21253 
CheckConfigChanges()21254 bool CheckConfigChanges ()
21255 {
21256 	struct stat tStat;
21257 	memset ( &tStat, 0, sizeof ( tStat ) );
21258 	if ( stat ( g_sConfigFile.cstr (), &tStat ) < 0 )
21259 		memset ( &tStat, 0, sizeof ( tStat ) );
21260 
21261 	DWORD uCRC32 = 0;
21262 
21263 #if !USE_WINDOWS
21264 	char sBuf [ 8192 ];
21265 	FILE * fp = NULL;
21266 
21267 	fp = fopen ( g_sConfigFile.cstr (), "rb" );
21268 	if ( !fp )
21269 		return true;
21270 	bool bGotLine = fgets ( sBuf, sizeof(sBuf), fp );
21271 	fclose ( fp );
21272 	if ( !bGotLine )
21273 		return true;
21274 
21275 	char * p = sBuf;
21276 	while ( isspace(*p) )
21277 		p++;
21278 	if ( p[0]=='#' && p[1]=='!' )
21279 	{
21280 		p += 2;
21281 
21282 		CSphVector<char> dContent;
21283 		char sError [ 1024 ];
21284 		if ( !TryToExec ( p, g_sConfigFile.cstr(), dContent, sError, sizeof(sError) ) )
21285 			return true;
21286 
21287 		uCRC32 = sphCRC32 ( dContent.Begin(), dContent.GetLength() );
21288 	} else
21289 		sphCalcFileCRC32 ( g_sConfigFile.cstr (), uCRC32 );
21290 #else
21291 	sphCalcFileCRC32 ( g_sConfigFile.cstr (), uCRC32 );
21292 #endif
21293 
21294 	if ( g_uCfgCRC32==uCRC32 && tStat.st_size==g_tCfgStat.st_size
21295 		&& tStat.st_mtime==g_tCfgStat.st_mtime && tStat.st_ctime==g_tCfgStat.st_ctime )
21296 			return false;
21297 
21298 	g_uCfgCRC32 = uCRC32;
21299 	g_tCfgStat = tStat;
21300 
21301 	return true;
21302 }
21303 
InitPersistentPool()21304 void InitPersistentPool()
21305 {
21306 	if ( g_eWorkers==MPM_THREADS && g_iPersistentPoolSize )
21307 	{
21308 		// always close all persistent connections before (re)calculation.
21309 		CSphScopedLock<StaticThreadsOnlyMutex_t> tLock ( g_tPersLock );
21310 		ARRAY_FOREACH ( i, g_dPersistentConnections )
21311 		{
21312 			if ( ( i % g_iPersistentPoolSize ) && g_dPersistentConnections[i]>=0 )
21313 				sphSockClose ( g_dPersistentConnections[i] );
21314 			g_dPersistentConnections[i] = -1;
21315 		}
21316 
21317 		// Set global pools for every uniq persistent host (addr:port or socket).
21318 		// 1-st host pooled at g_dPersistentConnections[0],
21319 		// 2-nd at g_dPersistentConnections[iStride],
21320 		// n-th at g_dPersistentConnections[iStride*(n-1)].(iStride==g_iPersistentPoolSize)
21321 		CSphOrderedHash < int, int, IdentityHash_fn, STATS_MAX_DASH > hPersCounter;
21322 		int iPoolSize = 0;
21323 		int iStride = g_iPersistentPoolSize + 1; // 1 int for # of free item, then the data
21324 		g_hDistIndexes.IterateStart ();
21325 		while ( g_hDistIndexes.IterateNext () )
21326 		{
21327 			DistributedIndex_t & tIndex = g_hDistIndexes.IterateGet ();
21328 			if ( tIndex.m_dAgents.GetLength() )
21329 				ARRAY_FOREACH ( i, tIndex.m_dAgents )
21330 				ARRAY_FOREACH ( j, tIndex.m_dAgents[i] )
21331 				if ( tIndex.m_dAgents[i].GetAgent(j)->m_bPersistent )
21332 				{
21333 					AgentDesc_t* pAgent = tIndex.m_dAgents[i].GetAgent(j);
21334 					if ( hPersCounter.Exists ( pAgent->m_iDashIndex ) )
21335 						// host already met. Copy existing offset
21336 						pAgent->m_dPersPool.Init ( hPersCounter[pAgent->m_iDashIndex] );
21337 					else
21338 					{
21339 						// New host. Allocate new stride.
21340 						pAgent->m_dPersPool.Init ( iPoolSize );
21341 						hPersCounter.Add ( iPoolSize, pAgent->m_iDashIndex );
21342 						iPoolSize += iStride;
21343 					}
21344 				}
21345 		}
21346 		g_dPersistentConnections.Resize ( iPoolSize );
21347 		ARRAY_FOREACH ( i, g_dPersistentConnections )
21348 			g_dPersistentConnections[i] = -1; // means "Not in use"
21349 	}
21350 }
21351 
ReloadIndexSettings(CSphConfigParser & tCP)21352 static void ReloadIndexSettings ( CSphConfigParser & tCP )
21353 {
21354 	if ( !tCP.ReParse ( g_sConfigFile.cstr () ) )
21355 	{
21356 		sphWarning ( "failed to parse config file '%s'; using previous settings", g_sConfigFile.cstr () );
21357 		return;
21358 	}
21359 
21360 	g_bDoDelete = false;
21361 
21362 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21363 		it.Get().m_bToDelete = true; ///< FIXME! What about write lock before doing this?
21364 
21365 	g_hDistIndexes.IterateStart ();
21366 	while ( g_hDistIndexes.IterateNext () )
21367 		g_hDistIndexes.IterateGet().m_bToDelete = true;
21368 
21369 	for ( IndexHashIterator_c it ( g_pTemplateIndexes ); it.Next(); )
21370 		it.Get().m_bToDelete = true;
21371 
21372 	int nTotalIndexes = g_pLocalIndexes->GetLength () + g_hDistIndexes.GetLength () + g_pTemplateIndexes->GetLength ();
21373 	int nChecked = 0;
21374 
21375 	if ( !tCP.m_tConf.Exists ( "index" ) )
21376 	{
21377 		g_bDoDelete |= ( nTotalIndexes>0 );
21378 		return;
21379 	}
21380 
21381 	const CSphConfig & hConf = tCP.m_tConf;
21382 	hConf["index"].IterateStart ();
21383 	while ( hConf["index"].IterateNext() )
21384 	{
21385 		const CSphConfigSection & hIndex = hConf["index"].IterateGet();
21386 		const char * sIndexName = hConf["index"].IterateGetKey().cstr();
21387 
21388 		if ( ServedIndex_t * pServedIndex = g_pLocalIndexes->GetWlockedEntry ( sIndexName ) )
21389 		{
21390 			ConfigureLocalIndex ( *pServedIndex, hIndex );
21391 			pServedIndex->m_bToDelete = false;
21392 			nChecked++;
21393 			pServedIndex->Unlock();
21394 
21395 		} else if ( g_hDistIndexes.Exists ( sIndexName ) && hIndex.Exists("type") && hIndex["type"]=="distributed" )
21396 		{
21397 			DistributedIndex_t tIdx;
21398 			ConfigureDistributedIndex ( &tIdx, sIndexName, hIndex );
21399 
21400 			// finally, check and add distributed index to global table
21401 			if ( tIdx.m_dAgents.GetLength()==0 && tIdx.m_dLocal.GetLength()==0 )
21402 			{
21403 				sphWarning ( "index '%s': no valid local/remote indexes in distributed index; using last valid definition", sIndexName );
21404 				g_hDistIndexes[sIndexName].m_bToDelete = false;
21405 
21406 			} else
21407 			{
21408 				g_tDistLock.Lock();
21409 				FreeAgentStats ( g_hDistIndexes[sIndexName] );
21410 				g_hDistIndexes[sIndexName] = tIdx;
21411 				g_tDistLock.Unlock();
21412 			}
21413 
21414 			nChecked++;
21415 
21416 		} else if ( ServedIndex_t * pTemplateIndex = g_pTemplateIndexes->GetWlockedEntry ( sIndexName ) )
21417 		{
21418 			ConfigureTemplateIndex ( *pTemplateIndex, hIndex );
21419 			pTemplateIndex->m_bToDelete = false;
21420 			nChecked++;
21421 			pTemplateIndex->Unlock();
21422 
21423 		} else // add new index
21424 		{
21425 			ESphAddIndex eType = AddIndex ( sIndexName, hIndex );
21426 			if ( eType==ADD_LOCAL )
21427 			{
21428 				if ( ServedIndex_t * pIndex = g_pLocalIndexes->GetWlockedEntry ( sIndexName ) )
21429 				{
21430 					pIndex->m_bOnlyNew = true;
21431 					pIndex->Unlock();
21432 				}
21433 			} else if ( eType==ADD_RT )
21434 			{
21435 				ServedIndex_t & tIndex = g_pLocalIndexes->GetUnlockedEntry ( sIndexName );
21436 
21437 				tIndex.m_bOnlyNew = false;
21438 				if ( PrereadNewIndex ( tIndex, hIndex, sIndexName ) )
21439 					tIndex.m_bEnabled = true;
21440 			}
21441 		}
21442 	}
21443 
21444 	InitPersistentPool();
21445 
21446 	if ( nChecked < nTotalIndexes )
21447 		g_bDoDelete = true;
21448 }
21449 
21450 
CheckDelete()21451 void CheckDelete ()
21452 {
21453 	if ( !g_bDoDelete )
21454 		return;
21455 
21456 	if ( g_dChildren.GetLength() )
21457 		return;
21458 
21459 	CSphVector<const CSphString *> dToDelete;
21460 	CSphVector<const CSphString *> dTmplToDelete;
21461 	CSphVector<const CSphString *> dDistToDelete;
21462 	dToDelete.Reserve ( 8 );
21463 	dTmplToDelete.Reserve ( 8 );
21464 	dDistToDelete.Reserve ( 8 );
21465 
21466 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21467 	{
21468 		ServedIndex_t & tIndex = it.Get();
21469 		if ( tIndex.m_bToDelete )
21470 			dToDelete.Add ( &it.GetKey() );
21471 	}
21472 
21473 	for ( IndexHashIterator_c it ( g_pTemplateIndexes ); it.Next(); )
21474 	{
21475 		ServedIndex_t & tIndex = it.Get();
21476 		if ( tIndex.m_bToDelete )
21477 			dTmplToDelete.Add ( &it.GetKey() );
21478 	}
21479 
21480 
21481 	g_hDistIndexes.IterateStart ();
21482 	while ( g_hDistIndexes.IterateNext () )
21483 	{
21484 		DistributedIndex_t & tIndex = g_hDistIndexes.IterateGet ();
21485 		if ( tIndex.m_bToDelete )
21486 			dDistToDelete.Add ( &g_hDistIndexes.IterateGetKey () );
21487 	}
21488 
21489 	ARRAY_FOREACH ( i, dToDelete )
21490 		g_pLocalIndexes->Delete ( *dToDelete[i] ); // should result in automatic CSphIndex::Unlock() via dtor call
21491 
21492 	ARRAY_FOREACH ( i, dTmplToDelete )
21493 		g_pTemplateIndexes->Delete ( *dTmplToDelete[i] ); // should result in automatic CSphIndex::Unlock() via dtor call
21494 
21495 	g_tDistLock.Lock();
21496 
21497 	ARRAY_FOREACH ( i, dDistToDelete )
21498 	{
21499 		FreeAgentStats ( g_hDistIndexes [ *dDistToDelete[i] ] );
21500 		g_hDistIndexes.Delete ( *dDistToDelete[i] );
21501 	}
21502 
21503 	g_tDistLock.Unlock();
21504 
21505 	g_bDoDelete = false;
21506 }
21507 
21508 
CheckRotateGlobalIDFs()21509 void CheckRotateGlobalIDFs ()
21510 {
21511 	CSphVector <CSphString> dFiles;
21512 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21513 	{
21514 		ServedIndex_t & tIndex = it.Get();
21515 		if ( tIndex.m_bEnabled && !tIndex.m_sGlobalIDFPath.IsEmpty() )
21516 			dFiles.Add ( tIndex.m_sGlobalIDFPath );
21517 	}
21518 	sphUpdateGlobalIDFs ( dFiles );
21519 }
21520 
21521 
RotationServiceThreadFunc(void *)21522 void RotationServiceThreadFunc ( void * )
21523 {
21524 	while ( !g_bShutdown )
21525 	{
21526 		if ( g_bInvokeRotationService )
21527 		{
21528 			CheckRotateGlobalIDFs ();
21529 			g_bInvokeRotationService = false;
21530 		}
21531 		sphSleepMsec ( 50 );
21532 	}
21533 }
21534 
21535 
CheckRotate()21536 void CheckRotate ()
21537 {
21538 	// do we need to rotate now?
21539 	if ( !g_iRotateCount )
21540 		return;
21541 
21542 	sphLogDebug ( "CheckRotate invoked" );
21543 
21544 	if ( g_eWorkers==MPM_PREFORK )
21545 		sphPluginReinit();
21546 
21547 	/////////////////////
21548 	// RAM-greedy rotate
21549 	/////////////////////
21550 
21551 	if ( !g_bSeamlessRotate )
21552 	{
21553 		// wait until there's no running queries
21554 		if ( g_dChildren.GetLength() && g_eWorkers!=MPM_PREFORK )
21555 			return;
21556 
21557 		if ( CheckConfigChanges () )
21558 		{
21559 			ReloadIndexSettings ( g_pCfg );
21560 		}
21561 
21562 		for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21563 		{
21564 			ServedIndex_t & tIndex = it.Get();
21565 			tIndex.WriteLock();
21566 			const char * sIndex = it.GetKey().cstr();
21567 			assert ( tIndex.m_pIndex );
21568 
21569 			if ( tIndex.m_bRT )
21570 			{
21571 				tIndex.Unlock();
21572 				continue;
21573 			}
21574 
21575 			bool bWasAdded = tIndex.m_bOnlyNew;
21576 			RotateIndexGreedy ( tIndex, sIndex );
21577 			if ( bWasAdded && tIndex.m_bEnabled && g_pCfg.m_tConf.Exists ( "index" ) )
21578 			{
21579 				const CSphConfigType & hConf = g_pCfg.m_tConf ["index"];
21580 				if ( hConf.Exists ( sIndex ) )
21581 				{
21582 					CSphString sError;
21583 					if ( !sphFixupIndexSettings ( tIndex.m_pIndex, hConf [sIndex], sError ) )
21584 					{
21585 						sphWarning ( "index '%s': %s - NOT SERVING", sIndex, sError.cstr() );
21586 						tIndex.m_bEnabled = false;
21587 					}
21588 				}
21589 			}
21590 			tIndex.Unlock();
21591 		}
21592 
21593 		IndexRotationDone ();
21594 		return;
21595 	}
21596 
21597 	///////////////////
21598 	// seamless rotate
21599 	///////////////////
21600 
21601 	if ( g_dRotating.GetLength() || g_dRotateQueue.GetLength() || g_sPrereading )
21602 		return; // rotate in progress already; will be handled in CheckPipes()
21603 
21604 	g_tRotateConfigMutex.Lock();
21605 	if ( CheckConfigChanges() )
21606 	{
21607 		ReloadIndexSettings ( g_pCfg );
21608 	}
21609 	g_tRotateConfigMutex.Unlock();
21610 
21611 	int iRotIndexes = 0;
21612 	// check what indexes need to be rotated
21613 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21614 	{
21615 		const ServedIndex_t & tIndex = it.Get();
21616 		const CSphString & sIndex = it.GetKey();
21617 		assert ( tIndex.m_pIndex );
21618 
21619 		CSphString sNewPath;
21620 		sNewPath.SetSprintf ( "%s.new", tIndex.m_sIndexPath.cstr() );
21621 
21622 		// check if there's a .new index incoming
21623 		// FIXME? move this code to index, and also check for exists-but-not-readable
21624 		CSphString sTmp;
21625 		sTmp.SetSprintf ( "%s.sph", sNewPath.cstr() );
21626 		bool bGotNew = sphIsReadable ( sTmp.cstr() );
21627 		bool bReEnable = false;
21628 		if ( tIndex.m_bOnlyNew && !bGotNew )
21629 		{
21630 			sTmp.SetSprintf ( "%s.sph", tIndex.m_sIndexPath.cstr() );
21631 			bReEnable = sphIsReadable ( sTmp.cstr() );
21632 		}
21633 
21634 		if ( !bGotNew && !bReEnable )
21635 		{
21636 			sphLogDebug ( "%s.sph is not readable. Skipping", sNewPath.cstr() );
21637 			continue;
21638 		}
21639 
21640 		if ( g_eWorkers==MPM_THREADS )
21641 		{
21642 			g_tRotateQueueMutex.Lock();
21643 			g_dRotateQueue.Add ( sIndex );
21644 			g_tRotateQueueMutex.Unlock();
21645 		} else
21646 		{
21647 			g_dRotating.Add ( sIndex.cstr() );
21648 
21649 			if ( !( tIndex.m_bPreopen || g_bPreopenIndexes ) )
21650 				sphWarning ( "rotating index '%s' without preopen option; use per-index propen=1 or searchd preopen_indexes=1", sIndex.cstr() );
21651 		}
21652 
21653 		iRotIndexes++;
21654 	}
21655 
21656 	if ( !iRotIndexes )
21657 	{
21658 		int iRotateCount = ( g_iRotateCount.Dec()-1 );
21659 		iRotateCount = Max ( 0, iRotateCount );
21660 		sphWarning ( "nothing to rotate after SIGHUP ( in queue=%d )", iRotateCount );
21661 	} else
21662 	{
21663 		g_bInvokeRotationService = true;
21664 	}
21665 
21666 	if ( g_eWorkers!=MPM_THREADS && iRotIndexes )
21667 		SeamlessForkPrereader ();
21668 }
21669 
21670 
CheckReopen()21671 void CheckReopen ()
21672 {
21673 	if ( !g_bGotSigusr1 )
21674 		return;
21675 
21676 	// reopen searchd log
21677 	if ( g_iLogFile>=0 && !g_bLogTty )
21678 	{
21679 		int iFD = ::open ( g_sLogFile.cstr(), O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
21680 		if ( iFD<0 )
21681 		{
21682 			sphWarning ( "failed to reopen log file '%s': %s", g_sLogFile.cstr(), strerror(errno) );
21683 		} else
21684 		{
21685 			::close ( g_iLogFile );
21686 			g_iLogFile = iFD;
21687 			g_bLogTty = ( isatty ( g_iLogFile )!=0 );
21688 			sphInfo ( "log reopened" );
21689 		}
21690 	}
21691 
21692 	// reopen query log
21693 	if ( !g_bQuerySyslog && g_iQueryLogFile!=g_iLogFile && g_iQueryLogFile>=0 && !isatty ( g_iQueryLogFile ) )
21694 	{
21695 		int iFD = ::open ( g_sQueryLogFile.cstr(), O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
21696 		if ( iFD<0 )
21697 		{
21698 			sphWarning ( "failed to reopen query log file '%s': %s", g_sQueryLogFile.cstr(), strerror(errno) );
21699 		} else
21700 		{
21701 			::close ( g_iQueryLogFile );
21702 			g_iQueryLogFile = iFD;
21703 			sphInfo ( "query log reopened" );
21704 		}
21705 	}
21706 
21707 #if !USE_WINDOWS
21708 	if ( g_eWorkers==MPM_PREFORK )
21709 		ARRAY_FOREACH ( i, g_dChildren )
21710 			kill ( g_dChildren[i], SIGUSR1 );
21711 #endif
21712 
21713 
21714 	g_bGotSigusr1 = 0;
21715 }
21716 
21717 
ThdSaveIndexes(void *)21718 static void ThdSaveIndexes ( void * )
21719 {
21720 	SaveIndexes ();
21721 
21722 	// we're no more flushing
21723 	g_pFlush->m_bFlushing = false;
21724 }
21725 
21726 #if !USE_WINDOWS
21727 int PreforkChild ();
21728 #endif
21729 
CheckFlush()21730 void CheckFlush ()
21731 {
21732 	if ( g_pFlush->m_bFlushing )
21733 		return;
21734 
21735 	// do a periodic check, unless we have a forced check
21736 	if ( !g_pFlush->m_bForceCheck )
21737 	{
21738 		static int64_t tmLastCheck = -1000;
21739 		int64_t tmNow = sphMicroTimer();
21740 
21741 		if ( !g_iAttrFlushPeriod || ( tmLastCheck + int64_t(g_iAttrFlushPeriod)*I64C(1000000) )>=tmNow )
21742 			return;
21743 
21744 		tmLastCheck = tmNow;
21745 		sphLogDebug ( "attrflush: doing periodic check" );
21746 	} else
21747 	{
21748 		sphLogDebug ( "attrflush: doing forced check" );
21749 	}
21750 
21751 	// check if there are dirty indexes
21752 	bool bDirty = false;
21753 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
21754 	{
21755 		const ServedIndex_t & tServed = it.Get();
21756 		if ( tServed.m_bEnabled && tServed.m_pIndex->GetAttributeStatus() )
21757 		{
21758 			bDirty = true;
21759 			break;
21760 		}
21761 	}
21762 
21763 	// need to set this before clearing check flag
21764 	if ( bDirty )
21765 		g_pFlush->m_bFlushing = true;
21766 
21767 	// if there was a forced check in progress, it no longer is
21768 	if ( g_pFlush->m_bForceCheck )
21769 		g_pFlush->m_bForceCheck = false;
21770 
21771 	// nothing to do, no indexes were updated
21772 	if ( !bDirty )
21773 	{
21774 		sphLogDebug ( "attrflush: no dirty indexes found" );
21775 		return;
21776 	}
21777 
21778 	// launch the flush!
21779 	g_pFlush->m_iFlushTag++;
21780 
21781 	sphLogDebug ( "attrflush: starting writer, tag ( %d )", g_pFlush->m_iFlushTag );
21782 
21783 #if !USE_WINDOWS
21784 	if ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK )
21785 	{
21786 		PreforkChild(); // FIXME! gracefully handle fork() failures, Windows, etc
21787 		if ( g_bHeadDaemon )
21788 		{
21789 			return;
21790 		}
21791 
21792 		// child process, do the work
21793 		SaveIndexes ();
21794 		g_pFlush->m_bFlushing = false;
21795 		exit ( 0 );
21796 	} else
21797 #endif
21798 	{
21799 		ThdDesc_t tThd;
21800 		if ( !sphThreadCreate ( &tThd.m_tThd, ThdSaveIndexes, NULL, true ) )
21801 			sphWarning ( "failed to create attribute save thread, error[%d] %s", errno, strerror(errno) );
21802 	}
21803 }
21804 
21805 
21806 #if !USE_WINDOWS
21807 #define WINAPI
21808 #else
21809 
21810 SERVICE_STATUS			g_ss;
21811 SERVICE_STATUS_HANDLE	g_ssHandle;
21812 
21813 
MySetServiceStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD dwWaitHint)21814 void MySetServiceStatus ( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint )
21815 {
21816 	static DWORD dwCheckPoint = 1;
21817 
21818 	if ( dwCurrentState==SERVICE_START_PENDING )
21819 		g_ss.dwControlsAccepted = 0;
21820 	else
21821 		g_ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
21822 
21823 	g_ss.dwCurrentState = dwCurrentState;
21824 	g_ss.dwWin32ExitCode = dwWin32ExitCode;
21825 	g_ss.dwWaitHint = dwWaitHint;
21826 
21827 	if ( dwCurrentState==SERVICE_RUNNING || dwCurrentState==SERVICE_STOPPED )
21828 		g_ss.dwCheckPoint = 0;
21829 	else
21830 		g_ss.dwCheckPoint = dwCheckPoint++;
21831 
21832 	SetServiceStatus ( g_ssHandle, &g_ss );
21833 }
21834 
21835 
ServiceControl(DWORD dwControlCode)21836 void WINAPI ServiceControl ( DWORD dwControlCode )
21837 {
21838 	switch ( dwControlCode )
21839 	{
21840 		case SERVICE_CONTROL_STOP:
21841 		case SERVICE_CONTROL_SHUTDOWN:
21842 			MySetServiceStatus ( SERVICE_STOP_PENDING, NO_ERROR, 0 );
21843 			g_bServiceStop = true;
21844 			break;
21845 
21846 		default:
21847 			MySetServiceStatus ( g_ss.dwCurrentState, NO_ERROR, 0 );
21848 			break;
21849 	}
21850 }
21851 
21852 
21853 // warning! static buffer, non-reentrable
WinErrorInfo()21854 const char * WinErrorInfo ()
21855 {
21856 	static char sBuf[1024];
21857 
21858 	DWORD uErr = ::GetLastError ();
21859 	snprintf ( sBuf, sizeof(sBuf), "code=%d, error=", uErr );
21860 
21861 	int iLen = strlen(sBuf);
21862 	if ( !FormatMessage ( FORMAT_MESSAGE_FROM_SYSTEM, NULL, uErr, 0, sBuf+iLen, sizeof(sBuf)-iLen, NULL ) ) // FIXME? force US-english langid?
21863 		snprintf ( sBuf+iLen, sizeof(sBuf)-iLen, "(no message)" );
21864 
21865 	return sBuf;
21866 }
21867 
21868 
ServiceOpenManager()21869 SC_HANDLE ServiceOpenManager ()
21870 {
21871 	SC_HANDLE hSCM = OpenSCManager (
21872 		NULL,						// local computer
21873 		NULL,						// ServicesActive database
21874 		SC_MANAGER_ALL_ACCESS );	// full access rights
21875 
21876 	if ( hSCM==NULL )
21877 		sphFatal ( "OpenSCManager() failed: %s", WinErrorInfo() );
21878 
21879 	return hSCM;
21880 }
21881 
21882 
AppendArg(char * sBuf,int iBufLimit,const char * sArg)21883 void AppendArg ( char * sBuf, int iBufLimit, const char * sArg )
21884 {
21885 	char * sBufMax = sBuf + iBufLimit - 2; // reserve place for opening space and trailing zero
21886 	sBuf += strlen(sBuf);
21887 
21888 	if ( sBuf>=sBufMax )
21889 		return;
21890 
21891 	int iArgLen = strlen(sArg);
21892 	bool bQuote = false;
21893 	for ( int i=0; i<iArgLen && !bQuote; i++ )
21894 		if ( sArg[i]==' ' || sArg[i]=='"' )
21895 			bQuote = true;
21896 
21897 	*sBuf++ = ' ';
21898 	if ( !bQuote )
21899 	{
21900 		// just copy
21901 		int iToCopy = Min ( sBufMax-sBuf, iArgLen );
21902 		memcpy ( sBuf, sArg, iToCopy );
21903 		sBuf[iToCopy] = '\0';
21904 
21905 	} else
21906 	{
21907 		// quote
21908 		sBufMax -= 2; // reserve place for quotes
21909 		if ( sBuf>=sBufMax )
21910 			return;
21911 
21912 		*sBuf++ = '"';
21913 		while ( sBuf<sBufMax && *sArg )
21914 		{
21915 			if ( *sArg=='"' )
21916 			{
21917 				// quote
21918 				if ( sBuf<sBufMax-1 )
21919 				{
21920 					*sBuf++ = '\\';
21921 					*sBuf++ = *sArg++;
21922 				}
21923 			} else
21924 			{
21925 				// copy
21926 				*sBuf++ = *sArg++;
21927 			}
21928 		}
21929 		*sBuf++ = '"';
21930 		*sBuf++ = '\0';
21931 	}
21932 }
21933 
21934 
ServiceInstall(int argc,char ** argv)21935 void ServiceInstall ( int argc, char ** argv )
21936 {
21937 	if ( g_bService )
21938 		return;
21939 
21940 	sphInfo ( "Installing service..." );
21941 
21942 	char szBinary[MAX_PATH];
21943 	if ( !GetModuleFileName ( NULL, szBinary, MAX_PATH ) )
21944 		sphFatal ( "GetModuleFileName() failed: %s", WinErrorInfo() );
21945 
21946 	char szPath[MAX_PATH];
21947 	szPath[0] = '\0';
21948 
21949 	AppendArg ( szPath, sizeof(szPath), szBinary );
21950 	AppendArg ( szPath, sizeof(szPath), "--ntservice" );
21951 	for ( int i=1; i<argc; i++ )
21952 		if ( strcmp ( argv[i], "--install" ) )
21953 			AppendArg ( szPath, sizeof(szPath), argv[i] );
21954 
21955 	SC_HANDLE hSCM = ServiceOpenManager ();
21956 	SC_HANDLE hService = CreateService (
21957 		hSCM,							// SCM database
21958 		g_sServiceName,					// name of service
21959 		g_sServiceName,					// service name to display
21960 		SERVICE_ALL_ACCESS,				// desired access
21961 		SERVICE_WIN32_OWN_PROCESS,		// service type
21962 		SERVICE_AUTO_START,				// start type
21963 		SERVICE_ERROR_NORMAL,			// error control type
21964 		szPath+1,						// path to service's binary
21965 		NULL,							// no load ordering group
21966 		NULL,							// no tag identifier
21967 		NULL,							// no dependencies
21968 		NULL,							// LocalSystem account
21969 		NULL );							// no password
21970 
21971 	if ( !hService )
21972 	{
21973 		CloseServiceHandle ( hSCM );
21974 		sphFatal ( "CreateService() failed: %s", WinErrorInfo() );
21975 
21976 	} else
21977 	{
21978 		sphInfo ( "Service '%s' installed successfully.", g_sServiceName );
21979 	}
21980 
21981 	CSphString sDesc;
21982 	sDesc.SetSprintf ( "%s-%s", g_sServiceName, SPHINX_VERSION );
21983 
21984 	SERVICE_DESCRIPTION tDesc;
21985 	tDesc.lpDescription = (LPSTR) sDesc.cstr();
21986 	if ( !ChangeServiceConfig2 ( hService, SERVICE_CONFIG_DESCRIPTION, &tDesc ) )
21987 		sphWarning ( "failed to set service description" );
21988 
21989 	CloseServiceHandle ( hService );
21990 	CloseServiceHandle ( hSCM );
21991 }
21992 
21993 
ServiceDelete()21994 void ServiceDelete ()
21995 {
21996 	if ( g_bService )
21997 		return;
21998 
21999 	sphInfo ( "Deleting service..." );
22000 
22001 	// open manager
22002 	SC_HANDLE hSCM = ServiceOpenManager ();
22003 
22004 	// open service
22005 	SC_HANDLE hService = OpenService ( hSCM, g_sServiceName, DELETE );
22006 	if ( !hService )
22007 	{
22008 		CloseServiceHandle ( hSCM );
22009 		sphFatal ( "OpenService() failed: %s", WinErrorInfo() );
22010 	}
22011 
22012 	// do delete
22013 	bool bRes = !!DeleteService ( hService );
22014 	CloseServiceHandle ( hService );
22015 	CloseServiceHandle ( hSCM );
22016 
22017 	if ( !bRes )
22018 		sphFatal ( "DeleteService() failed: %s", WinErrorInfo() );
22019 	else
22020 		sphInfo ( "Service '%s' deleted successfully.", g_sServiceName );
22021 }
22022 #endif // USE_WINDOWS
22023 
22024 
ShowHelp()22025 void ShowHelp ()
22026 {
22027 	fprintf ( stdout,
22028 		"Usage: searchd [OPTIONS]\n"
22029 		"\n"
22030 		"Options are:\n"
22031 		"-h, --help\t\tdisplay this help message\n"
22032 		"-c, --config <file>\tread configuration from specified file\n"
22033 		"\t\t\t(default is sphinx.conf)\n"
22034 		"--stop\t\t\tsend SIGTERM to currently running searchd\n"
22035 		"--stopwait\t\tsend SIGTERM and wait until actual exit\n"
22036 		"--status\t\tget ant print status variables\n"
22037 		"\t\t\t(PID is taken from pid_file specified in config file)\n"
22038 		"--iostats\t\tlog per-query io stats\n"
22039 #ifdef HAVE_CLOCK_GETTIME
22040 		"--cpustats\t\tlog per-query cpu stats\n"
22041 #endif
22042 #if USE_WINDOWS
22043 		"--install\t\tinstall as Windows service\n"
22044 		"--delete\t\tdelete Windows service\n"
22045 		"--servicename <name>\tuse given service name (default is 'searchd')\n"
22046 		"--ntservice\t\tinternal option used to invoke a Windows service\n"
22047 #endif
22048 		"--strip-path\t\tstrip paths from stopwords, wordforms, exceptions\n"
22049 		"\t\t\tand other file names stored in the index header\n"
22050 		"--replay-flags=<OPTIONS>\n"
22051 		"\t\t\textra binary log replay options (the only current one\n"
22052 		"\t\t\tis 'accept-desc-timestamp')\n"
22053 		"\n"
22054 		"Debugging options are:\n"
22055 		"--console\t\trun in console mode (do not fork, do not log to files)\n"
22056 		"-p, --port <port>\tlisten on given port (overrides config setting)\n"
22057 		"-l, --listen <spec>\tlisten on given address, port or path (overrides\n"
22058 		"\t\t\tconfig settings)\n"
22059 		"-i, --index <index>\tonly serve given index(es)\n"
22060 #if !USE_WINDOWS
22061 		"--nodetach\t\tdo not detach into background\n"
22062 #endif
22063 		"--logdebug, --logdebugv, --logdebugvv\n"
22064 		"\t\t\tenable additional debug information logging\n"
22065 		"\t\t\t(with different verboseness)\n"
22066 		"--pidfile\t\tforce using the PID file (useful with --console)\n"
22067 		"--safetrace\t\tonly use system backtrace() call in crash reports\n"
22068 		"\n"
22069 		"Examples:\n"
22070 		"searchd --config /usr/local/sphinx/etc/sphinx.conf\n"
22071 #if USE_WINDOWS
22072 		"searchd --install --config c:\\sphinx\\sphinx.conf\n"
22073 #endif
22074 		);
22075 }
22076 
22077 
22078 template<typename T>
InitSharedBuffer(CSphSharedBuffer<T> & tBuffer)22079 T * InitSharedBuffer ( CSphSharedBuffer<T> & tBuffer )
22080 {
22081 	CSphString sError, sWarning;
22082 	if ( !tBuffer.Alloc ( 1, sError, sWarning ) )
22083 		sphDie ( "failed to allocate shared buffer (msg=%s)", sError.cstr() );
22084 
22085 	T * pRes = tBuffer.GetWritePtr();
22086 	memset ( pRes, 0, sizeof(T) ); // reset
22087 	return pRes;
22088 }
22089 
22090 
22091 #if USE_WINDOWS
CtrlHandler(DWORD)22092 BOOL WINAPI CtrlHandler ( DWORD )
22093 {
22094 	if ( !g_bService )
22095 	{
22096 		g_bGotSigterm = 1;
22097 		sphInterruptNow();
22098 	}
22099 	return TRUE;
22100 }
22101 #endif
22102 
22103 
22104 #if !USE_WINDOWS
PreforkChild()22105 int PreforkChild ()
22106 {
22107 	// next one
22108 	int iRes = fork();
22109 	if ( iRes==-1 )
22110 		sphFatal ( "fork() failed during prefork (error=%s)", strerror(errno) );
22111 
22112 	// child process
22113 	if ( iRes==0 )
22114 	{
22115 		g_bHeadDaemon = false;
22116 		sphSetProcessInfo ( false );
22117 		return iRes;
22118 	}
22119 
22120 	// parent process
22121 	g_dChildren.Add ( iRes );
22122 	return iRes;
22123 }
22124 
22125 
22126 // returns 'true' only once - at the very start, to show it beatiful way.
SetWatchDog(int iDevNull)22127 bool SetWatchDog ( int iDevNull )
22128 {
22129 	InitSharedBuffer ( g_bDaemonAtShutdown );
22130 
22131 	// Fork #1 - detach from controlling terminal
22132 	switch ( fork() )
22133 	{
22134 		case -1:
22135 			// error
22136 			Shutdown ();
22137 			sphFatal ( "fork() failed (reason: %s)", strerror ( errno ) );
22138 			exit ( 1 );
22139 		case 0:
22140 			// daemonized child - or new and free watchdog :)
22141 			break;
22142 
22143 		default:
22144 			// tty-controlled parent
22145 			while ( g_tHaveTTY.ReadValue() )
22146 				sphSleepMsec ( 100 );
22147 
22148 			sphSetProcessInfo ( false );
22149 			exit ( 0 );
22150 	}
22151 
22152 	// became the session leader
22153 	if ( setsid()==-1 )
22154 	{
22155 		Shutdown ();
22156 		sphFatal ( "setsid() failed (reason: %s)", strerror ( errno ) );
22157 		exit ( 1 );
22158 	}
22159 
22160 	// Fork #2 - detach from session leadership (may be not necessary, however)
22161 	switch ( fork() )
22162 	{
22163 		case -1:
22164 			// error
22165 			Shutdown ();
22166 			sphFatal ( "fork() failed (reason: %s)", strerror ( errno ) );
22167 			exit ( 1 );
22168 		case 0:
22169 			// daemonized child - or new and free watchdog :)
22170 			break;
22171 
22172 		default:
22173 			// tty-controlled parent
22174 			sphSetProcessInfo ( false );
22175 			exit ( 0 );
22176 	}
22177 
22178 	// now we are the watchdog. Let us fork the actual process
22179 	int iReincarnate = 1;
22180 	bool bShutdown = false;
22181 	bool bStreamsActive = true;
22182 	int iRes = 0;
22183 	for ( ;; )
22184 	{
22185 		if ( iReincarnate!=0 )
22186 			iRes = fork();
22187 
22188 		if ( iRes==-1 )
22189 		{
22190 			Shutdown ();
22191 			sphFatal ( "fork() failed during watchdog setup (error=%s)", strerror(errno) );
22192 		}
22193 
22194 		// child process; return true to show that we have to reload everything
22195 		if ( iRes==0 )
22196 		{
22197 			atexit ( &ReleaseTTYFlag );
22198 			return bStreamsActive;
22199 		}
22200 
22201 		// parent process, watchdog
22202 		// close the io files
22203 		if ( bStreamsActive )
22204 		{
22205 			close ( STDIN_FILENO );
22206 			close ( STDOUT_FILENO );
22207 			close ( STDERR_FILENO );
22208 			dup2 ( iDevNull, STDIN_FILENO );
22209 			dup2 ( iDevNull, STDOUT_FILENO );
22210 			dup2 ( iDevNull, STDERR_FILENO );
22211 			bStreamsActive = false;
22212 		}
22213 
22214 		sphInfo ( "watchdog: main process %d forked ok", iRes );
22215 
22216 		SetSignalHandlers();
22217 
22218 		iReincarnate = 0;
22219 		int iPid, iStatus;
22220 		bool bDaemonAtShutdown = 0;
22221 		while ( ( iPid = wait ( &iStatus ) )>0 )
22222 		{
22223 			bDaemonAtShutdown = ( g_bDaemonAtShutdown[0]!=0 );
22224 			const char * sWillRestart = ( bDaemonAtShutdown ? "will not be restarted (daemon is shutting down)" : "will be restarted" );
22225 
22226 			assert ( iPid==iRes );
22227 			if ( WIFEXITED ( iStatus ) )
22228 			{
22229 				int iExit = WEXITSTATUS ( iStatus );
22230 				if ( iExit==2 || iExit==6 ) // really crash
22231 				{
22232 					sphInfo ( "watchdog: main process %d crashed via CRASH_EXIT (exit code %d), %s", iPid, iExit, sWillRestart );
22233 					iReincarnate = -1;
22234 				} else
22235 				{
22236 					sphInfo ( "watchdog: main process %d exited cleanly (exit code %d), shutting down", iPid, iExit );
22237 					bShutdown = true;
22238 				}
22239 			} else if ( WIFSIGNALED ( iStatus ) )
22240 			{
22241 				int iSig = WTERMSIG ( iStatus );
22242 				const char * sSig = NULL;
22243 				if ( iSig==SIGINT )
22244 					sSig = "SIGINIT";
22245 				else if ( iSig==SIGTERM )
22246 					sSig = "SIGTERM";
22247 				else if ( WATCHDOG_SIGKILL && iSig==SIGKILL )
22248 					sSig = "SIGKILL";
22249 				if ( sSig )
22250 				{
22251 					sphInfo ( "watchdog: main process %d killed cleanly with %s, shutting down", iPid, sSig );
22252 					bShutdown = true;
22253 				} else
22254 				{
22255 					if ( WCOREDUMP ( iStatus ) )
22256 						sphInfo ( "watchdog: main process %d killed dirtily with signal %d, core dumped, %s",
22257 							iPid, iSig, sWillRestart );
22258 					else
22259 						sphInfo ( "watchdog: main process %d killed dirtily with signal %d, %s",
22260 							iPid, iSig, sWillRestart );
22261 					iReincarnate = -1;
22262 				}
22263 			} else if ( WIFSTOPPED ( iStatus ) )
22264 				sphInfo ( "watchdog: main process %d stopped with signal %d", iPid, WSTOPSIG ( iStatus ) );
22265 #ifdef WIFCONTINUED
22266 			else if ( WIFCONTINUED ( iStatus ) )
22267 				sphInfo ( "watchdog: main process %d resumed", iPid );
22268 #endif
22269 		}
22270 
22271 		if ( bShutdown || g_bGotSigterm || bDaemonAtShutdown )
22272 		{
22273 			Shutdown();
22274 			exit ( 0 );
22275 		}
22276 	}
22277 }
22278 #endif // !USE_WINDOWS
22279 
22280 /// check for incoming signals, and react on them
CheckSignals()22281 void CheckSignals ()
22282 {
22283 #if USE_WINDOWS
22284 	if ( g_bService && g_bServiceStop )
22285 	{
22286 		Shutdown ();
22287 		MySetServiceStatus ( SERVICE_STOPPED, NO_ERROR, 0 );
22288 		exit ( 0 );
22289 	}
22290 #endif
22291 
22292 	if ( g_bGotSighup )
22293 	{
22294 		int iRotateCount = ( g_iRotateCount.Inc()+1 );
22295 		sphInfo ( "caught SIGHUP (seamless=%d, in queue=%d)", (int)g_bSeamlessRotate, iRotateCount );
22296 		g_bGotSighup = 0;
22297 	}
22298 
22299 	if ( g_bGotSigterm )
22300 	{
22301 		assert ( g_bHeadDaemon );
22302 		sphInfo ( "caught SIGTERM, shutting down" );
22303 		Shutdown ();
22304 		exit ( 0 );
22305 	}
22306 
22307 #if !USE_WINDOWS
22308 	if ( g_bGotSigchld )
22309 	{
22310 		// handle gone children
22311 		for ( ;; )
22312 		{
22313 			int iChildPid = waitpid ( -1, NULL, WNOHANG );
22314 			sphLogDebugvv ( "gone child %d ( %d )", iChildPid, g_dChildren.GetLength() ); // !COMMIT
22315 			if ( iChildPid<=0 )
22316 				break;
22317 
22318 			g_dChildren.RemoveValue ( iChildPid ); // FIXME! OPTIMIZE! can be slow
22319 		}
22320 		g_bGotSigchld = 0;
22321 
22322 		// prefork more children, if needed
22323 		if ( g_eWorkers==MPM_PREFORK )
22324 			while ( g_dChildren.GetLength() < g_iPreforkChildren )
22325 				if ( PreforkChild()==0 ) // child process? break from here, go work
22326 					return;
22327 	}
22328 #endif
22329 
22330 #if USE_WINDOWS
22331 	BYTE dPipeInBuf [ WIN32_PIPE_BUFSIZE ];
22332 	DWORD nBytesRead = 0;
22333 	BOOL bSuccess = ReadFile ( g_hPipe, dPipeInBuf, WIN32_PIPE_BUFSIZE, &nBytesRead, NULL );
22334 	if ( nBytesRead > 0 && bSuccess )
22335 	{
22336 		for ( DWORD i=0; i<nBytesRead; i++ )
22337 		{
22338 			switch ( dPipeInBuf[i] )
22339 			{
22340 			case 0:
22341 				g_bGotSighup = 1;
22342 				break;
22343 
22344 			case 1:
22345 				g_bGotSigterm = 1;
22346 				sphInterruptNow();
22347 				if ( g_bService )
22348 					g_bServiceStop = true;
22349 				break;
22350 			}
22351 		}
22352 
22353 		DisconnectNamedPipe ( g_hPipe );
22354 		ConnectNamedPipe ( g_hPipe, NULL );
22355 	}
22356 #endif
22357 }
22358 
22359 
QueryStatus(CSphVariant * v)22360 void QueryStatus ( CSphVariant * v )
22361 {
22362 	char sBuf [ SPH_ADDRESS_SIZE ];
22363 	char sListen [ 256 ];
22364 	CSphVariant tListen;
22365 
22366 	if ( !v )
22367 	{
22368 		snprintf ( sListen, sizeof ( sListen ), "127.0.0.1:%d:sphinx", SPHINXAPI_PORT );
22369 		tListen = CSphVariant ( sListen, 0 );
22370 		v = &tListen;
22371 	}
22372 
22373 	for ( ; v; v = v->m_pNext )
22374 	{
22375 		ListenerDesc_t tDesc = ParseListener ( v->cstr() );
22376 		if ( tDesc.m_eProto!=PROTO_SPHINX )
22377 			continue;
22378 
22379 		int iSock = -1;
22380 #if !USE_WINDOWS
22381 		if ( !tDesc.m_sUnix.IsEmpty() )
22382 		{
22383 			// UNIX connection
22384 			struct sockaddr_un uaddr;
22385 
22386 			size_t len = strlen ( tDesc.m_sUnix.cstr() );
22387 			if ( len+1 > sizeof(uaddr.sun_path ) )
22388 				sphFatal ( "UNIX socket path is too long (len=%d)", (int)len );
22389 
22390 			memset ( &uaddr, 0, sizeof(uaddr) );
22391 			uaddr.sun_family = AF_UNIX;
22392 			memcpy ( uaddr.sun_path, tDesc.m_sUnix.cstr(), len+1 );
22393 
22394 			iSock = socket ( AF_UNIX, SOCK_STREAM, 0 );
22395 			if ( iSock<0 )
22396 				sphFatal ( "failed to create UNIX socket: %s", sphSockError() );
22397 
22398 			if ( connect ( iSock, (struct sockaddr*)&uaddr, sizeof(uaddr) )<0 )
22399 			{
22400 				sphWarning ( "failed to connect to unix://%s: %s\n", tDesc.m_sUnix.cstr(), sphSockError() );
22401 				continue;
22402 			}
22403 
22404 		} else
22405 #endif
22406 		{
22407 			// TCP connection
22408 			struct sockaddr_in sin;
22409 			memset ( &sin, 0, sizeof(sin) );
22410 			sin.sin_family = AF_INET;
22411 			sin.sin_addr.s_addr = ( tDesc.m_uIP==htonl ( INADDR_ANY ) )
22412 				? htonl ( INADDR_LOOPBACK )
22413 				: tDesc.m_uIP;
22414 			sin.sin_port = htons ( (short)tDesc.m_iPort );
22415 
22416 			iSock = socket ( AF_INET, SOCK_STREAM, 0 );
22417 			if ( iSock<0 )
22418 				sphFatal ( "failed to create TCP socket: %s", sphSockError() );
22419 
22420 #ifdef TCP_NODELAY
22421 			int iOn = 1;
22422 			if ( setsockopt ( iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&iOn, sizeof(iOn) ) )
22423 				sphWarning ( "setsockopt() failed: %s", sphSockError() );
22424 #endif
22425 
22426 			if ( connect ( iSock, (struct sockaddr*)&sin, sizeof(sin) )<0 )
22427 			{
22428 				sphWarning ( "failed to connect to %s:%d: %s\n", sphFormatIP ( sBuf, sizeof(sBuf), tDesc.m_uIP ), tDesc.m_iPort, sphSockError() );
22429 				continue;
22430 			}
22431 		}
22432 
22433 		// send request
22434 		NetOutputBuffer_c tOut ( iSock );
22435 		tOut.SendDword ( SPHINX_CLIENT_VERSION );
22436 		tOut.SendWord ( SEARCHD_COMMAND_STATUS );
22437 		tOut.SendWord ( VER_COMMAND_STATUS );
22438 		tOut.SendInt ( 4 ); // request body length
22439 		tOut.SendInt ( 1 ); // dummy body
22440 		tOut.Flush ();
22441 
22442 		// get reply
22443 		NetInputBuffer_c tIn ( iSock );
22444 		if ( !tIn.ReadFrom ( 12, 5 ) ) // magic_header_size=12, magic_timeout=5
22445 			sphFatal ( "handshake failure (no response)" );
22446 
22447 		DWORD uVer = tIn.GetDword();
22448 		if ( uVer!=SPHINX_SEARCHD_PROTO && uVer!=0x01000000UL ) // workaround for all the revisions that sent it in host order...
22449 			sphFatal ( "handshake failure (unexpected protocol version=%d)", uVer );
22450 
22451 		if ( tIn.GetWord()!=SEARCHD_OK )
22452 			sphFatal ( "status command failed" );
22453 
22454 		if ( tIn.GetWord()!=VER_COMMAND_STATUS )
22455 			sphFatal ( "status command version mismatch" );
22456 
22457 		if ( !tIn.ReadFrom ( tIn.GetDword(), 5 ) ) // magic_timeout=5
22458 			sphFatal ( "failed to read status reply" );
22459 
22460 		fprintf ( stdout, "\nsearchd status\n--------------\n" );
22461 
22462 		int iRows = tIn.GetDword();
22463 		int iCols = tIn.GetDword();
22464 		for ( int i=0; i<iRows && !tIn.GetError(); i++ )
22465 		{
22466 			for ( int j=0; j<iCols && !tIn.GetError(); j++ )
22467 			{
22468 				fprintf ( stdout, "%s", tIn.GetString().cstr() );
22469 				fprintf ( stdout, ( j==0 ) ? ": " : " " );
22470 			}
22471 			fprintf ( stdout, "\n" );
22472 		}
22473 
22474 		// all done
22475 		sphSockClose ( iSock );
22476 		return;
22477 	}
22478 	sphFatal ( "failed to connect to daemon: please specify listen with sphinx protocol in your config file" );
22479 }
22480 
22481 
ShowProgress(const CSphIndexProgress * pProgress,bool bPhaseEnd)22482 void ShowProgress ( const CSphIndexProgress * pProgress, bool bPhaseEnd )
22483 {
22484 	assert ( pProgress );
22485 	if ( bPhaseEnd )
22486 	{
22487 		fprintf ( stdout, "\r                                                            \r" );
22488 	} else
22489 	{
22490 		fprintf ( stdout, "%s\r", pProgress->BuildMessage() );
22491 	}
22492 	fflush ( stdout );
22493 }
22494 
22495 
FailClient(int iSock,SearchdStatus_e eStatus,const char * sMessage)22496 void FailClient ( int iSock, SearchdStatus_e eStatus, const char * sMessage )
22497 {
22498 	assert ( eStatus==SEARCHD_RETRY || eStatus==SEARCHD_ERROR );
22499 
22500 	int iRespLen = 4 + strlen(sMessage);
22501 
22502 	NetOutputBuffer_c tOut ( iSock );
22503 	tOut.SendInt ( SPHINX_CLIENT_VERSION );
22504 	tOut.SendWord ( (WORD)eStatus );
22505 	tOut.SendWord ( 0 ); // version doesn't matter
22506 	tOut.SendInt ( iRespLen );
22507 	tOut.SendString ( sMessage );
22508 	tOut.Flush ();
22509 
22510 	// FIXME? without some wait, client fails to receive the response on windows
22511 	sphSockClose ( iSock );
22512 }
22513 
22514 
MysqlMaxedOut(int iSock)22515 void MysqlMaxedOut ( int iSock )
22516 {
22517 	static const char * dMaxedOutPacket = "\x17\x00\x00\x00\xff\x10\x04Too many connections";
22518 	sphSockSend ( iSock, dMaxedOutPacket, 27 );
22519 	sphSockClose ( iSock );
22520 }
22521 
22522 
DoAccept(int * pClientSock,char * sClientName)22523 Listener_t * DoAccept ( int * pClientSock, char * sClientName )
22524 {
22525 	assert ( pClientSock );
22526 	assert ( *pClientSock==-1 );
22527 
22528 	int iMaxFD = 0;
22529 	fd_set fdsAccept;
22530 	FD_ZERO ( &fdsAccept );
22531 
22532 	ARRAY_FOREACH ( i, g_dListeners )
22533 	{
22534 		sphFDSet ( g_dListeners[i].m_iSock, &fdsAccept );
22535 		iMaxFD = Max ( iMaxFD, g_dListeners[i].m_iSock );
22536 	}
22537 	iMaxFD++;
22538 
22539 	struct timeval tvTimeout;
22540 	tvTimeout.tv_sec = USE_WINDOWS ? 0 : 1;
22541 	tvTimeout.tv_usec = USE_WINDOWS ? 50000 : 0;
22542 
22543 	// select should be OK here as listener sockets are created early and get low FDs
22544 	int iRes = ::select ( iMaxFD, &fdsAccept, NULL, NULL, &tvTimeout );
22545 	if ( iRes==0 )
22546 		return NULL;
22547 
22548 	if ( iRes<0 )
22549 	{
22550 		int iErrno = sphSockGetErrno();
22551 		if ( iErrno==EINTR || iErrno==EAGAIN || iErrno==EWOULDBLOCK )
22552 			return NULL;
22553 
22554 		static int iLastErrno = -1;
22555 		if ( iLastErrno!=iErrno )
22556 			sphWarning ( "select() failed: %s", sphSockError(iErrno) );
22557 		iLastErrno = iErrno;
22558 		return NULL;
22559 	}
22560 
22561 	ARRAY_FOREACH ( i, g_dListeners )
22562 	{
22563 		if ( !FD_ISSET ( g_dListeners[i].m_iSock, &fdsAccept ) )
22564 			continue;
22565 
22566 		// accept
22567 		struct sockaddr_storage saStorage;
22568 		socklen_t uLength = sizeof(saStorage);
22569 		int iClientSock = accept ( g_dListeners[i].m_iSock, (struct sockaddr *)&saStorage, &uLength );
22570 
22571 		// handle failures
22572 		if ( iClientSock<0 )
22573 		{
22574 			const int iErrno = sphSockGetErrno();
22575 			if ( iErrno==EINTR || iErrno==ECONNABORTED || iErrno==EAGAIN || iErrno==EWOULDBLOCK )
22576 				return NULL;
22577 
22578 			sphFatal ( "accept() failed: %s", sphSockError(iErrno) );
22579 		}
22580 
22581 #ifdef TCP_NODELAY
22582 		int iOn = 1;
22583 		if ( g_dListeners[i].m_bTcp && setsockopt ( iClientSock, IPPROTO_TCP, TCP_NODELAY, (char*)&iOn, sizeof(iOn) ) )
22584 			sphWarning ( "setsockopt() failed: %s", sphSockError() );
22585 #endif
22586 
22587 		if ( g_pStats )
22588 		{
22589 			g_tStatsMutex.Lock();
22590 			g_pStats->m_iConnections++;
22591 			g_tStatsMutex.Unlock();
22592 		}
22593 
22594 		if ( g_eWorkers==MPM_PREFORK )
22595 		{
22596 			// protected by accept mutex
22597 			if ( ++*g_pConnID<0 )
22598 				*g_pConnID = 0;
22599 			g_iConnID = *g_pConnID;
22600 		} else
22601 		{
22602 			if ( ++g_iConnID<0 )
22603 				g_iConnID = 0;
22604 		}
22605 
22606 		// format client address
22607 		if ( sClientName )
22608 		{
22609 			sClientName[0] = '\0';
22610 			if ( saStorage.ss_family==AF_INET )
22611 			{
22612 				struct sockaddr_in * pSa = ((struct sockaddr_in *)&saStorage);
22613 				sphFormatIP ( sClientName, SPH_ADDRESS_SIZE, pSa->sin_addr.s_addr );
22614 
22615 				char * d = sClientName;
22616 				while ( *d )
22617 					d++;
22618 				snprintf ( d, 7, ":%d", (int)ntohs ( pSa->sin_port ) ); //NOLINT
22619 			}
22620 			if ( saStorage.ss_family==AF_UNIX )
22621 				strncpy ( sClientName, "(local)", SPH_ADDRESS_SIZE );
22622 		}
22623 
22624 		// accepted!
22625 #if !USE_WINDOWS && !HAVE_POLL
22626 		// when there is no poll(), we use select(),
22627 		// which can only handle a limited range of fds..
22628 		if ( SPH_FDSET_OVERFLOW ( iClientSock ) )
22629 		{
22630 			if ( ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK ) )
22631 			{
22632 				// in fork or prefork mode, we switch to a preallocated low fd
22633 				iClientSock = dup2 ( iClientSock, g_iClientFD );
22634 			} else
22635 			{
22636 				// otherwise, we fail this client (we have to)
22637 				FailClient ( iClientSock, SEARCHD_RETRY, "server maxed out, retry in a second" );
22638 				sphWarning ( "maxed out, dismissing client (socket=%d)", iClientSock );
22639 				sphSockClose ( iClientSock );
22640 				return NULL;
22641 			}
22642 		}
22643 #endif
22644 
22645 		*pClientSock = iClientSock;
22646 		return &g_dListeners[i];
22647 	}
22648 
22649 	return NULL;
22650 }
22651 
22652 
TickPreforked(CSphProcessSharedMutex * pAcceptMutex)22653 void TickPreforked ( CSphProcessSharedMutex * pAcceptMutex )
22654 {
22655 	assert ( !g_bHeadDaemon );
22656 	assert ( pAcceptMutex );
22657 
22658 	if ( g_bGotSigterm || g_bGotSighup )
22659 		exit ( 0 );
22660 
22661 	int iClientSock = -1;
22662 	char sClientIP[SPH_ADDRPORT_SIZE];
22663 	Listener_t * pListener = NULL;
22664 
22665 	for ( ; !g_bGotSigterm && !pListener && !g_bGotSighup; )
22666 	{
22667 		if ( pAcceptMutex->TimedLock ( 1000000 ) )
22668 		{
22669 			if ( !g_bGotSigterm && !g_bGotSighup )
22670 				pListener = DoAccept ( &iClientSock, sClientIP );
22671 
22672 			pAcceptMutex->Unlock();
22673 		}
22674 	}
22675 
22676 	if ( g_bGotSigterm )
22677 		exit ( 0 ); // clean shutdown (after mutex unlock)
22678 
22679 	if ( pListener )
22680 	{
22681 		HandleClient ( pListener->m_eProto, iClientSock, sClientIP, NULL );
22682 		sphSockClose ( iClientSock );
22683 	}
22684 }
22685 
22686 
GetOsThreadId()22687 int GetOsThreadId()
22688 {
22689 #if USE_WINDOWS
22690 	return GetCurrentThreadId();
22691 #elif defined(SYS_gettid)
22692 	return syscall ( SYS_gettid );
22693 #elif defined(__FreeBSD__)
22694 	long tid;
22695 	thr_self(&tid);
22696 	return (int)tid;
22697 #else
22698 	return 0;
22699 #endif
22700 }
22701 
22702 
HandlerThread(void * pArg)22703 void HandlerThread ( void * pArg )
22704 {
22705 	// handle that client
22706 	ThdDesc_t * pThd = (ThdDesc_t*) pArg;
22707 	sphThreadSet ( g_tConnKey, &pThd->m_iConnID );
22708 	pThd->m_iTid = GetOsThreadId();
22709 	HandleClient ( pThd->m_eProto, pThd->m_iClientSock, pThd->m_sClientName.cstr(), pThd );
22710 	sphSockClose ( pThd->m_iClientSock );
22711 
22712 	// done; remove myself from the table
22713 #if USE_WINDOWS
22714 	// FIXME? this is sort of automatic on UNIX (pthread_exit() gets implicitly called on return)
22715 	CloseHandle ( pThd->m_tThd );
22716 #endif
22717 	g_tThdMutex.Lock ();
22718 	g_dThd.Remove ( pThd );
22719 	g_tThdMutex.Unlock ();
22720 	SafeDelete ( pThd );
22721 }
22722 
22723 
CheckChildrenHup()22724 static void CheckChildrenHup ()
22725 {
22726 #if !USE_WINDOWS
22727 	if ( g_eWorkers!=MPM_PREFORK || !g_dHupChildren.GetLength() || g_tmRotateChildren>sphMicroTimer() )
22728 		return;
22729 
22730 	sphLogDebugvv ( "sending sighup to child %d ( %d )", g_dHupChildren.Last(), g_dHupChildren.GetLength() );
22731 	kill ( g_dHupChildren.Pop(), SIGHUP );
22732 	g_tmRotateChildren = sphMicroTimer() + g_iRotationThrottle*1000;
22733 #endif
22734 }
22735 
22736 
TickHead(bool bDontListen)22737 void TickHead ( bool bDontListen )
22738 {
22739 	CheckSignals ();
22740 	if ( !g_bHeadDaemon )
22741 		return;
22742 
22743 	CheckLeaks ();
22744 	CheckReopen ();
22745 	CheckPipes ();
22746 	CheckDelete ();
22747 	CheckRotate ();
22748 	CheckFlush ();
22749 	CheckChildrenHup();
22750 
22751 	sphInfo ( NULL ); // flush dupes
22752 
22753 	if ( bDontListen )
22754 	{
22755 		// FIXME! what if all children are busy; we might want to accept here and temp fork more
22756 		sphSleepMsec ( 1000 );
22757 		return;
22758 	}
22759 
22760 	int iClientSock = -1;
22761 	char sClientName[SPH_ADDRPORT_SIZE];
22762 	Listener_t * pListener = DoAccept ( &iClientSock, sClientName );
22763 	if ( !pListener )
22764 		return;
22765 
22766 	if ( ( g_iMaxChildren && ( g_dChildren.GetLength()>=g_iMaxChildren || g_dThd.GetLength()>=g_iMaxChildren ) )
22767 		|| ( g_iRotateCount && !g_bSeamlessRotate ) )
22768 	{
22769 		if ( pListener->m_eProto==PROTO_SPHINX )
22770 			FailClient ( iClientSock, SEARCHD_RETRY, "server maxed out, retry in a second" );
22771 		else
22772 			MysqlMaxedOut ( iClientSock );
22773 		sphWarning ( "maxed out, dismissing client" );
22774 
22775 		if ( g_pStats )
22776 			g_pStats->m_iMaxedOut++;
22777 		return;
22778 	}
22779 
22780 	// handle the client
22781 	if ( g_eWorkers==MPM_NONE )
22782 	{
22783 		HandleClient ( pListener->m_eProto, iClientSock, sClientName, NULL );
22784 		sphSockClose ( iClientSock );
22785 		return;
22786 	}
22787 
22788 #if !USE_WINDOWS
22789 	if ( g_eWorkers==MPM_FORK )
22790 	{
22791 		sphLogDebugv ( "conn %s: accepted, socket %d", sClientName, iClientSock );
22792 		int iChildPipe = PipeAndFork ( false, -1 );
22793 		SafeClose ( iChildPipe );
22794 		if ( !g_bHeadDaemon )
22795 		{
22796 			// child process, handle client
22797 			sphLogDebugv ( "conn %s: forked handler, socket %d", sClientName, iClientSock );
22798 			HandleClient ( pListener->m_eProto, iClientSock, sClientName, NULL );
22799 			sphSockClose ( iClientSock );
22800 			exit ( 0 );
22801 		} else
22802 		{
22803 			// parent process, continue accept()ing
22804 			sphSockClose ( iClientSock );
22805 			return;
22806 		}
22807 	}
22808 #endif // !USE_WINDOWS
22809 
22810 	if ( g_eWorkers==MPM_THREADS )
22811 	{
22812 		ThdDesc_t * pThd = new ThdDesc_t ();
22813 		pThd->m_eProto = pListener->m_eProto;
22814 		pThd->m_iClientSock = iClientSock;
22815 		pThd->m_sClientName = sClientName;
22816 		pThd->m_iConnID = g_iConnID;
22817 		pThd->m_tmConnect = sphMicroTimer();
22818 
22819 		g_tThdMutex.Lock ();
22820 		g_dThd.Add ( pThd );
22821 		g_tThdMutex.Unlock ();
22822 
22823 		if ( !SphCrashLogger_c::ThreadCreate ( &pThd->m_tThd, HandlerThread, pThd, true ) )
22824 		{
22825 			int iErr = errno;
22826 			g_tThdMutex.Lock ();
22827 			g_dThd.Remove ( pThd );
22828 			g_tThdMutex.Unlock ();
22829 			SafeDelete ( pThd );
22830 
22831 			FailClient ( iClientSock, SEARCHD_RETRY, "failed to create worker thread" );
22832 			sphWarning ( "failed to create worker thread, threads(%d), error[%d] %s", g_dThd.GetLength(), iErr, strerror(iErr) );
22833 		}
22834 		return;
22835 	}
22836 
22837 	// default (should not happen)
22838 	sphSockClose ( iClientSock );
22839 }
22840 
22841 
ParsePredictedTimeCosts(const char * p)22842 static void ParsePredictedTimeCosts ( const char * p )
22843 {
22844 	// yet another mini-parser!
22845 	// ident=value [, ident=value [...]]
22846 	while ( *p )
22847 	{
22848 		// parse ident
22849 		while ( sphIsSpace(*p) )
22850 			p++;
22851 		if ( !*p )
22852 			break;
22853 		if ( !sphIsAlpha(*p) )
22854 			sphDie ( "predicted_time_costs: parse error near '%s' (identifier expected)", p );
22855 		const char * q = p;
22856 		while ( sphIsAlpha(*p) )
22857 			p++;
22858 		CSphString sIdent;
22859 		sIdent.SetBinary ( q, p-q );
22860 		sIdent.ToLower();
22861 
22862 		// parse =value
22863 		while ( sphIsSpace(*p) )
22864 			p++;
22865 		if ( *p!='=' )
22866 			sphDie ( "predicted_time_costs: parse error near '%s' (expected '=' sign)", p );
22867 		p++;
22868 		while ( sphIsSpace(*p) )
22869 			p++;
22870 		if ( *p<'0' || *p>'9' )
22871 			sphDie ( "predicted_time_costs: parse error near '%s' (number expected)", p );
22872 		q = p;
22873 		while ( *p>='0' && *p<='9' )
22874 			p++;
22875 		CSphString sValue;
22876 		sValue.SetBinary ( q, p-q );
22877 		int iValue = atoi ( sValue.cstr() );
22878 
22879 		// parse comma
22880 		while ( sphIsSpace(*p) )
22881 			p++;
22882 		if ( *p && *p!=',' )
22883 			sphDie ( "predicted_time_costs: parse error near '%s' (expected ',' or end of line)", p );
22884 		p++;
22885 
22886 		// bind value
22887 		if ( sIdent=="skip" )
22888 			g_iPredictorCostSkip = iValue;
22889 		else if ( sIdent=="doc" )
22890 			g_iPredictorCostDoc = iValue;
22891 		else if ( sIdent=="hit" )
22892 			g_iPredictorCostHit = iValue;
22893 		else if ( sIdent=="match" )
22894 			g_iPredictorCostMatch = iValue;
22895 		else
22896 			sphDie ( "predicted_time_costs: unknown identifier '%s' (known ones are skip, doc, hit, match)", sIdent.cstr() );
22897 	}
22898 }
22899 
22900 
ConfigureSearchd(const CSphConfig & hConf,bool bOptPIDFile)22901 void ConfigureSearchd ( const CSphConfig & hConf, bool bOptPIDFile )
22902 {
22903 	if ( !hConf.Exists ( "searchd" ) || !hConf["searchd"].Exists ( "searchd" ) )
22904 		sphFatal ( "'searchd' config section not found in '%s'", g_sConfigFile.cstr () );
22905 
22906 	const CSphConfigSection & hSearchd = hConf["searchd"]["searchd"];
22907 
22908 	if ( !hConf.Exists ( "index" ) )
22909 		sphFatal ( "no indexes found in '%s'", g_sConfigFile.cstr () );
22910 
22911 	sphCheckDuplicatePaths ( hConf );
22912 
22913 	if ( bOptPIDFile )
22914 		if ( !hSearchd ( "pid_file" ) )
22915 			sphFatal ( "mandatory option 'pid_file' not found in 'searchd' section" );
22916 
22917 	if ( hSearchd.Exists ( "read_timeout" ) && hSearchd["read_timeout"].intval()>=0 )
22918 		g_iReadTimeout = hSearchd["read_timeout"].intval();
22919 
22920 	if ( hSearchd.Exists ( "client_timeout" ) && hSearchd["client_timeout"].intval()>=0 )
22921 		g_iClientTimeout = hSearchd["client_timeout"].intval();
22922 
22923 	if ( hSearchd.Exists ( "max_children" ) && hSearchd["max_children"].intval()>=0 )
22924 	{
22925 		g_iMaxChildren = hSearchd["max_children"].intval();
22926 		g_iPreforkChildren = g_iMaxChildren;
22927 	}
22928 
22929 	if ( hSearchd.Exists ( "persistent_connections_limit" ) && hSearchd["persistent_connections_limit"].intval()>=0 )
22930 		g_iPersistentPoolSize = hSearchd["persistent_connections_limit"].intval();
22931 
22932 	g_bPreopenIndexes = hSearchd.GetInt ( "preopen_indexes", (int)g_bPreopenIndexes )!=0;
22933 	sphSetUnlinkOld ( hSearchd.GetInt ( "unlink_old", 1 )!=0 );
22934 	g_iExpansionLimit = hSearchd.GetInt ( "expansion_limit", 0 );
22935 	g_bOnDiskAttrs = ( hSearchd.GetInt ( "ondisk_attrs_default", 0 )==1 );
22936 	g_bOnDiskPools = ( strcmp ( hSearchd.GetStr ( "ondisk_attrs_default", "" ), "pool" )==0 );
22937 
22938 	if ( hSearchd("subtree_docs_cache") )
22939 		g_iMaxCachedDocs = hSearchd.GetSize ( "subtree_docs_cache", g_iMaxCachedDocs );
22940 
22941 	if ( hSearchd("subtree_hits_cache") )
22942 		g_iMaxCachedHits = hSearchd.GetSize ( "subtree_hits_cache", g_iMaxCachedHits );
22943 
22944 	if ( hSearchd("seamless_rotate") )
22945 		g_bSeamlessRotate = ( hSearchd["seamless_rotate"].intval()!=0 );
22946 
22947 	if ( !g_bSeamlessRotate && g_bPreopenIndexes )
22948 		sphWarning ( "preopen_indexes=1 has no effect with seamless_rotate=0" );
22949 
22950 	g_iAttrFlushPeriod = hSearchd.GetInt ( "attr_flush_period", g_iAttrFlushPeriod );
22951 	g_iMaxPacketSize = hSearchd.GetSize ( "max_packet_size", g_iMaxPacketSize );
22952 	g_iMaxFilters = hSearchd.GetInt ( "max_filters", g_iMaxFilters );
22953 	g_iMaxFilterValues = hSearchd.GetInt ( "max_filter_values", g_iMaxFilterValues );
22954 	g_iMaxBatchQueries = hSearchd.GetInt ( "max_batch_queries", g_iMaxBatchQueries );
22955 	g_iDistThreads = hSearchd.GetInt ( "dist_threads", g_iDistThreads );
22956 	if ( hSearchd.Exists ( "prefork" ) )
22957 	{
22958 		g_iPreforkChildren = hSearchd.GetInt ( "prefork", g_iPreforkChildren );
22959 		sphWarning ( "'prefork' key is deprecated. Use 'max_children' instead" );
22960 	}
22961 	g_tRtThrottle.m_iMaxIOps = hSearchd.GetInt ( "rt_merge_iops", 0 );
22962 	g_tRtThrottle.m_iMaxIOSize = hSearchd.GetSize ( "rt_merge_maxiosize", 0 );
22963 	g_iPingInterval = hSearchd.GetInt ( "ha_ping_interval", 1000 );
22964 	g_uHAPeriodKarma = hSearchd.GetInt ( "ha_period_karma", 60 );
22965 	g_iQueryLogMinMs = hSearchd.GetInt ( "query_log_min_msec", g_iQueryLogMinMs );
22966 	g_iAgentConnectTimeout = hSearchd.GetInt ( "agent_connect_timeout", g_iAgentConnectTimeout );
22967 	g_iAgentQueryTimeout = hSearchd.GetInt ( "agent_query_timeout", g_iAgentQueryTimeout );
22968 	g_iAgentRetryDelay = hSearchd.GetInt ( "agent_retry_delay", g_iAgentRetryDelay );
22969 	if ( g_iAgentRetryDelay > MAX_RETRY_DELAY )
22970 		sphWarning ( "agent_retry_delay %d exceeded max recommended %d", g_iAgentRetryDelay, MAX_RETRY_DELAY );
22971 	g_iAgentRetryCount = hSearchd.GetInt ( "agent_retry_count", g_iAgentRetryCount );
22972 	if ( g_iAgentRetryCount > MAX_RETRY_COUNT )
22973 		sphWarning ( "agent_retry_count %d exceeded max recommended %d", g_iAgentRetryCount, MAX_RETRY_COUNT );
22974 
22975 	if ( hSearchd ( "collation_libc_locale" ) )
22976 	{
22977 		const char * sLocale = hSearchd.GetStr ( "collation_libc_locale" );
22978 		if ( !setlocale ( LC_COLLATE, sLocale ) )
22979 			sphWarning ( "setlocale failed (locale='%s')", sLocale );
22980 	}
22981 
22982 	if ( hSearchd ( "collation_server" ) )
22983 	{
22984 		CSphString sCollation = hSearchd.GetStr ( "collation_server" );
22985 		CSphString sError;
22986 		g_eCollation = sphCollationFromName ( sCollation, &sError );
22987 		if ( !sError.IsEmpty() )
22988 			sphWarning ( "%s", sError.cstr() );
22989 	}
22990 
22991 	if ( hSearchd("thread_stack") )
22992 	{
22993 		int iThreadStackSizeMin = 65536;
22994 		int iThreadStackSizeMax = 8*1024*1024;
22995 		int iStackSize = hSearchd.GetSize ( "thread_stack", iThreadStackSizeMin );
22996 		if ( iStackSize<iThreadStackSizeMin || iStackSize>iThreadStackSizeMax )
22997 			sphWarning ( "thread_stack %d out of bounds (64K..8M); clamped", iStackSize );
22998 
22999 		iStackSize = Min ( iStackSize, iThreadStackSizeMax );
23000 		iStackSize = Max ( iStackSize, iThreadStackSizeMin );
23001 		sphSetMyStackSize ( iStackSize );
23002 	}
23003 
23004 	if ( hSearchd("predicted_time_costs") )
23005 		ParsePredictedTimeCosts ( hSearchd["predicted_time_costs"].cstr() );
23006 
23007 	if ( hSearchd("shutdown_timeout") )
23008 	{
23009 		int iTimeout = hSearchd.GetInt ( "shutdown_timeout", 0 );
23010 		if ( iTimeout )
23011 			g_iShutdownTimeout = iTimeout * 1000000;
23012 	}
23013 
23014 	//////////////////////////////////////////////////
23015 	// prebuild MySQL wire protocol handshake packets
23016 	//////////////////////////////////////////////////
23017 
23018 	char sHandshake1[] =
23019 		"\x00\x00\x00" // packet length
23020 		"\x00" // packet id
23021 		"\x0A"; // protocol version; v.10
23022 
23023 	char sHandshake2[] =
23024 		"\x01\x00\x00\x00" // thread id
23025 		"\x01\x02\x03\x04\x05\x06\x07\x08" // scramble buffer (for auth)
23026 		"\x00" // filler
23027 		"\x08\x82" // server capabilities; CLIENT_PROTOCOL_41 | CLIENT_CONNECT_WITH_DB | CLIENT_SECURE_CONNECTION
23028 		"\x21" // server language; let it be ut8_general_ci to make different clients happy
23029 		"\x02\x00" // server status
23030 		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // filler
23031 		"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"; // scramble buffer2 (for auth, 4.1+)
23032 
23033 	const char * sVersion = hSearchd.GetStr ( "mysql_version_string", SPHINX_VERSION );
23034 	int iLen = strlen ( sVersion );
23035 
23036 	g_iMysqlHandshake = sizeof(sHandshake1) + strlen(sVersion) + sizeof(sHandshake2) - 1;
23037 	if ( g_iMysqlHandshake>=(int)sizeof(g_sMysqlHandshake) )
23038 	{
23039 		sphWarning ( "mysql_version_string too long; using default (version=%s)", SPHINX_VERSION );
23040 		g_iMysqlHandshake = sizeof(sHandshake1) + strlen(SPHINX_VERSION) + sizeof(sHandshake2) - 1;
23041 		assert ( g_iMysqlHandshake < (int)sizeof(g_sMysqlHandshake) );
23042 	}
23043 
23044 	char * p = g_sMysqlHandshake;
23045 	memcpy ( p, sHandshake1, sizeof(sHandshake1)-1 );
23046 	memcpy ( p+sizeof(sHandshake1)-1, sVersion, iLen+1 );
23047 	memcpy ( p+sizeof(sHandshake1)+iLen, sHandshake2, sizeof(sHandshake2)-1 );
23048 	g_sMysqlHandshake[0] = (char)(g_iMysqlHandshake-4); // safe, as long as buffer size is 128
23049 }
23050 
ConfigureAndPreload(const CSphConfig & hConf,const CSphVector<const char * > & dOptIndexes)23051 void ConfigureAndPreload ( const CSphConfig & hConf, const CSphVector<const char *> & dOptIndexes )
23052 {
23053 	int iCounter = 1;
23054 	int iValidIndexes = 0;
23055 	int64_t tmLoad = -sphMicroTimer();
23056 
23057 	if ( !hConf.Exists ( "index" ) )
23058 		sphFatal ( "no valid indexes to serve" );
23059 
23060 	hConf["index"].IterateStart ();
23061 	while ( hConf["index"].IterateNext() )
23062 	{
23063 		const CSphConfigSection & hIndex = hConf["index"].IterateGet();
23064 		const char * sIndexName = hConf["index"].IterateGetKey().cstr();
23065 
23066 		if ( g_bOptNoDetach && dOptIndexes.GetLength()!=0 )
23067 		{
23068 			bool bSkipIndex = true;
23069 
23070 			ARRAY_FOREACH_COND ( i, dOptIndexes, bSkipIndex )
23071 				if ( !strcasecmp ( sIndexName, dOptIndexes[i] ) )
23072 					bSkipIndex = false;
23073 
23074 			if ( bSkipIndex )
23075 				continue;
23076 		}
23077 
23078 		ESphAddIndex eAdd = AddIndex ( sIndexName, hIndex );
23079 		if ( eAdd==ADD_LOCAL || eAdd==ADD_RT )
23080 		{
23081 			ServedIndex_t & tIndex = g_pLocalIndexes->GetUnlockedEntry ( sIndexName );
23082 			iCounter++;
23083 
23084 			fprintf ( stdout, "precaching index '%s'\n", sIndexName );
23085 			fflush ( stdout );
23086 			tIndex.m_pIndex->SetProgressCallback ( ShowProgress );
23087 
23088 			if ( HasFiles ( tIndex.m_sIndexPath.cstr(), sphGetExts ( SPH_EXT_TYPE_NEW ) ) )
23089 			{
23090 				tIndex.m_bOnlyNew = !HasFiles ( tIndex.m_sIndexPath.cstr(), sphGetExts ( SPH_EXT_TYPE_CUR ) );
23091 				if ( RotateIndexGreedy ( tIndex, sIndexName ) )
23092 				{
23093 					CSphString sError;
23094 					if ( !sphFixupIndexSettings ( tIndex.m_pIndex, hIndex, sError ) )
23095 					{
23096 						sphWarning ( "index '%s': %s - NOT SERVING", sIndexName, sError.cstr() );
23097 						tIndex.m_bEnabled = false;
23098 					}
23099 				} else
23100 				{
23101 					if ( PrereadNewIndex ( tIndex, hIndex, sIndexName ) )
23102 						tIndex.m_bEnabled = true;
23103 				}
23104 			} else
23105 			{
23106 				tIndex.m_bOnlyNew = false;
23107 				if ( PrereadNewIndex ( tIndex, hIndex, sIndexName ) )
23108 					tIndex.m_bEnabled = true;
23109 			}
23110 
23111 			if ( !tIndex.m_bEnabled )
23112 				continue;
23113 
23114 			CSphString sError;
23115 			if ( !tIndex.m_sGlobalIDFPath.IsEmpty() )
23116 				if ( !sphPrereadGlobalIDF ( tIndex.m_sGlobalIDFPath, sError ) )
23117 					sphWarning ( "index '%s': global IDF unavailable - IGNORING", sIndexName );
23118 		}
23119 
23120 		if ( eAdd!=ADD_ERROR )
23121 			iValidIndexes++;
23122 	}
23123 
23124 	InitPersistentPool();
23125 
23126 	tmLoad += sphMicroTimer();
23127 	if ( !iValidIndexes )
23128 		sphFatal ( "no valid indexes to serve" );
23129 	else
23130 		fprintf ( stdout, "precached %d indexes in %0.3f sec\n", iCounter-1, float(tmLoad)/1000000 );
23131 }
23132 
OpenDaemonLog(const CSphConfigSection & hSearchd,bool bCloseIfOpened=false)23133 void OpenDaemonLog ( const CSphConfigSection & hSearchd, bool bCloseIfOpened=false )
23134 {
23135 	// create log
23136 		const char * sLog = "searchd.log";
23137 		if ( hSearchd.Exists ( "log" ) )
23138 		{
23139 			if ( hSearchd["log"]=="syslog" )
23140 			{
23141 #if !USE_SYSLOG
23142 				if ( g_iLogFile<0 )
23143 				{
23144 					g_iLogFile = STDOUT_FILENO;
23145 					sphWarning ( "failed to use syslog for logging. You have to reconfigure --with-syslog and rebuild the daemon!" );
23146 					sphInfo ( "will use default file 'searchd.log' for logging." );
23147 				}
23148 #else
23149 				g_bLogSyslog = true;
23150 #endif
23151 			} else
23152 			{
23153 				sLog = hSearchd["log"].cstr();
23154 			}
23155 		}
23156 
23157 		umask ( 066 );
23158 		if ( bCloseIfOpened && g_iLogFile!=STDOUT_FILENO )
23159 		{
23160 			close ( g_iLogFile );
23161 			g_iLogFile = STDOUT_FILENO;
23162 		}
23163 		if ( !g_bLogSyslog )
23164 		{
23165 			g_iLogFile = open ( sLog, O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
23166 			if ( g_iLogFile<0 )
23167 			{
23168 				g_iLogFile = STDOUT_FILENO;
23169 				sphFatal ( "failed to open log file '%s': %s", sLog, strerror(errno) );
23170 			}
23171 		}
23172 
23173 		g_sLogFile = sLog;
23174 		g_bLogTty = isatty ( g_iLogFile )!=0;
23175 }
23176 
23177 
ServiceMain(int argc,char ** argv)23178 int WINAPI ServiceMain ( int argc, char **argv )
23179 {
23180 	g_bLogTty = isatty ( g_iLogFile )!=0;
23181 
23182 #if USE_WINDOWS
23183 	CSphVector<char *> dArgs;
23184 	if ( g_bService )
23185 	{
23186 		g_ssHandle = RegisterServiceCtrlHandler ( g_sServiceName, ServiceControl );
23187 		if ( !g_ssHandle )
23188 			sphFatal ( "failed to start service: RegisterServiceCtrlHandler() failed: %s", WinErrorInfo() );
23189 
23190 		g_ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
23191 		MySetServiceStatus ( SERVICE_START_PENDING, NO_ERROR, 4000 );
23192 
23193 		if ( argc<=1 )
23194 		{
23195 			dArgs.Resize ( g_dArgs.GetLength() );
23196 			ARRAY_FOREACH ( i, g_dArgs )
23197 				dArgs[i] = (char*) g_dArgs[i].cstr();
23198 
23199 			argc = g_dArgs.GetLength();
23200 			argv = &dArgs[0];
23201 		}
23202 	}
23203 
23204 	char szPipeName[64];
23205 	snprintf ( szPipeName, sizeof(szPipeName), "\\\\.\\pipe\\searchd_%d", getpid() );
23206 	g_hPipe = CreateNamedPipe ( szPipeName, PIPE_ACCESS_INBOUND,
23207 		PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT,
23208 		PIPE_UNLIMITED_INSTANCES, 0, WIN32_PIPE_BUFSIZE, NMPWAIT_NOWAIT, NULL );
23209 	ConnectNamedPipe ( g_hPipe, NULL );
23210 #endif
23211 
23212 	tzset();
23213 
23214 	if ( !g_bService )
23215 		fprintf ( stdout, SPHINX_BANNER );
23216 
23217 	const char* sEndian = sphCheckEndian();
23218 	if ( sEndian )
23219 		sphDie ( "%s", sEndian );
23220 
23221 	if_const ( sizeof(SphDocID_t)==4 )
23222 		sphWarning ( "32-bit IDs are deprecated, rebuild your binaries with --enable-id64" );
23223 
23224 	//////////////////////
23225 	// parse command line
23226 	//////////////////////
23227 
23228 	CSphConfig		conf;
23229 	bool			bOptStop = false;
23230 	bool			bOptStopWait = false;
23231 	bool			bOptStatus = false;
23232 	bool			bOptPIDFile = false;
23233 	CSphVector<const char *>	dOptIndexes;
23234 
23235 	int				iOptPort = 0;
23236 	bool			bOptPort = false;
23237 
23238 	CSphString		sOptListen;
23239 	bool			bOptListen = false;
23240 	bool			bTestMode = false;
23241 
23242 	DWORD			uReplayFlags = 0;
23243 
23244 	#define OPT(_a1,_a2)	else if ( !strcmp(argv[i],_a1) || !strcmp(argv[i],_a2) )
23245 	#define OPT1(_a1)		else if ( !strcmp(argv[i],_a1) )
23246 
23247 	int i;
23248 	for ( i=1; i<argc; i++ )
23249 	{
23250 		// handle non-options
23251 		if ( argv[i][0]!='-' )		break;
23252 
23253 		// handle no-arg options
23254 		OPT ( "-h", "--help" )		{ ShowHelp(); return 0; }
23255 		OPT ( "-?", "--?" )			{ ShowHelp(); return 0; }
23256 		OPT1 ( "--console" )		{ g_eWorkers = MPM_NONE; g_bOptNoLock = true; g_bOptNoDetach = true; bTestMode = true; }
23257 		OPT1 ( "--stop" )			bOptStop = true;
23258 		OPT1 ( "--stopwait" )		{ bOptStop = true; bOptStopWait = true; }
23259 		OPT1 ( "--status" )			bOptStatus = true;
23260 		OPT1 ( "--pidfile" )		bOptPIDFile = true;
23261 		OPT1 ( "--iostats" )		g_bIOStats = true;
23262 #if !USE_WINDOWS
23263 		OPT1 ( "--cpustats" )		g_bCpuStats = true;
23264 #endif
23265 #if USE_WINDOWS
23266 		OPT1 ( "--install" )		{ if ( !g_bService ) { ServiceInstall ( argc, argv ); return 0; } }
23267 		OPT1 ( "--delete" )			{ if ( !g_bService ) { ServiceDelete (); return 0; } }
23268 		OPT1 ( "--ntservice" )		{} // it's valid but handled elsewhere
23269 #else
23270 		OPT1 ( "--nodetach" )		g_bOptNoDetach = true;
23271 #endif
23272 		OPT1 ( "--logdebug" )		g_eLogLevel = SPH_LOG_DEBUG;
23273 		OPT1 ( "--logdebugv" )		g_eLogLevel = SPH_LOG_VERBOSE_DEBUG;
23274 		OPT1 ( "--logdebugvv" )		g_eLogLevel = SPH_LOG_VERY_VERBOSE_DEBUG;
23275 		OPT1 ( "--safetrace" )		g_bSafeTrace = true;
23276 		OPT1 ( "--test" )			{ g_bWatchdog = false; bTestMode = true; } // internal option, do NOT document
23277 		OPT1 ( "--strip-path" )		g_bStripPath = true;
23278 
23279 		// FIXME! add opt=(csv)val handling here
23280 		OPT1 ( "--replay-flags=accept-desc-timestamp" )		uReplayFlags |= SPH_REPLAY_ACCEPT_DESC_TIMESTAMP;
23281 		OPT1 ( "--replay-flags=ignore-open-errors" )			uReplayFlags |= SPH_REPLAY_IGNORE_OPEN_ERROR;
23282 
23283 		// handle 1-arg options
23284 		else if ( (i+1)>=argc )		break;
23285 		OPT ( "-c", "--config" )	g_sConfigFile = argv[++i];
23286 		OPT ( "-p", "--port" )		{ bOptPort = true; iOptPort = atoi ( argv[++i] ); }
23287 		OPT ( "-l", "--listen" )	{ bOptListen = true; sOptListen = argv[++i]; }
23288 		OPT ( "-i", "--index" )		dOptIndexes.Add ( argv[++i] );
23289 #if USE_WINDOWS
23290 		OPT1 ( "--servicename" )	++i; // it's valid but handled elsewhere
23291 #endif
23292 
23293 		// handle unknown options
23294 		else
23295 			break;
23296 	}
23297 	if ( i!=argc )
23298 		sphFatal ( "malformed or unknown option near '%s'; use '-h' or '--help' to see available options.", argv[i] );
23299 
23300 #if USE_WINDOWS
23301 	// init WSA on Windows
23302 	// we need to do it this early because otherwise gethostbyname() from config parser could fail
23303 	WSADATA tWSAData;
23304 	int iStartupErr = WSAStartup ( WINSOCK_VERSION, &tWSAData );
23305 	if ( iStartupErr )
23306 		sphFatal ( "failed to initialize WinSock2: %s", sphSockError ( iStartupErr ) );
23307 
23308 #ifndef NDEBUG
23309 	// i want my windows debugging sessions to log onto stdout
23310 	g_bOptNoDetach = true;
23311 	g_bOptNoLock = true;
23312 #endif
23313 #endif
23314 
23315 	if ( !bOptPIDFile )
23316 		bOptPIDFile = !g_bOptNoLock;
23317 
23318 	// check port and listen arguments early
23319 	if ( !g_bOptNoDetach && ( bOptPort || bOptListen ) )
23320 	{
23321 		sphWarning ( "--listen and --port are only allowed in --console debug mode; switch ignored" );
23322 		bOptPort = bOptListen = false;
23323 	}
23324 
23325 	if ( bOptPort )
23326 	{
23327 		if ( bOptListen )
23328 			sphFatal ( "please specify either --port or --listen, not both" );
23329 
23330 		CheckPort ( iOptPort );
23331 	}
23332 
23333 	/////////////////////
23334 	// parse config file
23335 	/////////////////////
23336 
23337 	// fallback to defaults if there was no explicit config specified
23338 	while ( !g_sConfigFile.cstr() )
23339 	{
23340 #ifdef SYSCONFDIR
23341 		g_sConfigFile = SYSCONFDIR "/sphinx.conf";
23342 		if ( sphIsReadable ( g_sConfigFile.cstr () ) )
23343 			break;
23344 #endif
23345 
23346 		g_sConfigFile = "./sphinx.conf";
23347 		if ( sphIsReadable ( g_sConfigFile.cstr () ) )
23348 			break;
23349 
23350 		g_sConfigFile = NULL;
23351 		break;
23352 	}
23353 
23354 	if ( !g_sConfigFile.cstr () )
23355 		sphFatal ( "no readable config file (looked in "
23356 #ifdef SYSCONFDIR
23357 			SYSCONFDIR "/sphinx.conf, "
23358 #endif
23359 			"./sphinx.conf)." );
23360 
23361 	sphInfo ( "using config file '%s'...", g_sConfigFile.cstr () );
23362 
23363 	CheckConfigChanges ();
23364 
23365 	// do parse
23366 	if ( !g_pCfg.Parse ( g_sConfigFile.cstr () ) )
23367 		sphFatal ( "failed to parse config file '%s'", g_sConfigFile.cstr () );
23368 
23369 	const CSphConfig & hConf = g_pCfg.m_tConf;
23370 
23371 	if ( !hConf.Exists ( "searchd" ) || !hConf["searchd"].Exists ( "searchd" ) )
23372 		sphFatal ( "'searchd' config section not found in '%s'", g_sConfigFile.cstr () );
23373 
23374 	const CSphConfigSection & hSearchdpre = hConf["searchd"]["searchd"];
23375 
23376 	CSphString sError;
23377 	if ( !sphInitCharsetAliasTable ( sError ) )
23378 		sphFatal ( "failed to init charset alias table: %s", sError.cstr() );
23379 
23380 	////////////////////////
23381 	// stop running searchd
23382 	////////////////////////
23383 
23384 	if ( bOptStop )
23385 	{
23386 		if ( !hSearchdpre("pid_file") )
23387 			sphFatal ( "stop: option 'pid_file' not found in '%s' section 'searchd'", g_sConfigFile.cstr () );
23388 
23389 		const char * sPid = hSearchdpre["pid_file"].cstr(); // shortcut
23390 		FILE * fp = fopen ( sPid, "r" );
23391 		if ( !fp )
23392 			sphFatal ( "stop: pid file '%s' does not exist or is not readable", sPid );
23393 
23394 		char sBuf[16];
23395 		int iLen = (int) fread ( sBuf, 1, sizeof(sBuf)-1, fp );
23396 		sBuf[iLen] = '\0';
23397 		fclose ( fp );
23398 
23399 		int iPid = atoi(sBuf);
23400 		if ( iPid<=0 )
23401 			sphFatal ( "stop: failed to read valid pid from '%s'", sPid );
23402 
23403 		int iWaitTimeout = g_iShutdownTimeout + 100000;
23404 
23405 #if USE_WINDOWS
23406 		bool bTerminatedOk = false;
23407 
23408 		char szPipeName[64];
23409 		snprintf ( szPipeName, sizeof(szPipeName), "\\\\.\\pipe\\searchd_%d", iPid );
23410 
23411 		HANDLE hPipe = INVALID_HANDLE_VALUE;
23412 
23413 		while ( hPipe==INVALID_HANDLE_VALUE )
23414 		{
23415 			hPipe = CreateFile ( szPipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );
23416 
23417 			if ( hPipe==INVALID_HANDLE_VALUE )
23418 			{
23419 				if ( GetLastError()!=ERROR_PIPE_BUSY )
23420 				{
23421 					fprintf ( stdout, "WARNING: could not open pipe (GetLastError()=%d)\n", GetLastError () );
23422 					break;
23423 				}
23424 
23425 				if ( !WaitNamedPipe ( szPipeName, iWaitTimeout/1000 ) )
23426 				{
23427 					fprintf ( stdout, "WARNING: could not open pipe (GetLastError()=%d)\n", GetLastError () );
23428 					break;
23429 				}
23430 			}
23431 		}
23432 
23433 		if ( hPipe!=INVALID_HANDLE_VALUE )
23434 		{
23435 			DWORD uWritten = 0;
23436 			BYTE uWrite = 1;
23437 			BOOL bResult = WriteFile ( hPipe, &uWrite, 1, &uWritten, NULL );
23438 			if ( !bResult )
23439 				fprintf ( stdout, "WARNING: failed to send SIGHTERM to searchd (pid=%d, GetLastError()=%d)\n", iPid, GetLastError () );
23440 
23441 			bTerminatedOk = !!bResult;
23442 
23443 			CloseHandle ( hPipe );
23444 		}
23445 
23446 		if ( bTerminatedOk )
23447 		{
23448 			sphInfo ( "stop: successfully terminated pid %d", iPid );
23449 			exit ( 0 );
23450 		} else
23451 			sphFatal ( "stop: error terminating pid %d", iPid );
23452 #else
23453 		CSphString sPipeName;
23454 		int iPipeCreated = -1;
23455 		int fdPipe = -1;
23456 		if ( bOptStopWait )
23457 		{
23458 			sPipeName = GetNamedPipeName ( iPid );
23459 			iPipeCreated = mkfifo ( sPipeName.cstr(), 0666 );
23460 			if ( iPipeCreated!=-1 )
23461 				fdPipe = ::open ( sPipeName.cstr(), O_RDONLY | O_NONBLOCK );
23462 
23463 			if ( iPipeCreated==-1 )
23464 				sphWarning ( "mkfifo failed (path=%s, err=%d, msg=%s); will NOT wait", sPipeName.cstr(), errno, strerror(errno) );
23465 			else if ( fdPipe<0 )
23466 				sphWarning ( "open failed (path=%s, err=%d, msg=%s); will NOT wait", sPipeName.cstr(), errno, strerror(errno) );
23467 		}
23468 
23469 		if ( kill ( iPid, SIGTERM ) )
23470 			sphFatal ( "stop: kill() on pid %d failed: %s", iPid, strerror(errno) );
23471 		else
23472 			sphInfo ( "stop: successfully sent SIGTERM to pid %d", iPid );
23473 
23474 		int iExitCode = ( bOptStopWait && ( iPipeCreated==-1 || fdPipe<0 ) ) ? 1 : 0;
23475 		bool bHandshake = true;
23476 		while ( bOptStopWait && fdPipe>=0 )
23477 		{
23478 			int iReady = sphPoll ( fdPipe, iWaitTimeout );
23479 
23480 			// error on wait
23481 			if ( iReady<0 )
23482 			{
23483 				iExitCode = 3;
23484 				sphWarning ( "stopwait%s error '%s'", ( bHandshake ? " handshake" : " " ), strerror(errno) );
23485 				break;
23486 			}
23487 
23488 			// timeout
23489 			if ( iReady==0 )
23490 			{
23491 				if ( !bHandshake )
23492 					continue;
23493 
23494 				iExitCode = 1;
23495 				break;
23496 			}
23497 
23498 			// reading data
23499 			DWORD uStatus = 0;
23500 			int iRead = ::read ( fdPipe, &uStatus, sizeof(DWORD) );
23501 			if ( iRead!=sizeof(DWORD) )
23502 			{
23503 				sphWarning ( "stopwait read fifo error '%s'", strerror(errno) );
23504 				iExitCode = 3; // stopped demon crashed during stop
23505 				break;
23506 			} else
23507 			{
23508 				iExitCode = ( uStatus==1 ? 0 : 2 ); // uStatus == 1 - AttributeSave - ok, other values - error
23509 			}
23510 
23511 			if ( !bHandshake )
23512 				break;
23513 
23514 			bHandshake = false;
23515 		}
23516 		if ( fdPipe>=0 )
23517 			::close ( fdPipe );
23518 		if ( iPipeCreated!=-1 )
23519 			::unlink ( sPipeName.cstr() );
23520 
23521 		exit ( iExitCode );
23522 #endif
23523 	}
23524 
23525 	////////////////////////////////
23526 	// query running searchd status
23527 	////////////////////////////////
23528 
23529 	if ( bOptStatus )
23530 	{
23531 		QueryStatus ( hSearchdpre("listen") );
23532 		exit ( 0 );
23533 	}
23534 
23535 	/////////////////////
23536 	// configure searchd
23537 	/////////////////////
23538 
23539 	ConfigureSearchd ( hConf, bOptPIDFile );
23540 	sphConfigureCommon ( hConf ); // this also inits plugins now
23541 
23542 	g_bWatchdog = hSearchdpre.GetInt ( "watchdog", g_bWatchdog )!=0;
23543 
23544 	if ( hSearchdpre("workers") )
23545 	{
23546 		if ( hSearchdpre["workers"]=="none" )
23547 			g_eWorkers = MPM_NONE;
23548 		else if ( hSearchdpre["workers"]=="fork" )
23549 			g_eWorkers = MPM_FORK;
23550 		else if ( hSearchdpre["workers"]=="prefork" )
23551 			g_eWorkers = MPM_PREFORK;
23552 		else if ( hSearchdpre["workers"]=="threads" )
23553 			g_eWorkers = MPM_THREADS;
23554 		else
23555 			sphFatal ( "unknown workers=%s value", hSearchdpre["workers"].cstr() );
23556 	}
23557 #if USE_WINDOWS
23558 	if ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK )
23559 		sphFatal ( "workers=fork and workers=prefork are not supported on Windows" );
23560 #endif
23561 
23562 	if ( g_iMaxPacketSize<128*1024 || g_iMaxPacketSize>128*1024*1024 )
23563 		sphFatal ( "max_packet_size out of bounds (128K..128M)" );
23564 
23565 	if ( g_iMaxFilters<1 || g_iMaxFilters>10240 )
23566 		sphFatal ( "max_filters out of bounds (1..10240)" );
23567 
23568 	if ( g_iMaxFilterValues<1 || g_iMaxFilterValues>10485760 )
23569 		sphFatal ( "max_filter_values out of bounds (1..10485760)" );
23570 
23571 	bool bVisualLoad = true;
23572 	bool bWatched = false;
23573 #if !USE_WINDOWS
23574 	// Let us start watchdog right now, on foreground first.
23575 	int iDevNull = open ( "/dev/null", O_RDWR );
23576 	if ( g_bWatchdog && g_eWorkers==MPM_THREADS && !g_bOptNoDetach )
23577 	{
23578 		bWatched = true;
23579 		if ( !g_bOptNoLock )
23580 			OpenDaemonLog ( hConf["searchd"]["searchd"] );
23581 		bVisualLoad = SetWatchDog ( iDevNull );
23582 		OpenDaemonLog ( hConf["searchd"]["searchd"], true ); // just the 'IT Happens' magic - switch off, then on.
23583 	}
23584 #endif
23585 
23586 	// here we either since plain startup, either being resurrected (forked) by watchdog.
23587 	// create the pid
23588 	if ( bOptPIDFile )
23589 	{
23590 		g_sPidFile = hSearchdpre["pid_file"].cstr();
23591 
23592 		g_iPidFD = ::open ( g_sPidFile.scstr(), O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
23593 		if ( g_iPidFD<0 )
23594 			sphFatal ( "failed to create pid file '%s': %s", g_sPidFile.scstr(), strerror(errno) );
23595 	}
23596 	if ( bOptPIDFile && !sphLockEx ( g_iPidFD, false ) )
23597 		sphFatal ( "failed to lock pid file '%s': %s (searchd already running?)", g_sPidFile.scstr(), strerror(errno) );
23598 
23599 	// Actions on resurrection
23600 	if ( bWatched && !bVisualLoad && CheckConfigChanges() )
23601 	{
23602 		// reparse the config file
23603 		sphInfo ( "Reloading the config" );
23604 		if ( !g_pCfg.ReParse ( g_sConfigFile.cstr () ) )
23605 			sphFatal ( "failed to parse config file '%s'", g_sConfigFile.cstr () );
23606 
23607 		sphInfo ( "Reconfigure the daemon" );
23608 		ConfigureSearchd ( hConf, bOptPIDFile );
23609 	}
23610 
23611 	// hSearchdpre might be dead if we reloaded the config.
23612 	const CSphConfigSection & hSearchd = hConf["searchd"]["searchd"];
23613 
23614 	// handle my signals
23615 	SetSignalHandlers ( g_bOptNoDetach );
23616 
23617 	// create logs
23618 	if ( !g_bOptNoLock )
23619 	{
23620 		// create log
23621 		OpenDaemonLog ( hSearchd, true );
23622 
23623 		// create query log if required
23624 		if ( hSearchd.Exists ( "query_log" ) )
23625 		{
23626 			if ( hSearchd["query_log"]=="syslog" )
23627 				g_bQuerySyslog = true;
23628 			else
23629 			{
23630 				g_iQueryLogFile = open ( hSearchd["query_log"].cstr(), O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
23631 				if ( g_iQueryLogFile<0 )
23632 					sphFatal ( "failed to open query log file '%s': %s", hSearchd["query_log"].cstr(), strerror(errno) );
23633 			}
23634 			g_sQueryLogFile = hSearchd["query_log"].cstr();
23635 		}
23636 	}
23637 
23638 	//////////////////////////////////////////////////
23639 	// shared stuff (perf counters, flushing) startup
23640 	//////////////////////////////////////////////////
23641 
23642 	g_pStats = InitSharedBuffer ( g_tStatsBuffer );
23643 	g_pFlush = InitSharedBuffer ( g_tFlushBuffer );
23644 	g_pStats->m_uStarted = (DWORD)time(NULL);
23645 
23646 	// g_pConnId for prefork will be initialized later, together with mutex
23647 
23648 	if ( g_eWorkers==MPM_THREADS )
23649 	{
23650 		if ( !sphThreadKeyCreate ( &g_tConnKey ) )
23651 			sphFatal ( "failed to create TLS for connection ID" );
23652 	}
23653 
23654 	////////////////////
23655 	// network startup
23656 	////////////////////
23657 
23658 	Listener_t tListener;
23659 	tListener.m_eProto = PROTO_SPHINX;
23660 	tListener.m_bTcp = true;
23661 
23662 	// command line arguments override config (but only in --console)
23663 	if ( bOptListen )
23664 	{
23665 		AddListener ( sOptListen );
23666 
23667 	} else if ( bOptPort )
23668 	{
23669 		tListener.m_iSock = sphCreateInetSocket ( htonl ( INADDR_ANY ), iOptPort );
23670 		g_dListeners.Add ( tListener );
23671 
23672 	} else
23673 	{
23674 		// listen directives in configuration file
23675 		for ( CSphVariant * v = hSearchd("listen"); v; v = v->m_pNext )
23676 			AddListener ( v->strval() );
23677 
23678 		// default is to listen on our two ports
23679 		if ( !g_dListeners.GetLength() )
23680 		{
23681 			tListener.m_iSock = sphCreateInetSocket ( htonl ( INADDR_ANY ), SPHINXAPI_PORT );
23682 			tListener.m_eProto = PROTO_SPHINX;
23683 			g_dListeners.Add ( tListener );
23684 
23685 			tListener.m_iSock = sphCreateInetSocket ( htonl ( INADDR_ANY ), SPHINXQL_PORT );
23686 			tListener.m_eProto = PROTO_MYSQL41;
23687 			g_dListeners.Add ( tListener );
23688 		}
23689 	}
23690 
23691 #if !USE_WINDOWS
23692 	// reserve an fd for clients
23693 
23694 	g_iClientFD = dup ( iDevNull );
23695 #endif
23696 
23697 	g_pLocalIndexes = new IndexHash_c();
23698 	g_pTemplateIndexes = new IndexHash_c();
23699 	//////////////////////
23700 	// build indexes hash
23701 	//////////////////////
23702 
23703 	// setup mva updates arena here, since we could have saved persistent mva updates
23704 	const char * sArenaError = sphArenaInit ( hSearchd.GetSize ( "mva_updates_pool", MVA_UPDATES_POOL ) );
23705 	if ( sArenaError )
23706 		sphWarning ( "process shared mutex unsupported, MVA update disabled ( %s )", sArenaError );
23707 
23708 	// configure and preload
23709 
23710 	ConfigureAndPreload ( hConf, dOptIndexes );
23711 
23712 	///////////
23713 	// startup
23714 	///////////
23715 
23716 	if ( g_eWorkers==MPM_THREADS )
23717 		sphRTInit ( hSearchd, bTestMode );
23718 
23719 	if ( hSearchd.Exists ( "snippets_file_prefix" ) )
23720 		g_sSnippetsFilePrefix = hSearchd["snippets_file_prefix"].cstr();
23721 	else
23722 		g_sSnippetsFilePrefix = "";
23723 
23724 	const char* sLogFormat = hSearchd.GetStr ( "query_log_format", "plain" );
23725 	if ( !strcmp ( sLogFormat, "sphinxql" ) )
23726 		g_eLogFormat = LOG_FORMAT_SPHINXQL;
23727 	else if ( strcmp ( sLogFormat, "plain" ) )
23728 	{
23729 		CSphVector<CSphString> dParams;
23730 		sphSplit ( dParams, sLogFormat );
23731 		ARRAY_FOREACH ( j, dParams )
23732 		{
23733 			sLogFormat = dParams[j].cstr();
23734 			if ( !strcmp ( sLogFormat, "sphinxql" ) )
23735 				g_eLogFormat = LOG_FORMAT_SPHINXQL;
23736 			else if ( !strcmp ( sLogFormat, "plain" ) )
23737 				g_eLogFormat = LOG_FORMAT_PLAIN;
23738 			else if ( !strcmp ( sLogFormat, "compact_in" ) )
23739 				g_bShortenIn = true;
23740 		}
23741 		if ( g_bShortenIn && g_eLogFormat==LOG_FORMAT_PLAIN )
23742 			sphWarning ( "combination of query_log_format=plain and option compact_in is not supported." );
23743 	}
23744 
23745 	// prepare to detach
23746 	if ( !g_bOptNoDetach )
23747 	{
23748 #if !USE_WINDOWS
23749 		if ( !bWatched || bVisualLoad )
23750 		{
23751 			close ( STDIN_FILENO );
23752 			close ( STDOUT_FILENO );
23753 			close ( STDERR_FILENO );
23754 			dup2 ( iDevNull, STDIN_FILENO );
23755 			dup2 ( iDevNull, STDOUT_FILENO );
23756 			dup2 ( iDevNull, STDERR_FILENO );
23757 		}
23758 #endif
23759 		ReleaseTTYFlag();
23760 
23761 		// explicitly unlock everything in parent immediately before fork
23762 		//
23763 		// there's a race in case another instance is started before
23764 		// child re-acquires all locks; but let's hope that's rare
23765 		if ( !bWatched )
23766 		{
23767 			for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
23768 			{
23769 				ServedIndex_t & tServed = it.Get();
23770 				if ( tServed.m_bEnabled )
23771 					tServed.m_pIndex->Unlock();
23772 			}
23773 			for ( IndexHashIterator_c it ( g_pTemplateIndexes ); it.Next(); )
23774 			{
23775 				ServedIndex_t & tServed = it.Get();
23776 				if ( tServed.m_bEnabled )
23777 					tServed.m_pIndex->Unlock();
23778 			}
23779 		}
23780 	}
23781 
23782 	if ( bOptPIDFile && !bWatched )
23783 		sphLockUn ( g_iPidFD );
23784 
23785 #if !USE_WINDOWS
23786 	if ( !g_bOptNoDetach && !bWatched )
23787 	{
23788 		switch ( fork() )
23789 		{
23790 			case -1:
23791 				// error
23792 				Shutdown ();
23793 				sphFatal ( "fork() failed (reason: %s)", strerror ( errno ) );
23794 				exit ( 1 );
23795 
23796 			case 0:
23797 				// daemonized child
23798 				break;
23799 
23800 			default:
23801 				// tty-controlled parent
23802 				sphSetProcessInfo ( false );
23803 				exit ( 0 );
23804 		}
23805 	}
23806 #endif
23807 
23808 	if ( g_eWorkers==MPM_THREADS )
23809 		sphRTConfigure ( hSearchd, bTestMode );
23810 
23811 	if ( bOptPIDFile )
23812 	{
23813 #if !USE_WINDOWS
23814 		// re-lock pid
23815 		// FIXME! there's a potential race here
23816 		if ( !sphLockEx ( g_iPidFD, true ) )
23817 			sphFatal ( "failed to re-lock pid file '%s': %s", g_sPidFile.scstr(), strerror(errno) );
23818 #endif
23819 
23820 		char sPid[16];
23821 		snprintf ( sPid, sizeof(sPid), "%d\n", (int)getpid() );
23822 		int iPidLen = strlen(sPid);
23823 
23824 		sphSeek ( g_iPidFD, 0, SEEK_SET );
23825 		if ( !sphWrite ( g_iPidFD, sPid, iPidLen ) )
23826 			sphFatal ( "failed to write to pid file '%s' (errno=%d, msg=%s)", g_sPidFile.scstr(),
23827 				errno, strerror(errno) );
23828 
23829 		if ( ::ftruncate ( g_iPidFD, iPidLen ) )
23830 			sphFatal ( "failed to truncate pid file '%s' (errno=%d, msg=%s)", g_sPidFile.scstr(),
23831 				errno, strerror(errno) );
23832 	}
23833 
23834 #if USE_WINDOWS
23835 	SetConsoleCtrlHandler ( CtrlHandler, TRUE );
23836 #endif
23837 
23838 	if ( !g_bOptNoDetach && !bWatched )
23839 	{
23840 		// re-lock indexes
23841 		for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
23842 		{
23843 			ServedIndex_t & tServed = it.Get();
23844 			if ( !tServed.m_bEnabled )
23845 				continue;
23846 
23847 			// obtain exclusive lock
23848 			if ( !tServed.m_pIndex->Lock() )
23849 			{
23850 				sphWarning ( "index '%s': lock: %s; INDEX UNUSABLE", it.GetKey().cstr(),
23851 					tServed.m_pIndex->GetLastError().cstr() );
23852 				tServed.m_bEnabled = false;
23853 				continue;
23854 			}
23855 
23856 			// try to mlock again because mlock does not survive over fork
23857 			if ( !tServed.m_pIndex->Mlock() )
23858 			{
23859 				sphWarning ( "index '%s': %s", it.GetKey().cstr(),
23860 					tServed.m_pIndex->GetLastError().cstr() );
23861 			}
23862 		}
23863 	}
23864 
23865 	// if we're running in console mode, dump queries to tty as well
23866 	if ( g_bOptNoLock && hSearchd ( "query_log" ) )
23867 	{
23868 		g_bQuerySyslog = false;
23869 		g_bLogSyslog = false;
23870 		g_iQueryLogFile = g_iLogFile;
23871 	}
23872 
23873 #if USE_SYSLOG
23874 	if ( g_bLogSyslog || g_bQuerySyslog )
23875 	{
23876 		openlog ( "searchd", LOG_PID, LOG_DAEMON );
23877 	}
23878 #else
23879 	if ( g_bQuerySyslog )
23880 		sphFatal ( "Wrong query_log file! You have to reconfigure --with-syslog and rebuild daemon if you want to use syslog there." );
23881 #endif
23882 	/////////////////
23883 	// serve clients
23884 	/////////////////
23885 
23886 	g_bHeadDaemon = true;
23887 
23888 #if USE_WINDOWS
23889 	if ( g_bService )
23890 		MySetServiceStatus ( SERVICE_RUNNING, NO_ERROR, 0 );
23891 #endif
23892 
23893 	sphSetReadBuffers ( hSearchd.GetSize ( "read_buffer", 0 ), hSearchd.GetSize ( "read_unhinted", 0 ) );
23894 
23895 	CSphProcessSharedMutex * pAcceptMutex = NULL;
23896 #if !USE_WINDOWS
23897 	if ( g_eWorkers==MPM_PREFORK )
23898 	{
23899 		pAcceptMutex = new CSphProcessSharedMutex ( sizeof(g_iConnID) );
23900 		if ( !pAcceptMutex )
23901 			sphFatal ( "failed to create process-shared mutex" );
23902 
23903 		g_pConnID = (int*) pAcceptMutex->GetSharedData();
23904 
23905 		if ( !pAcceptMutex->GetError() )
23906 		{
23907 			while ( g_dChildren.GetLength() < g_iPreforkChildren )
23908 			{
23909 				if ( PreforkChild()==0 ) // child process? break from here, go work
23910 					break;
23911 			}
23912 
23913 			g_iRotationThrottle = hSearchd.GetInt ( "prefork_rotation_throttle", 0 );
23914 		} else
23915 		{
23916 			sphWarning ( "process shared mutex unsupported, switching to 'workers = fork' ( %s )", pAcceptMutex->GetError() );
23917 			g_eWorkers = MPM_FORK;
23918 			SafeDelete ( pAcceptMutex );
23919 		}
23920 	}
23921 #endif
23922 
23923 	g_pPersistentInUse = new InterWorkerStorage;
23924 	g_pPersistentInUse->Init ( sizeof(DWORD) );
23925 	{
23926 		CSphScopedLockedShare<InterWorkerStorage> dPersNum ( *g_pPersistentInUse );
23927 		dPersNum.SharedValue<DWORD>() = 0;
23928 	}
23929 
23930 	// in threaded mode, create a dedicated rotation thread
23931 	if ( g_eWorkers==MPM_THREADS )
23932 	{
23933 		if ( g_bSeamlessRotate && !sphThreadCreate ( &g_tRotateThread, RotationThreadFunc, 0 ) )
23934 			sphDie ( "failed to create rotation thread" );
23935 	}
23936 
23937 	// replay last binlog
23938 	SmallStringHash_T<CSphIndex*> hIndexes;
23939 	for ( IndexHashIterator_c it ( g_pLocalIndexes ); it.Next(); )
23940 		if ( it.Get().m_bEnabled )
23941 			hIndexes.Add ( it.Get().m_pIndex, it.GetKey() );
23942 
23943 	if ( g_eWorkers==MPM_THREADS )
23944 		sphReplayBinlog ( hIndexes, uReplayFlags, DumpMemStat );
23945 	hIndexes.Reset();
23946 
23947 	if ( !g_bOptNoDetach )
23948 		g_bLogStdout = false;
23949 
23950 	if ( g_bIOStats && !sphInitIOStats () )
23951 		sphWarning ( "unable to init IO statistics" );
23952 
23953 	// threads mode
23954 	// create optimize and flush threads, and load saved sphinxql state
23955 	if ( g_eWorkers==MPM_THREADS )
23956 	{
23957 		if ( !sphThreadCreate ( &g_tRtFlushThread, RtFlushThreadFunc, 0 ) )
23958 			sphDie ( "failed to create rt-flush thread" );
23959 
23960 		if ( !sphThreadCreate ( &g_tOptimizeThread, OptimizeThreadFunc, 0 ) )
23961 			sphDie ( "failed to create optimize thread" );
23962 
23963 		g_sSphinxqlState = hSearchd.GetStr ( "sphinxql_state" );
23964 		if ( !g_sSphinxqlState.IsEmpty() )
23965 		{
23966 			SphinxqlStateRead ( g_sSphinxqlState );
23967 			g_tmSphinxqlState = sphMicroTimer();
23968 
23969 			CSphString sError;
23970 			CSphWriter tWriter;
23971 			CSphString sNewState;
23972 			sNewState.SetSprintf ( "%s.new", g_sSphinxqlState.cstr() );
23973 			// initial check that work can be done
23974 			bool bCanWrite = tWriter.OpenFile ( sNewState, sError );
23975 			tWriter.CloseFile();
23976 			::unlink ( sNewState.cstr() );
23977 
23978 			if ( !bCanWrite )
23979 				sphWarning ( "sphinxql_state flush disabled: %s", sError.cstr() );
23980 			else if ( !sphThreadCreate ( &g_tSphinxqlStateFlushThread, SphinxqlStateThreadFunc, NULL ) )
23981 				sphDie ( "failed to create sphinxql_state writer thread" );
23982 		}
23983 	}
23984 
23985 	// fork/prefork mode
23986 	// load plugins from sphinxql state, then disable dynamic CREATE/DROP FUNCTION/RANKER
23987 	if ( g_eWorkers==MPM_FORK || g_eWorkers==MPM_PREFORK )
23988 	{
23989 		g_sSphinxqlState = hSearchd.GetStr ( "sphinxql_state" );
23990 		if ( !g_sSphinxqlState.IsEmpty() )
23991 			SphinxqlStateRead ( g_sSphinxqlState );
23992 		sphPluginLock ( true );
23993 	}
23994 
23995 	if ( !sphThreadCreate ( &g_tRotationServiceThread, RotationServiceThreadFunc, 0 ) )
23996 		sphDie ( "failed to create rotation service thread" );
23997 
23998 	if ( !sphThreadCreate ( &g_tPingThread, PingThreadFunc, 0 ) )
23999 		sphDie ( "failed to create ping service thread" );
24000 
24001 	// almost ready, time to start listening
24002 	int iBacklog = hSearchd.GetInt ( "listen_backlog", SEARCHD_BACKLOG );
24003 	ARRAY_FOREACH ( j, g_dListeners )
24004 		if ( listen ( g_dListeners[j].m_iSock, iBacklog )==-1 )
24005 			sphFatal ( "listen() failed: %s", sphSockError() );
24006 
24007 	// crash logging for the main thread (for cases like --console and workers={fork|prefork})
24008 	SphCrashLogger_c tQueryTLS;
24009 	tQueryTLS.SetupTLS ();
24010 
24011 	sphInfo ( "accepting connections" );
24012 	for ( ;; )
24013 	{
24014 		SphCrashLogger_c::SetupTimePID();
24015 
24016 		if ( !g_bHeadDaemon && pAcceptMutex )
24017 			TickPreforked ( pAcceptMutex );
24018 		else
24019 			TickHead ( pAcceptMutex!=0 );
24020 	}
24021 } // NOLINT function length
24022 
24023 
DieCallback(const char * sMessage)24024 bool DieCallback ( const char * sMessage )
24025 {
24026 	sphLogFatal ( "%s", sMessage );
24027 	return false; // caller should not log
24028 }
24029 
24030 
UservarsHook(const CSphString & sUservar)24031 UservarIntSet_c * UservarsHook ( const CSphString & sUservar )
24032 {
24033 	CSphScopedLock<StaticThreadsOnlyMutex_t> tLock ( g_tUservarsMutex );
24034 
24035 	Uservar_t * pVar = g_hUservars ( sUservar );
24036 	if ( !pVar )
24037 		return NULL;
24038 
24039 	assert ( pVar->m_eType==USERVAR_INT_SET );
24040 	pVar->m_pVal->AddRef();
24041 	return pVar->m_pVal;
24042 }
24043 
24044 
main(int argc,char ** argv)24045 int main ( int argc, char **argv )
24046 {
24047 	// threads should be initialized before memory allocations
24048 	char cTopOfMainStack;
24049 	sphThreadInit();
24050 	MemorizeStack ( &cTopOfMainStack );
24051 
24052 	sphSetDieCallback ( DieCallback );
24053 	sphSetLogger ( sphLog );
24054 	g_pUservarsHook = UservarsHook;
24055 	sphCollationInit ();
24056 	sphBacktraceSetBinaryName ( argv[0] );
24057 	GeodistInit();
24058 
24059 #if USE_WINDOWS
24060 	int iNameIndex = -1;
24061 	for ( int i=1; i<argc; i++ )
24062 	{
24063 		if ( strcmp ( argv[i], "--ntservice" )==0 )
24064 			g_bService = true;
24065 
24066 		if ( strcmp ( argv[i], "--servicename" )==0 && (i+1)<argc )
24067 		{
24068 			iNameIndex = i+1;
24069 			g_sServiceName = argv[iNameIndex];
24070 		}
24071 	}
24072 
24073 	if ( g_bService )
24074 	{
24075 		for ( int i=0; i<argc; i++ )
24076 			g_dArgs.Add ( argv[i] );
24077 
24078 		if ( iNameIndex>=0 )
24079 			g_sServiceName = g_dArgs[iNameIndex].cstr ();
24080 
24081 		SERVICE_TABLE_ENTRY dDispatcherTable[] =
24082 		{
24083 			{ (LPSTR) g_sServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
24084 			{ NULL, NULL }
24085 		};
24086 		if ( !StartServiceCtrlDispatcher ( dDispatcherTable ) )
24087 			sphFatal ( "StartServiceCtrlDispatcher() failed: %s", WinErrorInfo() );
24088 	} else
24089 #endif
24090 
24091 	return ServiceMain ( argc, argv );
24092 }
24093 
24094 //
24095 // $Id$
24096 //
24097