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