1 //
2 // $Id: snippets_udf.cc 3508 2012-11-05 11:48:48Z kevg $
3 //
4 
5 //
6 // Copyright (c) 2001-2012, Andrew Aksyonoff
7 // Copyright (c) 2008-2012, 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 <stdio.h>
17 #include <string.h>
18 #include <assert.h>
19 
20 #include <sys/un.h>
21 #include <netdb.h>
22 
23 #include <mysql_version.h>
24 
25 #if MYSQL_VERSION_ID>=50515
26 #include "sql_class.h"
27 #include "sql_array.h"
28 #elif MYSQL_VERSION_ID>50100
29 #include "mysql_priv.h"
30 #include <mysql/plugin.h>
31 #else
32 #include "../mysql_priv.h"
33 #endif
34 
35 #include <mysys_err.h>
36 #include <my_sys.h>
37 
38 #if MYSQL_VERSION_ID>=50120
39 typedef uchar byte;
40 #endif
41 
42 /// partially copy-pasted stuff that should be moved elsewhere
43 
44 #if UNALIGNED_RAM_ACCESS
45 
46 /// pass-through wrapper
sphUnalignedRead(const T & tRef)47 template < typename T > inline T sphUnalignedRead ( const T & tRef )
48 {
49 	return tRef;
50 }
51 
52 /// pass-through wrapper
sphUnalignedWrite(void * pPtr,const T & tVal)53 template < typename T > void sphUnalignedWrite ( void * pPtr, const T & tVal )
54 {
55 	*(T*)pPtr = tVal;
56 }
57 
58 #else
59 
60 /// unaligned read wrapper for some architectures (eg. SPARC)
61 template < typename T >
sphUnalignedRead(const T & tRef)62 inline T sphUnalignedRead ( const T & tRef )
63 {
64 	T uTmp;
65 	byte * pSrc = (byte *) &tRef;
66 	byte * pDst = (byte *) &uTmp;
67 	for ( int i=0; i<(int)sizeof(T); i++ )
68 		*pDst++ = *pSrc++;
69 	return uTmp;
70 }
71 
72 /// unaligned write wrapper for some architectures (eg. SPARC)
73 template < typename T >
sphUnalignedWrite(void * pPtr,const T & tVal)74 void sphUnalignedWrite ( void * pPtr, const T & tVal )
75 {
76 	byte * pDst = (byte *) pPtr;
77 	byte * pSrc = (byte *) &tVal;
78 	for ( int i=0; i<(int)sizeof(T); i++ )
79 		*pDst++ = *pSrc++;
80 }
81 
82 #endif
83 
84 #define SPHINXSE_MAX_ALLOC			(16*1024*1024)
85 
86 #define SafeDelete(_arg)		{ if ( _arg ) delete ( _arg );		(_arg) = NULL; }
87 #define SafeDeleteArray(_arg)	{ if ( _arg ) delete [] ( _arg );	(_arg) = NULL; }
88 
89 #define Min(a,b) ((a)<(b)?(a):(b))
90 
91 typedef unsigned int DWORD;
92 
sphF2DW(float f)93 inline DWORD sphF2DW ( float f ) { union { float f; uint32 d; } u; u.f = f; return u.d; }
94 
sphDup(const char * sSrc,int iLen=-1)95 static char * sphDup ( const char * sSrc, int iLen=-1 )
96 {
97 	if ( !sSrc )
98 		return NULL;
99 
100 	if ( iLen<0 )
101 		iLen = strlen(sSrc);
102 
103 	char * sRes = new char [ 1+iLen ];
104 	memcpy ( sRes, sSrc, iLen );
105 	sRes[iLen] = '\0';
106 	return sRes;
107 }
108 
sphShowErrno(const char * sCall)109 static inline void sphShowErrno ( const char * sCall )
110 {
111 	char sError[256];
112 	snprintf ( sError, sizeof(sError), "%s() failed: [%d] %s", sCall, errno, strerror(errno) );
113 	my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sError );
114 }
115 
116 static const bool sphReportErrors = true;
117 
sphSend(int iFd,const char * pBuffer,int iSize,bool bReportErrors=false)118 static bool sphSend ( int iFd, const char * pBuffer, int iSize, bool bReportErrors = false )
119 {
120 	assert ( pBuffer );
121 	assert ( iSize > 0 );
122 
123 	const int iResult = send ( iFd, pBuffer, iSize, 0 );
124 	if ( iResult!=iSize )
125 	{
126 		if ( bReportErrors ) sphShowErrno("send");
127 		return false;
128 	}
129 	return true;
130 }
131 
sphRecv(int iFd,char * pBuffer,int iSize,bool bReportErrors=false)132 static bool sphRecv ( int iFd, char * pBuffer, int iSize, bool bReportErrors = false )
133 {
134 	assert ( pBuffer );
135 	assert ( iSize > 0 );
136 
137 	while ( iSize )
138 	{
139 		const int iResult = recv ( iFd, pBuffer, iSize, 0 );
140 		if ( iResult > 0 )
141 		{
142 			iSize -= iResult;
143 			pBuffer += iSize;
144 		} else if ( iResult==0 )
145 		{
146 			if ( bReportErrors )
147 				my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), "recv() failed: disconnected" );
148 			return false;
149 		} else
150 		{
151 			if ( bReportErrors ) sphShowErrno("recv");
152 			return false;
153 		}
154 	}
155 	return true;
156 }
157 
158 enum
159 {
160 	SPHINX_SEARCHD_PROTO		= 1,
161 
162 	SEARCHD_COMMAND_EXCERPT		= 1,
163 
164 	VER_COMMAND_EXCERPT		= 0x104,
165 };
166 
167 /// known answers
168 enum
169 {
170 	SEARCHD_OK		= 0,	///< general success, command-specific reply follows
171 	SEARCHD_ERROR	= 1,	///< general failure, error message follows
172 	SEARCHD_RETRY	= 2,	///< temporary failure, error message follows, client should retry later
173 	SEARCHD_WARNING	= 3		///< general success, warning message and command-specific reply follow
174 };
175 
176 #define SPHINXSE_DEFAULT_SCHEME		"sphinx"
177 #define SPHINXSE_DEFAULT_HOST		"127.0.0.1"
178 #define SPHINXSE_DEFAULT_PORT		9312
179 #define SPHINXSE_DEFAULT_INDEX		"*"
180 
181 class CSphBuffer
182 {
183 private:
184 	bool m_bOverrun;
185 	int m_iSize;
186 	int m_iLeft;
187 	char * m_pBuffer;
188 	char * m_pCurrent;
189 
190 public:
CSphBuffer(const int iSize)191 	explicit CSphBuffer ( const int iSize )
192 		: m_bOverrun ( false )
193 		, m_iSize ( iSize )
194 		, m_iLeft ( iSize )
195 	{
196 		assert ( iSize > 0 );
197 		m_pBuffer = new char[iSize];
198 		m_pCurrent = m_pBuffer;
199 	}
200 
~CSphBuffer()201 	~CSphBuffer ()
202 	{
203 		SafeDeleteArray ( m_pBuffer );
204 	}
205 
Ptr() const206 	const char * Ptr() const { return m_pBuffer; }
207 
Finalize()208 	bool Finalize()
209 	{
210 		return !( m_bOverrun || m_iLeft!=0 || ( m_pCurrent - m_pBuffer )!=m_iSize );
211 	}
212 
213 	void SendBytes ( const void * pBytes, int iBytes );
214 
SendWord(short int v)215 	void SendWord ( short int v )					{ v = ntohs(v); SendBytes ( &v, sizeof(v) ); } // NOLINT
SendInt(int v)216 	void SendInt ( int v )							{ v = ntohl(v); SendBytes ( &v, sizeof(v) ); }
SendDword(DWORD v)217 	void SendDword ( DWORD v )						{ v = ntohl(v) ;SendBytes ( &v, sizeof(v) ); }
SendUint64(ulonglong v)218 	void SendUint64 ( ulonglong v )					{ SendDword ( uint ( v>>32 ) ); SendDword ( uint ( v&0xFFFFFFFFUL ) ); }
SendString(const char * v)219 	void SendString ( const char * v )				{ SendString ( v, strlen(v) ); }
SendString(const char * v,int iLen)220 	void SendString ( const char * v, int iLen )	{ SendDword(iLen); SendBytes ( v, iLen ); }
SendFloat(float v)221 	void SendFloat ( float v )						{ SendDword ( sphF2DW(v) ); }
222 };
223 
SendBytes(const void * pBytes,int iBytes)224 void CSphBuffer::SendBytes ( const void * pBytes, int iBytes )
225 {
226 	if ( m_iLeft < iBytes )
227 	{
228 		m_bOverrun = true;
229 		return;
230 	}
231 
232 	memcpy ( m_pCurrent, pBytes, iBytes );
233 
234 	m_pCurrent += iBytes;
235 	m_iLeft -= iBytes;
236 }
237 
238 struct CSphUrl
239 {
240 	char * m_sBuffer;
241 	char * m_sFormatted;
242 
243 	char * m_sScheme;
244 	char * m_sHost;
245 	char * m_sIndex;
246 
247 	int m_iPort;
248 
CSphUrlCSphUrl249 	CSphUrl()
250 		: m_sBuffer ( NULL )
251 		, m_sFormatted ( NULL )
252 		, m_sScheme ( SPHINXSE_DEFAULT_SCHEME )
253 		, m_sHost ( SPHINXSE_DEFAULT_HOST )
254 		, m_sIndex ( SPHINXSE_DEFAULT_INDEX )
255 		, m_iPort ( SPHINXSE_DEFAULT_PORT )
256 	{}
257 
~CSphUrlCSphUrl258 	~CSphUrl()
259 	{
260 		SafeDeleteArray ( m_sFormatted );
261 		SafeDeleteArray ( m_sBuffer );
262 	}
263 
264 	bool Parse ( const char * sUrl, int iLen );
265 	int Connect();
266 	const char * Format();
267 };
268 
Format()269 const char * CSphUrl::Format()
270 {
271 	if ( !m_sFormatted )
272 	{
273 		int iSize = 15 + strlen(m_sHost) + strlen(m_sIndex);
274 		m_sFormatted = new char [ iSize ];
275 		if ( m_iPort )
276 			snprintf ( m_sFormatted, iSize, "inet://%s:%d/%s", m_sHost, m_iPort, m_sIndex );
277 		else
278 			snprintf ( m_sFormatted, iSize, "unix://%s/%s", m_sHost, m_sIndex );
279 	}
280 	return m_sFormatted;
281 }
282 
283 // the following scheme variants are recognized
284 //
285 // inet://host/index
286 // inet://host:port/index
287 // unix://unix/domain/socket:index
288 // unix://unix/domain/socket
Parse(const char * sUrl,int iLen)289 bool CSphUrl::Parse ( const char * sUrl, int iLen )
290 {
291 	bool bOk = true;
292 	while ( iLen )
293 	{
294 		bOk = false;
295 
296 		m_sBuffer = sphDup ( sUrl, iLen );
297 		m_sScheme = m_sBuffer;
298 
299 		m_sHost = strstr ( m_sBuffer, "://" );
300 		if ( !m_sHost )
301 			break;
302 		m_sHost[0] = '\0';
303 		m_sHost += 2;
304 
305 		if ( !strcmp ( m_sScheme, "unix" ) )
306 		{
307 			// unix-domain socket
308 			m_iPort = 0;
309 			if (!( m_sIndex = strrchr ( m_sHost, ':' ) ))
310 				m_sIndex = SPHINXSE_DEFAULT_INDEX;
311 			else
312 			{
313 				*m_sIndex++ = '\0';
314 				if ( !*m_sIndex )
315 					m_sIndex = SPHINXSE_DEFAULT_INDEX;
316 			}
317 			bOk = true;
318 			break;
319 		}
320 		if ( strcmp ( m_sScheme, "sphinx" )!=0 && strcmp ( m_sScheme, "inet" )!=0 )
321 			break;
322 
323 		// inet
324 		m_sHost++;
325 		char * sPort = strchr ( m_sHost, ':' );
326 		if ( sPort )
327 		{
328 			*sPort++ = '\0';
329 			if ( *sPort )
330 			{
331 				m_sIndex = strchr ( sPort, '/' );
332 				if ( m_sIndex )
333 					*m_sIndex++ = '\0';
334 				else
335 					m_sIndex = SPHINXSE_DEFAULT_INDEX;
336 
337 				m_iPort = atoi(sPort);
338 				if ( !m_iPort )
339 					m_iPort = SPHINXSE_DEFAULT_PORT;
340 			}
341 		} else
342 		{
343 			m_sIndex = strchr ( m_sHost, '/' );
344 			if ( m_sIndex )
345 				*m_sIndex++ = '\0';
346 			else
347 				m_sIndex = SPHINXSE_DEFAULT_INDEX;
348 		}
349 
350 		bOk = true;
351 		break;
352 	}
353 
354 	return bOk;
355 }
356 
Connect()357 int CSphUrl::Connect()
358 {
359 	struct sockaddr_in sin;
360 #ifndef __WIN__
361 	struct sockaddr_un saun;
362 #endif
363 
364 	int iDomain = 0;
365 	int iSockaddrSize = 0;
366 	struct sockaddr * pSockaddr = NULL;
367 
368 	in_addr_t ip_addr;
369 
370 	if ( m_iPort )
371 	{
372 		iDomain = AF_INET;
373 		iSockaddrSize = sizeof(sin);
374 		pSockaddr = (struct sockaddr *) &sin;
375 
376 		memset ( &sin, 0, sizeof(sin) );
377 		sin.sin_family = AF_INET;
378 		sin.sin_port = htons ( m_iPort );
379 
380 		// resolve address
381 		if ( (int)( ip_addr = inet_addr ( m_sHost ) )!=(int)INADDR_NONE )
382 			memcpy ( &sin.sin_addr, &ip_addr, sizeof(ip_addr) );
383 		else
384 		{
385 			int tmp_errno;
386 			bool bError = false;
387 
388 #if MYSQL_VERSION_ID>=50515
389 			struct addrinfo *hp = NULL;
390 			tmp_errno = getaddrinfo ( m_sHost, NULL, NULL, &hp );
391 			if ( !tmp_errno || !hp || !hp->ai_addr )
392 			{
393 				bError = true;
394 				if ( hp )
395 					freeaddrinfo ( hp );
396 			}
397 #else
398 			struct hostent tmp_hostent, *hp;
399 			char buff2 [ GETHOSTBYNAME_BUFF_SIZE ];
400 			hp = my_gethostbyname_r ( m_sHost, &tmp_hostent, buff2, sizeof(buff2), &tmp_errno );
401 			if ( !hp )
402 			{
403 				my_gethostbyname_r_free();
404 				bError = true;
405 			}
406 #endif
407 
408 			if ( bError )
409 			{
410 				char sError[256];
411 				my_snprintf ( sError, sizeof(sError), "failed to resolve searchd host (name=%s)", m_sHost );
412 
413 				my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
414 				return -1;
415 			}
416 
417 #if MYSQL_VERSION_ID>=50515
418 			memcpy ( &sin.sin_addr, hp->ai_addr, Min ( sizeof(sin.sin_addr), (size_t)hp->ai_addrlen ) );
419 			freeaddrinfo ( hp );
420 #else
421 			memcpy ( &sin.sin_addr, hp->h_addr, Min ( sizeof(sin.sin_addr), (size_t)hp->h_length ) );
422 			my_gethostbyname_r_free();
423 #endif
424 		}
425 	} else
426 	{
427 #ifndef __WIN__
428 		iDomain = AF_UNIX;
429 		iSockaddrSize = sizeof(saun);
430 		pSockaddr = (struct sockaddr *) &saun;
431 
432 		memset ( &saun, 0, sizeof(saun) );
433 		saun.sun_family = AF_UNIX;
434 		strncpy ( saun.sun_path, m_sHost, sizeof(saun.sun_path)-1 );
435 #else
436 		my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), "Unix-domain sockets are not supported on Windows" );
437 		return -1;
438 #endif
439 	}
440 
441 	// connect to searchd and exchange versions
442 	uint uServerVersion;
443 	uint uClientVersion = htonl ( SPHINX_SEARCHD_PROTO );
444 	int iSocket = -1;
445 	char * pError = NULL;
446 	do
447 	{
448 		iSocket = socket ( iDomain, SOCK_STREAM, 0 );
449 		if ( iSocket==-1 )
450 		{
451 			pError = "Failed to create client socket";
452 			break;
453 		}
454 
455 		if ( connect ( iSocket, pSockaddr, iSockaddrSize )==-1 )
456 		{
457 			pError = "Failed to connect to searchd";
458 			break;
459 		}
460 
461 		if ( !sphRecv ( iSocket, (char *)&uServerVersion, sizeof(uServerVersion) ) )
462 		{
463 			pError = "Failed to receive searchd version";
464 			break;
465 		}
466 
467 		if ( !sphSend ( iSocket, (char *)&uClientVersion, sizeof(uClientVersion) ) )
468 		{
469 			pError = "Failed to send client version";
470 			break;
471 		}
472 	}
473 	while(0);
474 
475 	// fixme: compare versions?
476 
477 	if ( pError )
478 	{
479 		char sError[1024];
480 		snprintf ( sError, sizeof(sError), "%s [%d] %s", Format(), errno, strerror(errno) );
481 		my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
482 
483 		if ( iSocket!=-1 )
484 			close ( iSocket );
485 
486 		return -1;
487 	}
488 
489 	return iSocket;
490 }
491 
492 struct CSphResponse
493 {
494 	char * m_pBuffer;
495 	char * m_pBody;
496 
CSphResponseCSphResponse497 	CSphResponse ()
498 		: m_pBuffer ( NULL )
499 		, m_pBody ( NULL )
500 	{}
501 
CSphResponseCSphResponse502 	explicit CSphResponse ( DWORD uSize )
503 		: m_pBody ( NULL )
504 	{
505 		m_pBuffer = new char[uSize];
506 	}
507 
~CSphResponseCSphResponse508 	~CSphResponse ()
509 	{
510 		SafeDeleteArray ( m_pBuffer );
511 	}
512 
513 	static CSphResponse * Read ( int iSocket, int iClientVersion );
514 };
515 
516 CSphResponse *
Read(int iSocket,int iClientVersion)517 CSphResponse::Read ( int iSocket, int iClientVersion )
518 {
519 	char sHeader[8];
520 	if ( !sphRecv ( iSocket, sHeader, sizeof(sHeader) ) )
521 		return NULL;
522 
523 	int iStatus = ntohs ( sphUnalignedRead ( *(short int *) &sHeader[0] ) );
524 	int iVersion = ntohs ( sphUnalignedRead ( *(short int *) &sHeader[2] ) );
525 	DWORD uLength = ntohl ( sphUnalignedRead ( *(DWORD *)		&sHeader[4] ) );
526 
527 	if ( iVersion<iClientVersion )
528 		return NULL;
529 
530 	if ( uLength<=SPHINXSE_MAX_ALLOC )
531 	{
532 		CSphResponse * pResponse = new CSphResponse ( uLength );
533 		if ( !sphRecv ( iSocket, pResponse->m_pBuffer, uLength ) )
534 		{
535 			SafeDelete ( pResponse );
536 			return NULL;
537 		}
538 
539 		pResponse->m_pBody = pResponse->m_pBuffer;
540 		if ( iStatus!=SEARCHD_OK )
541 		{
542 			DWORD uSize = ntohl ( *(DWORD *)pResponse->m_pBuffer );
543 			if ( iStatus==SEARCHD_WARNING )
544 			{
545 				pResponse->m_pBody += uSize; // fixme: report the warning somehow
546 			} else
547 			{
548 				char * sMessage = sphDup ( pResponse->m_pBuffer + sizeof(DWORD), uSize );
549 				my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sMessage );
550 				SafeDeleteArray ( sMessage );
551 				SafeDelete ( pResponse );
552 				return NULL;
553 			}
554 		}
555 		return pResponse;
556 	}
557 	return NULL;
558 }
559 
560 /// udf
561 
562 extern "C"
563 {
564 	my_bool sphinx_snippets_init ( UDF_INIT * pUDF, UDF_ARGS * pArgs, char * sMessage );
565 	void sphinx_snippets_deinit ( UDF_INIT * pUDF );
566 	char * sphinx_snippets ( UDF_INIT * pUDF, UDF_ARGS * pArgs, char * sResult, unsigned long * pLength, char * pIsNull, char * sError );
567 };
568 
569 #define MAX_MESSAGE_LENGTH 255
570 #define MAX_RESULT_LENGTH 255
571 
572 struct CSphSnippets
573 {
574 	CSphUrl m_tUrl;
575 	CSphResponse * m_pResponse;
576 
577 	int m_iBeforeMatch;
578 	int m_iAfterMatch;
579 	int m_iChunkSeparator;
580 	int m_iStripMode;
581 	int m_iPassageBoundary;
582 	int m_iLimit;
583 	int m_iLimitWords;
584 	int m_iLimitPassages;
585 	int m_iAround;
586 	int m_iPassageId;
587 	int m_iFlags;
588 
CSphSnippetsCSphSnippets589 	CSphSnippets()
590 		: m_pResponse(NULL)
591 		, m_iBeforeMatch(0)
592 		, m_iAfterMatch(0)
593 		, m_iChunkSeparator(0)
594 		, m_iStripMode(0)
595 		, m_iPassageBoundary(0)
596 		// defaults
597 		, m_iLimit(256)
598 		, m_iLimitWords(0)
599 		, m_iLimitPassages(0)
600 		, m_iAround(5)
601 		, m_iPassageId(1)
602 		, m_iFlags(1)
603 	{
604 	}
605 
~CSphSnippetsCSphSnippets606 	~CSphSnippets()
607 	{
608 		SafeDelete ( m_pResponse );
609 	}
610 };
611 
612 #define KEYWORD(NAME) else if ( strncmp ( NAME, pArgs->attributes[i], pArgs->attribute_lengths[i] )==0 )
613 
614 #define CHECK_TYPE(TYPE)											\
615 	if ( pArgs->arg_type[i]!=TYPE )									\
616 	{																\
617 		snprintf ( sMessage, MAX_MESSAGE_LENGTH,					\
618 					"%.*s argument must be a string",				\
619 					(int)pArgs->attribute_lengths[i],				\
620 					pArgs->attributes[i] );							\
621 		bFail = true;												\
622 		break;														\
623 	}																\
624 	if ( TYPE==STRING_RESULT && !pArgs->args[i] )					\
625 	{																\
626 		snprintf ( sMessage, MAX_MESSAGE_LENGTH,					\
627 					"%.*s argument must be constant (and not NULL)",	\
628 					(int)pArgs->attribute_lengths[i],				\
629 					pArgs->attributes[i] );							\
630 		bFail = true;												\
631 		break;														\
632 	}
633 
634 #define STRING CHECK_TYPE(STRING_RESULT)
635 #define INT CHECK_TYPE(INT_RESULT); int iValue = *(long long *)pArgs->args[i]
636 
sphinx_snippets_init(UDF_INIT * pUDF,UDF_ARGS * pArgs,char * sMessage)637 my_bool sphinx_snippets_init ( UDF_INIT * pUDF, UDF_ARGS * pArgs, char * sMessage )
638 {
639 	if ( pArgs->arg_count < 3 )
640 	{
641 		strncpy ( sMessage, "insufficient arguments", MAX_MESSAGE_LENGTH );
642 		return 1;
643 	}
644 
645 	bool bFail = false;
646 	CSphSnippets * pOpts = new CSphSnippets;
647 	for ( uint i = 0; i < pArgs->arg_count; i++ )
648 	{
649 		if ( i < 3 )
650 		{
651 			if ( pArgs->arg_type[i]!=STRING_RESULT )
652 			{
653 				strncpy ( sMessage, "first three arguments must be of string type", MAX_MESSAGE_LENGTH );
654 				bFail = true;
655 				break;
656 			}
657 		}
658 		KEYWORD("sphinx")
659 		{
660 			STRING;
661 			if ( !pOpts->m_tUrl.Parse ( pArgs->args[i], pArgs->lengths[i] ) )
662 			{
663 				strncpy ( sMessage, "failed to parse connection string", MAX_MESSAGE_LENGTH );
664 				bFail = true;
665 				break;
666 			}
667 		}
668 		KEYWORD("before_match")		{ STRING; pOpts->m_iBeforeMatch = i; }
669 		KEYWORD("after_match")		{ STRING; pOpts->m_iAfterMatch = i; }
670 		KEYWORD("chunk_separator")	{ STRING; pOpts->m_iChunkSeparator = i; }
671 		KEYWORD("html_strip_mode")	{ STRING; pOpts->m_iStripMode = i; }
672 		KEYWORD("passage_boundary") { STRING; pOpts->m_iPassageBoundary = i; }
673 
674 		KEYWORD("limit")			{ INT; pOpts->m_iLimit = iValue; }
675 		KEYWORD("limit_words")		{ INT; pOpts->m_iLimitWords = iValue; }
676 		KEYWORD("limit_passages")	{ INT; pOpts->m_iLimitPassages = iValue; }
677 		KEYWORD("around")			{ INT; pOpts->m_iAround = iValue; }
678 		KEYWORD("start_passage_id") { INT; pOpts->m_iPassageId = iValue; }
679 
680 		KEYWORD("exact_phrase")		{ INT; if ( iValue ) pOpts->m_iFlags |= 2; }
681 		KEYWORD("single_passage")	{ INT; if ( iValue ) pOpts->m_iFlags |= 4; }
682 		KEYWORD("use_boundaries")	{ INT; if ( iValue ) pOpts->m_iFlags |= 8; }
683 		KEYWORD("weight_order")		{ INT; if ( iValue ) pOpts->m_iFlags |= 16; }
684 		KEYWORD("query_mode")		{ INT; if ( iValue ) pOpts->m_iFlags |= 32; }
685 		KEYWORD("force_all_words")	{ INT; if ( iValue ) pOpts->m_iFlags |= 64; }
686 		KEYWORD("load_files")		{ INT; if ( iValue ) pOpts->m_iFlags |= 128; }
687 		KEYWORD("allow_empty")		{ INT; if ( iValue ) pOpts->m_iFlags |= 256; }
688 		KEYWORD("emit_zones")		{ INT; if ( iValue ) pOpts->m_iFlags |= 512; }
689 		KEYWORD("load_files_scattered") { INT; if ( iValue ) pOpts->m_iFlags |= 1024; }
690 		else
691 		{
692 			snprintf ( sMessage, MAX_MESSAGE_LENGTH, "unrecognized argument: %.*s",
693 						(int)pArgs->attribute_lengths[i], pArgs->attributes[i] );
694 			bFail = true;
695 			break;
696 		}
697 	}
698 
699 	if ( bFail )
700 	{
701 		SafeDelete ( pOpts );
702 		return 1;
703 	}
704 	pUDF->ptr = (char *)pOpts;
705 	return 0;
706 }
707 
708 #undef STRING
709 #undef INT
710 #undef KEYWORD
711 #undef CHECK_TYPE
712 
713 #define ARG(i) pArgs->args[i], pArgs->lengths[i]
714 #define ARG_LEN(VAR, LEN) ( VAR ? pArgs->lengths[VAR] : LEN )
715 
716 #define SEND_STRING(INDEX, DEFAULT)							\
717 	if ( INDEX )											\
718 		tBuffer.SendString ( ARG(INDEX) );					\
719 	else													\
720 		tBuffer.SendString ( DEFAULT, sizeof(DEFAULT) - 1 );
721 
722 
sphinx_snippets(UDF_INIT * pUDF,UDF_ARGS * pArgs,char * sResult,unsigned long * pLength,char * pIsNull,char * pError)723 char * sphinx_snippets ( UDF_INIT * pUDF, UDF_ARGS * pArgs, char * sResult, unsigned long * pLength, char * pIsNull, char * pError )
724 {
725 	CSphSnippets * pOpts = (CSphSnippets *)pUDF->ptr;
726 	assert ( pOpts );
727 
728 	if ( !pArgs->args[0] || !pArgs->args[1] || !pArgs->args[2] )
729 	{
730 		*pIsNull = 1;
731 		return sResult;
732 	}
733 
734 	const int iSize = 68 +
735 		pArgs->lengths[1] + // index
736 		pArgs->lengths[2] + // words
737 		ARG_LEN ( pOpts->m_iBeforeMatch, 3 ) +
738 		ARG_LEN ( pOpts->m_iAfterMatch, 4 ) +
739 		ARG_LEN ( pOpts->m_iChunkSeparator, 5 ) +
740 		ARG_LEN ( pOpts->m_iStripMode, 5 ) +
741 		ARG_LEN ( pOpts->m_iPassageBoundary, 0 ) +
742 		4 + pArgs->lengths[0]; // document
743 
744 	CSphBuffer tBuffer(iSize);
745 
746 	tBuffer.SendWord ( SEARCHD_COMMAND_EXCERPT );
747 	tBuffer.SendWord ( VER_COMMAND_EXCERPT );
748 	tBuffer.SendDword ( iSize - 8 );
749 
750 	tBuffer.SendDword ( 0 );
751 	tBuffer.SendDword ( pOpts->m_iFlags );
752 
753 	tBuffer.SendString ( ARG(1) ); // index
754 	tBuffer.SendString ( ARG(2) ); // words
755 
756 	SEND_STRING ( pOpts->m_iBeforeMatch, "<b>" );
757 	SEND_STRING ( pOpts->m_iAfterMatch, "</b>" );
758 	SEND_STRING ( pOpts->m_iChunkSeparator, " ... " );
759 
760 	tBuffer.SendInt ( pOpts->m_iLimit );
761 	tBuffer.SendInt ( pOpts->m_iAround );
762 
763 	tBuffer.SendInt ( pOpts->m_iLimitPassages );
764 	tBuffer.SendInt ( pOpts->m_iLimitWords );
765 	tBuffer.SendInt ( pOpts->m_iPassageId );
766 
767 	SEND_STRING ( pOpts->m_iStripMode, "index" );
768 	SEND_STRING ( pOpts->m_iPassageBoundary, "" );
769 
770 	// single document
771 	tBuffer.SendInt ( 1 );
772 	tBuffer.SendString ( ARG(0) );
773 
774 	int iSocket = -1;
775 	do
776 	{
777 		if ( !tBuffer.Finalize() )
778 		{
779 			my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: failed to build request" );
780 			break;
781 		}
782 
783 		iSocket = pOpts->m_tUrl.Connect();
784 		if ( iSocket==-1 ) break;
785 		if ( !sphSend ( iSocket, tBuffer.Ptr(), iSize, sphReportErrors ) ) break;
786 
787 		CSphResponse * pResponse = CSphResponse::Read ( iSocket, VER_COMMAND_EXCERPT );
788 		if ( !pResponse ) break;
789 
790 		close ( iSocket );
791 		pOpts->m_pResponse = pResponse;
792 		*pLength = ntohl ( *(DWORD *)pResponse->m_pBody );
793 		return pResponse->m_pBody + sizeof(DWORD);
794 	}
795 	while(0);
796 
797 	if ( iSocket!=-1 )
798 		close ( iSocket );
799 
800 	*pError = 1;
801 	return sResult;
802 }
803 
804 #undef SEND_STRING
805 #undef ARG_LEN
806 #undef ARG
807 
sphinx_snippets_deinit(UDF_INIT * pUDF)808 void sphinx_snippets_deinit ( UDF_INIT * pUDF )
809 {
810 	CSphSnippets * pOpts = (CSphSnippets *)pUDF->ptr;
811 	SafeDelete ( pOpts );
812 }
813 
814 //
815 // $Id: snippets_udf.cc 3508 2012-11-05 11:48:48Z kevg $
816 //
817