version up : percona-toolkit 2.2.10.
[slapos.git] / component / mysql-5.5 / mysql-5.5-sphinx-2.0.1-beta.diff
1 diff -uNr storage/sphinx/CMakeLists.txt storage/sphinx/CMakeLists.txt
2 --- storage/sphinx/CMakeLists.txt       1970-01-01 01:00:00.000000000 +0100
3 +++ storage/sphinx/CMakeLists.txt       2011-10-13 00:59:59.282957578 +0200
4 @@ -0,0 +1,16 @@
5 +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX")
6 +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX")
7 +ADD_DEFINITIONS(-DMYSQL_SERVER)
8 +
9 +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include
10 +                    ${CMAKE_SOURCE_DIR}/sql
11 +                    ${CMAKE_SOURCE_DIR}/extra/yassl/include
12 +                    ${CMAKE_SOURCE_DIR}/regex)
13 +
14 +SET(SPHINX_SOURCES ha_sphinx.cc)
15 +IF(MYSQL_VERSION_ID LESS 50515) 
16 +ADD_LIBRARY(sphinx ha_sphinx.cc)
17 +ELSE()
18 +SET(SPHINX_PLUGIN_DYNAMIC "ha_sphinx")
19 +MYSQL_ADD_PLUGIN(sphinx ${SPHINX_SOURCES} STORAGE_ENGINE MODULE_ONLY LINK_LIBRARIES mysys)
20 +ENDIF()
21 diff -uNr storage/sphinx/gen_data.php storage/sphinx/gen_data.php
22 --- storage/sphinx/gen_data.php 1970-01-01 01:00:00.000000000 +0100
23 +++ storage/sphinx/gen_data.php 2006-06-07 09:28:43.000000000 +0200
24 @@ -0,0 +1,37 @@
25 +<?php
26 +
27 +$file_name= $argv[1];
28 +
29 +//echo $file_name;
30 +
31 +$cont= file_get_contents($file_name);
32 +
33 +$words= explode(" ", $cont);
34 +
35 +//echo "words: ".(count($words))."\n";
36 +
37 +$cw = count($words);
38 +
39 +echo "REPLACE INTO test.documents ( id, group_id, date_added, title, content ) VALUES\n";
40 +
41 +
42 +for ($i=1; $i<=100000; $i++)
43 +{
44 +  $count_words= mt_rand(10,30);
45 +  $pred = "";
46 +  for ($j=0; $j<$count_words; $j++)
47 +  {
48 +    $pred .= chop($words[mt_rand(1, $cw-1)])." ";
49 +  }
50 +  $count_words= mt_rand(3,5);
51 +  $tit = "";
52 +  for ($j=0; $j<$count_words; $j++)
53 +  {
54 +    $tit .= chop($words[mt_rand(1, $cw-1)])." ";
55 +  }
56 +  echo "($i,".mt_rand(1,20).",NOW(),'".addslashes($tit)."','".addslashes($pred)."'),\n";
57 +}       
58 +  echo "(0,1,now(),'end','eND');\n";
59 +  
60 +
61 +?>
62 diff -uNr storage/sphinx/ha_sphinx.cc storage/sphinx/ha_sphinx.cc
63 --- storage/sphinx/ha_sphinx.cc 1970-01-01 01:00:00.000000000 +0100
64 +++ storage/sphinx/ha_sphinx.cc 2011-10-13 00:59:59.282957578 +0200
65 @@ -0,0 +1,3547 @@
66 +//
67 +// $Id: ha_sphinx.cc 2752 2011-03-29 08:21:05Z tomat $
68 +//
69 +
70 +//
71 +// Copyright (c) 2001-2011, Andrew Aksyonoff
72 +// Copyright (c) 2008-2011, Sphinx Technologies Inc
73 +// All rights reserved
74 +//
75 +// This program is free software; you can redistribute it and/or modify
76 +// it under the terms of the GNU General Public License. You should have
77 +// received a copy of the GPL license along with this program; if you
78 +// did not, you can find it at http://www.gnu.org/
79 +//
80 +
81 +#ifdef USE_PRAGMA_IMPLEMENTATION
82 +#pragma implementation // gcc: Class implementation
83 +#endif
84 +
85 +#if _MSC_VER>=1400
86 +#define _CRT_SECURE_NO_DEPRECATE 1
87 +#define _CRT_NONSTDC_NO_DEPRECATE 1
88 +#endif
89 +
90 +#include <mysql_version.h>
91 +
92 +#if MYSQL_VERSION_ID>=50515
93 +#include "sql_class.h"
94 +#include "sql_array.h"
95 +#elif MYSQL_VERSION_ID>50100
96 +#include "mysql_priv.h"
97 +#include <mysql/plugin.h>
98 +#else
99 +#include "../mysql_priv.h"
100 +#endif
101 +
102 +#include <mysys_err.h>
103 +#include <my_sys.h>
104 +#include <mysql.h> // include client for INSERT table (sort of redoing federated..)
105 +
106 +#ifndef __WIN__
107 +       // UNIX-specific
108 +       #include <my_net.h>
109 +       #include <netdb.h>
110 +       #include <sys/un.h>
111 +
112 +       #define RECV_FLAGS      MSG_WAITALL
113 +
114 +       #define sphSockClose(_sock)     ::close(_sock)
115 +#else
116 +       // Windows-specific
117 +       #include <io.h>
118 +       #define strcasecmp      stricmp
119 +       #define snprintf        _snprintf
120 +
121 +       #define RECV_FLAGS      0
122 +
123 +       #define sphSockClose(_sock)     ::closesocket(_sock)
124 +#endif
125 +
126 +#include <ctype.h>
127 +#include "ha_sphinx.h"
128 +
129 +#ifndef MSG_WAITALL
130 +#define MSG_WAITALL 0
131 +#endif
132 +
133 +#if _MSC_VER>=1400
134 +#pragma warning(push,4)
135 +#endif
136 +
137 +/////////////////////////////////////////////////////////////////////////////
138 +
139 +/// there might be issues with min() on different platforms (eg. Gentoo, they say)
140 +#define Min(a,b) ((a)<(b)?(a):(b))
141 +
142 +/// unaligned RAM accesses are forbidden on SPARC
143 +#if defined(sparc) || defined(__sparc__)
144 +#define UNALIGNED_RAM_ACCESS 0
145 +#else
146 +#define UNALIGNED_RAM_ACCESS 1
147 +#endif
148 +
149 +
150 +#if UNALIGNED_RAM_ACCESS
151 +
152 +/// pass-through wrapper
153 +template < typename T > inline T sphUnalignedRead ( const T & tRef )
154 +{
155 +       return tRef;
156 +}
157 +
158 +/// pass-through wrapper
159 +template < typename T > void sphUnalignedWrite ( void * pPtr, const T & tVal )
160 +{
161 +       *(T*)pPtr = tVal;
162 +}
163 +
164 +#else
165 +
166 +/// unaligned read wrapper for some architectures (eg. SPARC)
167 +template < typename T >
168 +inline T sphUnalignedRead ( const T & tRef )
169 +{
170 +       T uTmp;
171 +       byte * pSrc = (byte *) &tRef;
172 +       byte * pDst = (byte *) &uTmp;
173 +       for ( int i=0; i<(int)sizeof(T); i++ )
174 +               *pDst++ = *pSrc++;
175 +       return uTmp;
176 +}
177 +
178 +/// unaligned write wrapper for some architectures (eg. SPARC)
179 +template < typename T >
180 +void sphUnalignedWrite ( void * pPtr, const T & tVal )
181 +{
182 +       byte * pDst = (byte *) pPtr;
183 +       byte * pSrc = (byte *) &tVal;
184 +       for ( int i=0; i<(int)sizeof(T); i++ )
185 +               *pDst++ = *pSrc++;
186 +}
187 +
188 +#endif
189 +
190 +#if MYSQL_VERSION_ID>=50515
191 +
192 +#define sphinx_hash_init my_hash_init
193 +#define sphinx_hash_free my_hash_free
194 +#define sphinx_hash_search my_hash_search
195 +#define sphinx_hash_delete my_hash_delete
196 +
197 +#else
198 +
199 +#define sphinx_hash_init hash_init
200 +#define sphinx_hash_free hash_free
201 +#define sphinx_hash_search hash_search
202 +#define sphinx_hash_delete hash_delete
203 +
204 +#endif
205 +
206 +/////////////////////////////////////////////////////////////////////////////
207 +
208 +// FIXME! make this all dynamic
209 +#define SPHINXSE_MAX_FILTERS           32
210 +
211 +#define SPHINXAPI_DEFAULT_HOST         "127.0.0.1"
212 +#define SPHINXAPI_DEFAULT_PORT         9312
213 +#define SPHINXAPI_DEFAULT_INDEX                "*"
214 +
215 +#define SPHINXQL_DEFAULT_PORT          9306
216 +
217 +#define SPHINXSE_SYSTEM_COLUMNS                3
218 +
219 +#define SPHINXSE_MAX_ALLOC                     (16*1024*1024)
220 +#define SPHINXSE_MAX_KEYWORDSTATS      4096
221 +
222 +#define SPHINXSE_VERSION                       "0.9.9 ($Revision: 2752 $)"
223 +
224 +// FIXME? the following is cut-n-paste from sphinx.h and searchd.cpp
225 +// cut-n-paste is somewhat simpler that adding dependencies however..
226 +
227 +enum
228 +{
229 +       SPHINX_SEARCHD_PROTO    = 1,
230 +       SEARCHD_COMMAND_SEARCH  = 0,
231 +       VER_COMMAND_SEARCH              = 0x116,
232 +};
233 +
234 +/// search query sorting orders
235 +enum ESphSortOrder
236 +{
237 +       SPH_SORT_RELEVANCE              = 0,    ///< sort by document relevance desc, then by date
238 +       SPH_SORT_ATTR_DESC              = 1,    ///< sort by document date desc, then by relevance desc
239 +       SPH_SORT_ATTR_ASC               = 2,    ///< sort by document date asc, then by relevance desc
240 +       SPH_SORT_TIME_SEGMENTS  = 3,    ///< sort by time segments (hour/day/week/etc) desc, then by relevance desc
241 +       SPH_SORT_EXTENDED               = 4,    ///< sort by SQL-like expression (eg. "@relevance DESC, price ASC, @id DESC")
242 +       SPH_SORT_EXPR                   = 5,    ///< sort by expression
243 +
244 +       SPH_SORT_TOTAL
245 +};
246 +
247 +/// search query matching mode
248 +enum ESphMatchMode
249 +{
250 +       SPH_MATCH_ALL = 0,                      ///< match all query words
251 +       SPH_MATCH_ANY,                          ///< match any query word
252 +       SPH_MATCH_PHRASE,                       ///< match this exact phrase
253 +       SPH_MATCH_BOOLEAN,                      ///< match this boolean query
254 +       SPH_MATCH_EXTENDED,                     ///< match this extended query
255 +       SPH_MATCH_FULLSCAN,                     ///< match all document IDs w/o fulltext query, apply filters
256 +       SPH_MATCH_EXTENDED2,            ///< extended engine V2
257 +
258 +       SPH_MATCH_TOTAL
259 +};
260 +
261 +/// search query relevance ranking mode
262 +enum ESphRankMode
263 +{
264 +       SPH_RANK_PROXIMITY_BM25         = 0,    ///< default mode, phrase proximity major factor and BM25 minor one
265 +       SPH_RANK_BM25                           = 1,    ///< statistical mode, BM25 ranking only (faster but worse quality)
266 +       SPH_RANK_NONE                           = 2,    ///< no ranking, all matches get a weight of 1
267 +       SPH_RANK_WORDCOUNT                      = 3,    ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
268 +       SPH_RANK_PROXIMITY                      = 4,    ///< phrase proximity
269 +       SPH_RANK_MATCHANY                       = 5,    ///< emulate old match-any weighting
270 +       SPH_RANK_FIELDMASK                      = 6,    ///< sets bits where there were matches
271 +       SPH_RANK_SPH04                          = 7,    ///< codename SPH04, phrase proximity + bm25 + head/exact boost
272 +
273 +       SPH_RANK_TOTAL,
274 +       SPH_RANK_DEFAULT                        = SPH_RANK_PROXIMITY_BM25
275 +};
276 +
277 +/// search query grouping mode
278 +enum ESphGroupBy
279 +{
280 +       SPH_GROUPBY_DAY         = 0,    ///< group by day
281 +       SPH_GROUPBY_WEEK        = 1,    ///< group by week
282 +       SPH_GROUPBY_MONTH       = 2,    ///< group by month
283 +       SPH_GROUPBY_YEAR        = 3,    ///< group by year
284 +       SPH_GROUPBY_ATTR        = 4             ///< group by attribute value
285 +};
286 +
287 +/// known attribute types
288 +enum
289 +{
290 +       SPH_ATTR_NONE           = 0,                    ///< not an attribute at all
291 +       SPH_ATTR_INTEGER        = 1,                    ///< this attr is just an integer
292 +       SPH_ATTR_TIMESTAMP      = 2,                    ///< this attr is a timestamp
293 +       SPH_ATTR_ORDINAL        = 3,                    ///< this attr is an ordinal string number (integer at search time, specially handled at indexing time)
294 +       SPH_ATTR_BOOL           = 4,                    ///< this attr is a boolean bit field
295 +       SPH_ATTR_FLOAT          = 5,
296 +       SPH_ATTR_BIGINT         = 6,
297 +
298 +       SPH_ATTR_MULTI          = 0x40000000UL  ///< this attr has multiple values (0 or more)
299 +};
300 +
301 +/// known answers
302 +enum
303 +{
304 +       SEARCHD_OK              = 0,    ///< general success, command-specific reply follows
305 +       SEARCHD_ERROR   = 1,    ///< general failure, error message follows
306 +       SEARCHD_RETRY   = 2,    ///< temporary failure, error message follows, client should retry later
307 +       SEARCHD_WARNING = 3             ///< general success, warning message and command-specific reply follow
308 +};
309 +
310 +//////////////////////////////////////////////////////////////////////////////
311 +
312 +#define SPHINX_DEBUG_OUTPUT            0
313 +#define SPHINX_DEBUG_CALLS             0
314 +
315 +#include <stdarg.h>
316 +
317 +#if SPHINX_DEBUG_OUTPUT
318 +inline void SPH_DEBUG ( const char * format, ... )
319 +{
320 +       va_list ap;
321 +       va_start ( ap, format );
322 +       fprintf ( stderr, "SphinxSE: " );
323 +       vfprintf ( stderr, format, ap );
324 +       fprintf ( stderr, "\n" );
325 +       va_end ( ap );
326 +}
327 +#else
328 +inline void SPH_DEBUG ( const char *, ... ) {}
329 +#endif
330 +
331 +#if SPHINX_DEBUG_CALLS
332 +
333 +#define SPH_ENTER_FUNC() { SPH_DEBUG ( "enter %s", __FUNCTION__ ); }
334 +#define SPH_ENTER_METHOD() { SPH_DEBUG ( "enter %s(this=%08x)", __FUNCTION__, this ); }
335 +#define SPH_RET(_arg) { SPH_DEBUG ( "leave %s", __FUNCTION__ ); return _arg; }
336 +#define SPH_VOID_RET() { SPH_DEBUG ( "leave %s", __FUNCTION__ ); return; }
337 +
338 +#else
339 +
340 +#define SPH_ENTER_FUNC()
341 +#define SPH_ENTER_METHOD()
342 +#define SPH_RET(_arg) { return(_arg); }
343 +#define SPH_VOID_RET() { return; }
344 +
345 +#endif
346 +
347 +
348 +#define SafeDelete(_arg)               { if ( _arg ) delete ( _arg );          (_arg) = NULL; }
349 +#define SafeDeleteArray(_arg)  { if ( _arg ) delete [] ( _arg );       (_arg) = NULL; }
350 +
351 +//////////////////////////////////////////////////////////////////////////////
352 +
353 +/// per-table structure that will be shared among all open Sphinx SE handlers
354 +struct CSphSEShare
355 +{
356 +       pthread_mutex_t m_tMutex;
357 +       THR_LOCK                m_tLock;
358 +
359 +       char *                  m_sTable;
360 +       char *                  m_sScheme;              ///< our connection string
361 +       char *                  m_sHost;                ///< points into m_sScheme buffer, DO NOT FREE EXPLICITLY
362 +       char *                  m_sSocket;              ///< points into m_sScheme buffer, DO NOT FREE EXPLICITLY
363 +       char *                  m_sIndex;               ///< points into m_sScheme buffer, DO NOT FREE EXPLICITLY
364 +       ushort                  m_iPort;
365 +       bool                    m_bSphinxQL;    ///< is this read-only SphinxAPI table, or write-only SphinxQL table?
366 +       uint                    m_iTableNameLen;
367 +       uint                    m_iUseCount;
368 +       CHARSET_INFO *  m_pTableQueryCharset;
369 +
370 +       int                                     m_iTableFields;
371 +       char **                         m_sTableField;
372 +       enum_field_types *      m_eTableFieldType;
373 +
374 +       CSphSEShare ()
375 +               : m_sTable ( NULL )
376 +               , m_sScheme ( NULL )
377 +               , m_sHost ( NULL )
378 +               , m_sSocket ( NULL )
379 +               , m_sIndex ( NULL )
380 +               , m_iPort ( 0 )
381 +               , m_bSphinxQL ( false )
382 +               , m_iTableNameLen ( 0 )
383 +               , m_iUseCount ( 1 )
384 +               , m_pTableQueryCharset ( NULL )
385 +
386 +               , m_iTableFields ( 0 )
387 +               , m_sTableField ( NULL )
388 +               , m_eTableFieldType ( NULL )
389 +       {
390 +               thr_lock_init ( &m_tLock );
391 +               pthread_mutex_init ( &m_tMutex, MY_MUTEX_INIT_FAST );
392 +       }
393 +
394 +       ~CSphSEShare ()
395 +       {
396 +               pthread_mutex_destroy ( &m_tMutex );
397 +               thr_lock_delete ( &m_tLock );
398 +
399 +               SafeDeleteArray ( m_sTable );
400 +               SafeDeleteArray ( m_sScheme );
401 +               ResetTable ();
402 +       }
403 +
404 +       void ResetTable ()
405 +       {
406 +               for ( int i=0; i<m_iTableFields; i++ )
407 +                       SafeDeleteArray ( m_sTableField[i] );
408 +               SafeDeleteArray ( m_sTableField );
409 +               SafeDeleteArray ( m_eTableFieldType );
410 +       }
411 +};
412 +
413 +/// schema attribute
414 +struct CSphSEAttr
415 +{
416 +       char *                  m_sName;                ///< attribute name (received from Sphinx)
417 +       uint32                  m_uType;                ///< attribute type (received from Sphinx)
418 +       int                             m_iField;               ///< field index in current table (-1 if none)
419 +
420 +       CSphSEAttr()
421 +               : m_sName ( NULL )
422 +               , m_uType ( SPH_ATTR_NONE )
423 +               , m_iField ( -1 )
424 +       {}
425 +
426 +       ~CSphSEAttr ()
427 +       {
428 +               SafeDeleteArray ( m_sName );
429 +       }
430 +};
431 +
432 +/// word stats
433 +struct CSphSEWordStats
434 +{
435 +       char *                  m_sWord;
436 +       int                             m_iDocs;
437 +       int                             m_iHits;
438 +
439 +       CSphSEWordStats ()
440 +               : m_sWord ( NULL )
441 +               , m_iDocs ( 0 )
442 +               , m_iHits ( 0 )
443 +       {}
444 +
445 +       ~CSphSEWordStats ()
446 +       {
447 +               SafeDeleteArray ( m_sWord );
448 +       }
449 +};
450 +
451 +/// request stats
452 +struct CSphSEStats
453 +{
454 +public:
455 +       int                                     m_iMatchesTotal;
456 +       int                                     m_iMatchesFound;
457 +       int                                     m_iQueryMsec;
458 +       int                                     m_iWords;
459 +       CSphSEWordStats *       m_dWords;
460 +       bool                            m_bLastError;
461 +       char                            m_sLastMessage[1024];
462 +
463 +       CSphSEStats()
464 +               : m_dWords ( NULL )
465 +       {
466 +               Reset ();
467 +       }
468 +
469 +       void Reset ()
470 +       {
471 +               m_iMatchesTotal = 0;
472 +               m_iMatchesFound = 0;
473 +               m_iQueryMsec = 0;
474 +               m_iWords = 0;
475 +               SafeDeleteArray ( m_dWords );
476 +               m_bLastError = false;
477 +               m_sLastMessage[0] = '\0';
478 +       }
479 +
480 +       ~CSphSEStats()
481 +       {
482 +               Reset ();
483 +       }
484 +};
485 +
486 +/// thread local storage
487 +struct CSphSEThreadData
488 +{
489 +       static const int        MAX_QUERY_LEN   = 262144; // 256k should be enough, right?
490 +
491 +       bool                            m_bStats;
492 +       CSphSEStats                     m_tStats;
493 +
494 +       bool                            m_bQuery;
495 +       char                            m_sQuery[MAX_QUERY_LEN];
496 +
497 +       CHARSET_INFO *          m_pQueryCharset;
498 +
499 +       bool                            m_bReplace;             ///< are we doing an INSERT or REPLACE
500 +
501 +       bool                            m_bCondId;              ///< got a value from condition pushdown
502 +       longlong                        m_iCondId;              ///< value acquired from id=value condition pushdown
503 +       bool                            m_bCondDone;    ///< index_read() is now over
504 +
505 +       CSphSEThreadData ()
506 +               : m_bStats ( false )
507 +               , m_bQuery ( false )
508 +               , m_pQueryCharset ( NULL )
509 +               , m_bReplace ( false )
510 +               , m_bCondId ( false )
511 +               , m_iCondId ( 0 )
512 +               , m_bCondDone ( false )
513 +       {}
514 +};
515 +
516 +/// filter types
517 +enum ESphFilter
518 +{
519 +       SPH_FILTER_VALUES               = 0,    ///< filter by integer values set
520 +       SPH_FILTER_RANGE                = 1,    ///< filter by integer range
521 +       SPH_FILTER_FLOATRANGE   = 2             ///< filter by float range
522 +};
523 +
524 +
525 +/// search query filter
526 +struct CSphSEFilter
527 +{
528 +public:
529 +       ESphFilter              m_eType;
530 +       char *                  m_sAttrName;
531 +       longlong                m_uMinValue;
532 +       longlong                m_uMaxValue;
533 +       float                   m_fMinValue;
534 +       float                   m_fMaxValue;
535 +       int                             m_iValues;
536 +       longlong *              m_pValues;
537 +       int                             m_bExclude;
538 +
539 +public:
540 +       CSphSEFilter ()
541 +               : m_eType ( SPH_FILTER_VALUES )
542 +               , m_sAttrName ( NULL )
543 +               , m_uMinValue ( 0 )
544 +               , m_uMaxValue ( UINT_MAX )
545 +               , m_fMinValue ( 0.0f )
546 +               , m_fMaxValue ( 0.0f )
547 +               , m_iValues ( 0 )
548 +               , m_pValues ( NULL )
549 +               , m_bExclude ( 0 )
550 +       {
551 +       }
552 +
553 +       ~CSphSEFilter ()
554 +       {
555 +               SafeDeleteArray ( m_pValues );
556 +       }
557 +};
558 +
559 +
560 +/// float vs dword conversion
561 +inline uint32 sphF2DW ( float f )      { union { float f; uint32 d; } u; u.f = f; return u.d; }
562 +
563 +/// dword vs float conversion
564 +inline float sphDW2F ( uint32 d )      { union { float f; uint32 d; } u; u.d = d; return u.f; }
565 +
566 +
567 +/// client-side search query
568 +struct CSphSEQuery
569 +{
570 +public:
571 +       const char *    m_sHost;
572 +       int                             m_iPort;
573 +
574 +private:
575 +       char *                  m_sQueryBuffer;
576 +
577 +       const char *    m_sIndex;
578 +       int                             m_iOffset;
579 +       int                             m_iLimit;
580 +
581 +       bool                    m_bQuery;
582 +       char *                  m_sQuery;
583 +       uint32 *                m_pWeights;
584 +       int                             m_iWeights;
585 +       ESphMatchMode   m_eMode;
586 +       ESphRankMode    m_eRanker;
587 +       ESphSortOrder   m_eSort;
588 +       char *                  m_sSortBy;
589 +       int                             m_iMaxMatches;
590 +       int                             m_iMaxQueryTime;
591 +       uint32                  m_iMinID;
592 +       uint32                  m_iMaxID;
593 +
594 +       int                             m_iFilters;
595 +       CSphSEFilter    m_dFilters[SPHINXSE_MAX_FILTERS];
596 +
597 +       ESphGroupBy             m_eGroupFunc;
598 +       char *                  m_sGroupBy;
599 +       char *                  m_sGroupSortBy;
600 +       int                             m_iCutoff;
601 +       int                             m_iRetryCount;
602 +       int                             m_iRetryDelay;
603 +       char *                  m_sGroupDistinct;                                                       ///< points to query buffer; do NOT delete
604 +       int                             m_iIndexWeights;
605 +       char *                  m_sIndexWeight[SPHINXSE_MAX_FILTERS];           ///< points to query buffer; do NOT delete
606 +       int                             m_iIndexWeight[SPHINXSE_MAX_FILTERS];
607 +       int                             m_iFieldWeights;
608 +       char *                  m_sFieldWeight[SPHINXSE_MAX_FILTERS];           ///< points to query buffer; do NOT delete
609 +       int                             m_iFieldWeight[SPHINXSE_MAX_FILTERS];
610 +
611 +       bool                    m_bGeoAnchor;
612 +       char *                  m_sGeoLatAttr;
613 +       char *                  m_sGeoLongAttr;
614 +       float                   m_fGeoLatitude;
615 +       float                   m_fGeoLongitude;
616 +
617 +       char *                  m_sComment;
618 +       char *                  m_sSelect;
619 +
620 +       struct Override_t
621 +       {
622 +               union Value_t
623 +               {
624 +                       uint32          m_uValue;
625 +                       longlong        m_iValue64;
626 +                       float           m_fValue;
627 +               };
628 +               char *                                          m_sName; ///< points to query buffer
629 +               int                                                     m_iType;
630 +               Dynamic_array<ulonglong>        m_dIds;
631 +               Dynamic_array<Value_t>          m_dValues;
632 +       };
633 +       Dynamic_array<Override_t *> m_dOverrides;
634 +
635 +public:
636 +       char                    m_sParseError[256];
637 +
638 +public:
639 +       CSphSEQuery ( const char * sQuery, int iLength, const char * sIndex );
640 +       ~CSphSEQuery ();
641 +
642 +       bool                    Parse ();
643 +       int                             BuildRequest ( char ** ppBuffer );
644 +
645 +protected:
646 +       char *                  m_pBuf;
647 +       char *                  m_pCur;
648 +       int                             m_iBufLeft;
649 +       bool                    m_bBufOverrun;
650 +
651 +       template < typename T > int ParseArray ( T ** ppValues, const char * sValue );
652 +       bool                    ParseField ( char * sField );
653 +
654 +       void                    SendBytes ( const void * pBytes, int iBytes );
655 +       void                    SendWord ( short int v )                { v = ntohs(v); SendBytes ( &v, sizeof(v) ); }
656 +       void                    SendInt ( int v )                               { v = ntohl(v); SendBytes ( &v, sizeof(v) ); }
657 +       void                    SendDword ( uint v )                    { v = ntohl(v) ;SendBytes ( &v, sizeof(v) ); }
658 +       void                    SendUint64 ( ulonglong v )              { SendDword ( (uint)(v>>32) ); SendDword ( (uint)(v&0xFFFFFFFFUL) ); }
659 +       void                    SendString ( const char * v )   { int iLen = strlen(v); SendDword(iLen); SendBytes ( v, iLen ); }
660 +       void                    SendFloat ( float v )                   { SendDword ( sphF2DW(v) ); }
661 +};
662 +
663 +template int CSphSEQuery::ParseArray<uint32> ( uint32 **, const char * );
664 +template int CSphSEQuery::ParseArray<longlong> ( longlong **, const char * );
665 +
666 +//////////////////////////////////////////////////////////////////////////////
667 +
668 +#if MYSQL_VERSION_ID>50100
669 +
670 +#if MYSQL_VERSION_ID<50114
671 +#error Sphinx SE requires MySQL 5.1.14 or higher if compiling for 5.1.x series!
672 +#endif
673 +
674 +static handler *       sphinx_create_handler ( handlerton * hton, TABLE_SHARE * table, MEM_ROOT * mem_root );
675 +static int                     sphinx_init_func ( void * p );
676 +static int                     sphinx_close_connection ( handlerton * hton, THD * thd );
677 +static int                     sphinx_panic ( handlerton * hton, enum ha_panic_function flag );
678 +static bool                    sphinx_show_status ( handlerton * hton, THD * thd, stat_print_fn * stat_print, enum ha_stat_type stat_type );
679 +
680 +#else
681 +
682 +static bool                    sphinx_init_func_for_handlerton ();
683 +static int                     sphinx_close_connection ( THD * thd );
684 +bool                           sphinx_show_status ( THD * thd );
685 +
686 +#endif // >50100
687 +
688 +//////////////////////////////////////////////////////////////////////////////
689 +
690 +static const char      sphinx_hton_name[]              = "SPHINX";
691 +static const char      sphinx_hton_comment[]   = "Sphinx storage engine " SPHINXSE_VERSION;
692 +
693 +#if MYSQL_VERSION_ID<50100
694 +handlerton sphinx_hton =
695 +{
696 +       #ifdef MYSQL_HANDLERTON_INTERFACE_VERSION
697 +       MYSQL_HANDLERTON_INTERFACE_VERSION,
698 +       #endif
699 +       sphinx_hton_name,
700 +       SHOW_OPTION_YES,
701 +       sphinx_hton_comment,
702 +       DB_TYPE_SPHINX_DB,
703 +       sphinx_init_func_for_handlerton,
704 +       0,                                                      // slot
705 +       0,                                                      // savepoint size
706 +       sphinx_close_connection,        // close_connection
707 +       NULL,   // savepoint
708 +       NULL,   // rollback to savepoint
709 +       NULL,   // release savepoint
710 +       NULL,   // commit
711 +       NULL,   // rollback
712 +       NULL,   // prepare
713 +       NULL,   // recover
714 +       NULL,   // commit_by_xid
715 +       NULL,   // rollback_by_xid
716 +       NULL,   // create_cursor_read_view
717 +       NULL,   // set_cursor_read_view
718 +       NULL,   // close_cursor_read_view
719 +       HTON_CAN_RECREATE
720 +};
721 +#else
722 +static handlerton * sphinx_hton_ptr = NULL;
723 +#endif
724 +
725 +//////////////////////////////////////////////////////////////////////////////
726 +
727 +// variables for Sphinx shared methods
728 +pthread_mutex_t                sphinx_mutex;           // mutex to init the hash
729 +static int                     sphinx_init = 0;        // flag whether the hash was initialized
730 +static HASH                    sphinx_open_tables;     // hash used to track open tables
731 +
732 +//////////////////////////////////////////////////////////////////////////////
733 +// INITIALIZATION AND SHUTDOWN
734 +//////////////////////////////////////////////////////////////////////////////
735 +
736 +// hashing function
737 +#if MYSQL_VERSION_ID>=50120
738 +typedef size_t GetKeyLength_t;
739 +#else
740 +typedef uint GetKeyLength_t;
741 +#endif
742 +
743 +static byte * sphinx_get_key ( const byte * pSharePtr, GetKeyLength_t * pLength, my_bool )
744 +{
745 +       CSphSEShare * pShare = (CSphSEShare *) pSharePtr;
746 +       *pLength = (size_t) pShare->m_iTableNameLen;
747 +       return (byte*) pShare->m_sTable;
748 +}
749 +
750 +#if MYSQL_VERSION_ID<50100
751 +static int sphinx_init_func ( void * ) // to avoid unused arg warning
752 +#else
753 +static int sphinx_init_func ( void * p )
754 +#endif
755 +{
756 +       SPH_ENTER_FUNC();
757 +       if ( !sphinx_init )
758 +       {
759 +               sphinx_init = 1;
760 +               void ( pthread_mutex_init ( &sphinx_mutex, MY_MUTEX_INIT_FAST ) );
761 +               sphinx_hash_init ( &sphinx_open_tables, system_charset_info, 32, 0, 0,
762 +                       sphinx_get_key, 0, 0 );
763 +
764 +               #if MYSQL_VERSION_ID > 50100
765 +               handlerton * hton = (handlerton*) p;
766 +               hton->state = SHOW_OPTION_YES;
767 +               hton->db_type = DB_TYPE_FIRST_DYNAMIC;
768 +               hton->create = sphinx_create_handler;
769 +               hton->close_connection = sphinx_close_connection;
770 +               hton->show_status = sphinx_show_status;
771 +               hton->panic = sphinx_panic;
772 +               hton->flags = HTON_CAN_RECREATE;
773 +               #endif
774 +       }
775 +       SPH_RET(0);
776 +}
777 +
778 +
779 +#if MYSQL_VERSION_ID<50100
780 +static bool sphinx_init_func_for_handlerton ()
781 +{
782 +       return sphinx_init_func ( &sphinx_hton );
783 +}
784 +#endif
785 +
786 +
787 +#if MYSQL_VERSION_ID>50100
788 +
789 +static int sphinx_close_connection ( handlerton * hton, THD * thd )
790 +{
791 +       // deallocate common handler data
792 +       SPH_ENTER_FUNC();
793 +       void ** tmp = thd_ha_data ( thd, hton );
794 +       CSphSEThreadData * pTls = (CSphSEThreadData*) (*tmp);
795 +       SafeDelete ( pTls );
796 +       *tmp = NULL;
797 +       SPH_RET(0);
798 +}
799 +
800 +
801 +static int sphinx_done_func ( void * )
802 +{
803 +       SPH_ENTER_FUNC();
804 +
805 +       int error = 0;
806 +       if ( sphinx_init )
807 +       {
808 +               sphinx_init = 0;
809 +               if ( sphinx_open_tables.records )
810 +                       error = 1;
811 +               sphinx_hash_free ( &sphinx_open_tables );
812 +               pthread_mutex_destroy ( &sphinx_mutex );
813 +       }
814 +
815 +       SPH_RET(0);
816 +}
817 +
818 +
819 +static int sphinx_panic ( handlerton * hton, enum ha_panic_function )
820 +{
821 +       return sphinx_done_func ( hton );
822 +}
823 +
824 +#else
825 +
826 +static int sphinx_close_connection ( THD * thd )
827 +{
828 +       // deallocate common handler data
829 +       SPH_ENTER_FUNC();
830 +       CSphSEThreadData * pTls = (CSphSEThreadData*) thd->ha_data[sphinx_hton.slot];
831 +       SafeDelete ( pTls );
832 +       thd->ha_data[sphinx_hton.slot] = NULL;
833 +       SPH_RET(0);
834 +}
835 +
836 +#endif // >50100
837 +
838 +//////////////////////////////////////////////////////////////////////////////
839 +// SHOW STATUS
840 +//////////////////////////////////////////////////////////////////////////////
841 +
842 +#if MYSQL_VERSION_ID>50100
843 +static bool sphinx_show_status ( handlerton * hton, THD * thd, stat_print_fn * stat_print,
844 +       enum ha_stat_type )
845 +#else
846 +bool sphinx_show_status ( THD * thd )
847 +#endif
848 +{
849 +       SPH_ENTER_FUNC();
850 +
851 +#if MYSQL_VERSION_ID<50100
852 +       Protocol * protocol = thd->protocol;
853 +       List<Item> field_list;
854 +#endif
855 +
856 +       char buf1[IO_SIZE];
857 +       uint buf1len;
858 +       char buf2[IO_SIZE];
859 +       uint buf2len = 0;
860 +       String words;
861 +
862 +       buf1[0] = '\0';
863 +       buf2[0] = '\0';
864 +
865 +
866 +#if MYSQL_VERSION_ID>50100
867 +       // 5.1.x style stats
868 +       CSphSEThreadData * pTls = (CSphSEThreadData*) ( *thd_ha_data ( thd, hton ) );
869 +
870 +#define LOC_STATS(_key,_keylen,_val,_vallen) \
871 +       stat_print ( thd, sphinx_hton_name, strlen(sphinx_hton_name), _key, _keylen, _val, _vallen );
872 +
873 +#else
874 +       // 5.0.x style stats
875 +       if ( have_sphinx_db!=SHOW_OPTION_YES )
876 +       {
877 +               my_message ( ER_NOT_SUPPORTED_YET,
878 +                       "failed to call SHOW SPHINX STATUS: --skip-sphinx was specified",
879 +                       MYF(0) );
880 +               SPH_RET(TRUE);
881 +       }
882 +       CSphSEThreadData * pTls = (CSphSEThreadData*) thd->ha_data[sphinx_hton.slot];
883 +
884 +       field_list.push_back ( new Item_empty_string ( "Type", 10 ) );
885 +       field_list.push_back ( new Item_empty_string ( "Name", FN_REFLEN ) );
886 +       field_list.push_back ( new Item_empty_string ( "Status", 10 ) );
887 +       if ( protocol->send_fields ( &field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF ) )
888 +               SPH_RET(TRUE);
889 +
890 +#define LOC_STATS(_key,_keylen,_val,_vallen) \
891 +       protocol->prepare_for_resend (); \
892 +       protocol->store ( "SPHINX", 6, system_charset_info ); \
893 +       protocol->store ( _key, _keylen, system_charset_info ); \
894 +       protocol->store ( _val, _vallen, system_charset_info ); \
895 +       if ( protocol->write() ) \
896 +               SPH_RET(TRUE);
897 +
898 +#endif
899 +
900 +
901 +       // show query stats
902 +       if ( pTls && pTls->m_bStats )
903 +       {
904 +               const CSphSEStats * pStats = &pTls->m_tStats;
905 +               buf1len = my_snprintf ( buf1, sizeof(buf1),
906 +                       "total: %d, total found: %d, time: %d, words: %d",
907 +                       pStats->m_iMatchesTotal, pStats->m_iMatchesFound, pStats->m_iQueryMsec, pStats->m_iWords );
908 +
909 +               LOC_STATS ( "stats", 5, buf1, buf1len );
910 +
911 +               if ( pStats->m_iWords )
912 +               {
913 +                       for ( int i=0; i<pStats->m_iWords; i++ )
914 +                       {
915 +                               CSphSEWordStats & tWord = pStats->m_dWords[i];
916 +                               buf2len = my_snprintf ( buf2, sizeof(buf2), "%s%s:%d:%d ",
917 +                                       buf2, tWord.m_sWord, tWord.m_iDocs, tWord.m_iHits );
918 +                       }
919 +
920 +                       // convert it if we can
921 +                       const char * sWord = buf2;
922 +                       int iWord = buf2len;
923 +
924 +                       String sBuf3;
925 +                       if ( pTls->m_pQueryCharset )
926 +                       {
927 +                               uint iErrors;
928 +                               sBuf3.copy ( buf2, buf2len, pTls->m_pQueryCharset, system_charset_info, &iErrors );
929 +                               sWord = sBuf3.c_ptr();
930 +                               iWord = sBuf3.length();
931 +                       }
932 +
933 +                       LOC_STATS ( "words", 5, sWord, iWord );
934 +               }
935 +       }
936 +
937 +       // show last error or warning (either in addition to stats, or on their own)
938 +       if ( pTls && pTls->m_tStats.m_sLastMessage && pTls->m_tStats.m_sLastMessage[0] )
939 +       {
940 +               const char * sMessageType = pTls->m_tStats.m_bLastError ? "error" : "warning";
941 +
942 +               LOC_STATS (
943 +                       sMessageType, strlen ( sMessageType ),
944 +                       pTls->m_tStats.m_sLastMessage, strlen ( pTls->m_tStats.m_sLastMessage ) );
945 +
946 +       } else
947 +       {
948 +               // well, nothing to show just yet
949 +#if MYSQL_VERSION_ID < 50100
950 +               LOC_STATS ( "stats", 5, "no query has been executed yet", sizeof("no query has been executed yet")-1 );
951 +#endif
952 +       }
953 +
954 +#if MYSQL_VERSION_ID < 50100
955 +       send_eof(thd);
956 +#endif
957 +
958 +       SPH_RET(FALSE);
959 +}
960 +
961 +//////////////////////////////////////////////////////////////////////////////
962 +// HELPERS
963 +//////////////////////////////////////////////////////////////////////////////
964 +
965 +static char * sphDup ( const char * sSrc, int iLen=-1 )
966 +{
967 +       if ( !sSrc )
968 +               return NULL;
969 +
970 +       if ( iLen<0 )
971 +               iLen = strlen(sSrc);
972 +
973 +       char * sRes = new char [ 1+iLen ];
974 +       memcpy ( sRes, sSrc, iLen );
975 +       sRes[iLen] = '\0';
976 +       return sRes;
977 +}
978 +
979 +
980 +static void sphLogError ( const char * sFmt, ... )
981 +{
982 +       // emit timestamp
983 +#ifdef __WIN__
984 +       SYSTEMTIME t;
985 +       GetLocalTime ( &t );
986 +
987 +       fprintf ( stderr, "%02d%02d%02d %2d:%02d:%02d SphinxSE: internal error: ",
988 +               (int)t.wYear % 100, (int)t.wMonth, (int)t.wDay,
989 +               (int)t.wHour, (int)t.wMinute, (int)t.wSecond );
990 +#else
991 +       // Unix version
992 +       time_t tStamp;
993 +       time ( &tStamp );
994 +
995 +       struct tm * pParsed;
996 +#ifdef HAVE_LOCALTIME_R
997 +       struct tm tParsed;
998 +       localtime_r ( &tStamp, &tParsed );
999 +       pParsed = &tParsed;
1000 +#else
1001 +       pParsed = localtime ( &tStamp );
1002 +#endif // HAVE_LOCALTIME_R
1003 +
1004 +       fprintf ( stderr, "%02d%02d%02d %2d:%02d:%02d SphinxSE: internal error: ",
1005 +               pParsed->tm_year % 100, pParsed->tm_mon + 1, pParsed->tm_mday,
1006 +               pParsed->tm_hour, pParsed->tm_min, pParsed->tm_sec);
1007 +#endif // __WIN__
1008 +
1009 +       // emit message
1010 +       va_list ap;
1011 +       va_start ( ap, sFmt );
1012 +       vfprintf ( stderr, sFmt, ap );
1013 +       va_end ( ap );
1014 +
1015 +       // emit newline
1016 +       fprintf ( stderr, "\n" );
1017 +}
1018 +
1019 +
1020 +
1021 +// the following scheme variants are recognized
1022 +//
1023 +// sphinx://host[:port]/index
1024 +// sphinxql://host[:port]/index
1025 +// unix://unix/domain/socket[:index]
1026 +static bool ParseUrl ( CSphSEShare * share, TABLE * table, bool bCreate )
1027 +{
1028 +       SPH_ENTER_FUNC();
1029 +
1030 +       if ( share )
1031 +       {
1032 +               // check incoming stuff
1033 +               if ( !table )
1034 +               {
1035 +                       sphLogError ( "table==NULL in ParseUrl()" );
1036 +                       return false;
1037 +               }
1038 +               if ( !table->s )
1039 +               {
1040 +                       sphLogError ( "(table->s)==NULL in ParseUrl()" );
1041 +                       return false;
1042 +               }
1043 +
1044 +               // free old stuff
1045 +               share->ResetTable ();
1046 +
1047 +               // fill new stuff
1048 +               share->m_iTableFields = table->s->fields;
1049 +               if ( share->m_iTableFields )
1050 +               {
1051 +                       share->m_sTableField = new char * [ share->m_iTableFields ];
1052 +                       share->m_eTableFieldType = new enum_field_types [ share->m_iTableFields ];
1053 +
1054 +                       for ( int i=0; i<share->m_iTableFields; i++ )
1055 +                       {
1056 +                               share->m_sTableField[i] = sphDup ( table->field[i]->field_name );
1057 +                               share->m_eTableFieldType[i] = table->field[i]->type();
1058 +                       }
1059 +               }
1060 +       }
1061 +
1062 +       // defaults
1063 +       bool bOk = true;
1064 +       bool bQL = false;
1065 +       char * sScheme = NULL;
1066 +       char * sHost = SPHINXAPI_DEFAULT_HOST;
1067 +       char * sIndex = SPHINXAPI_DEFAULT_INDEX;
1068 +       int iPort = SPHINXAPI_DEFAULT_PORT;
1069 +
1070 +       // parse connection string, if any
1071 +       while ( table->s->connect_string.length!=0 )
1072 +       {
1073 +               sScheme = sphDup ( table->s->connect_string.str, table->s->connect_string.length );
1074 +
1075 +               sHost = strstr ( sScheme, "://" );
1076 +               if ( !sHost )
1077 +               {
1078 +                       bOk = false;
1079 +                       break;
1080 +               }
1081 +               sHost[0] = '\0';
1082 +               sHost += 3;
1083 +
1084 +               /////////////////////////////
1085 +               // sphinxapi via unix socket
1086 +               /////////////////////////////
1087 +
1088 +               if ( !strcmp ( sScheme, "unix" ) )
1089 +               {
1090 +                       sHost--; // reuse last slash
1091 +                       iPort = 0;
1092 +                       if (!( sIndex = strrchr ( sHost, ':' ) ))
1093 +                               sIndex = SPHINXAPI_DEFAULT_INDEX;
1094 +                       else
1095 +                       {
1096 +                               *sIndex++ = '\0';
1097 +                               if ( !*sIndex )
1098 +                                       sIndex = SPHINXAPI_DEFAULT_INDEX;
1099 +                       }
1100 +                       bOk = true;
1101 +                       break;
1102 +               }
1103 +
1104 +               /////////////////////
1105 +               // sphinxapi via tcp
1106 +               /////////////////////
1107 +
1108 +               if ( !strcmp ( sScheme, "sphinx" ) )
1109 +               {
1110 +                       char * sPort = strchr ( sHost, ':' );
1111 +                       if ( sPort )
1112 +                       {
1113 +                               *sPort++ = '\0';
1114 +                               if ( *sPort )
1115 +                               {
1116 +                                       sIndex = strchr ( sPort, '/' );
1117 +                                       if ( sIndex )
1118 +                                               *sIndex++ = '\0';
1119 +                                       else
1120 +                                               sIndex = SPHINXAPI_DEFAULT_INDEX;
1121 +
1122 +                                       iPort = atoi(sPort);
1123 +                                       if ( !iPort )
1124 +                                               iPort = SPHINXAPI_DEFAULT_PORT;
1125 +                               }
1126 +                       } else
1127 +                       {
1128 +                               sIndex = strchr ( sHost, '/' );
1129 +                               if ( sIndex )
1130 +                                       *sIndex++ = '\0';
1131 +                               else
1132 +                                       sIndex = SPHINXAPI_DEFAULT_INDEX;
1133 +                       }
1134 +                       bOk = true;
1135 +                       break;
1136 +               }
1137 +
1138 +               ////////////
1139 +               // sphinxql
1140 +               ////////////
1141 +
1142 +               if ( !strcmp ( sScheme, "sphinxql" ) )
1143 +               {
1144 +                       bQL = true;
1145 +                       iPort = SPHINXQL_DEFAULT_PORT;
1146 +
1147 +                       // handle port
1148 +                       char * sPort = strchr ( sHost, ':' );
1149 +                       sIndex = sHost; // starting point for index name search
1150 +
1151 +                       if ( sPort )
1152 +                       {
1153 +                               *sPort++ = '\0';
1154 +                               sIndex = sPort;
1155 +
1156 +                               iPort = atoi(sPort);
1157 +                               if ( !iPort )
1158 +                               {
1159 +                                       bOk = false; // invalid port; can report ER_FOREIGN_DATA_STRING_INVALID
1160 +                                       break;
1161 +                               }
1162 +                       }
1163 +
1164 +                       // find index
1165 +                       sIndex = strchr ( sIndex, '/' );
1166 +                       if ( sIndex )
1167 +                               *sIndex++ = '\0';
1168 +
1169 +                       // final checks
1170 +                       // host and index names are required
1171 +                       bOk = ( sHost && *sHost && sIndex && *sIndex );
1172 +                       break;
1173 +               }
1174 +
1175 +               // unknown case
1176 +               bOk = false;
1177 +               break;
1178 +       }
1179 +
1180 +       if ( !bOk )
1181 +       {
1182 +               my_error ( bCreate ? ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE : ER_FOREIGN_DATA_STRING_INVALID,
1183 +                       MYF(0), table->s->connect_string );
1184 +       } else
1185 +       {
1186 +               if ( share )
1187 +               {
1188 +                       SafeDeleteArray ( share->m_sScheme );
1189 +                       share->m_sScheme = sScheme;
1190 +                       share->m_sHost = sHost;
1191 +                       share->m_sIndex = sIndex;
1192 +                       share->m_iPort = (ushort)iPort;
1193 +                       share->m_bSphinxQL = bQL;
1194 +               }
1195 +       }
1196 +       if ( !bOk && !share )
1197 +               SafeDeleteArray ( sScheme );
1198 +
1199 +       SPH_RET(bOk);
1200 +}
1201 +
1202 +
1203 +// Example of simple lock controls. The "share" it creates is structure we will
1204 +// pass to each sphinx handler. Do you have to have one of these? Well, you have
1205 +// pieces that are used for locking, and they are needed to function.
1206 +static CSphSEShare * get_share ( const char * table_name, TABLE * table )
1207 +{
1208 +       SPH_ENTER_FUNC();
1209 +       pthread_mutex_lock ( &sphinx_mutex );
1210 +
1211 +       CSphSEShare * pShare = NULL;
1212 +       for ( ;; )
1213 +       {
1214 +               // check if we already have this share
1215 +#if MYSQL_VERSION_ID>=50120
1216 +               pShare = (CSphSEShare*) sphinx_hash_search ( &sphinx_open_tables, (const uchar *) table_name, strlen(table_name) );
1217 +#else
1218 +#ifdef __WIN__
1219 +               pShare = (CSphSEShare*) sphinx_hash_search ( &sphinx_open_tables, (const byte *) table_name, strlen(table_name) );
1220 +#else
1221 +               pShare = (CSphSEShare*) sphinx_hash_search ( &sphinx_open_tables, table_name, strlen(table_name) );
1222 +#endif // win
1223 +#endif // pre-5.1.20
1224 +
1225 +               if ( pShare )
1226 +               {
1227 +                       pShare->m_iUseCount++;
1228 +                       break;
1229 +               }
1230 +
1231 +               // try to allocate new share
1232 +               pShare = new CSphSEShare ();
1233 +               if ( !pShare )
1234 +                       break;
1235 +
1236 +               // try to setup it
1237 +               if ( !ParseUrl ( pShare, table, false ) )
1238 +               {
1239 +                       SafeDelete ( pShare );
1240 +                       break;
1241 +               }
1242 +
1243 +               if ( !pShare->m_bSphinxQL )
1244 +                       pShare->m_pTableQueryCharset = table->field[2]->charset();
1245 +
1246 +               // try to hash it
1247 +               pShare->m_iTableNameLen = strlen(table_name);
1248 +               pShare->m_sTable = sphDup ( table_name );
1249 +               if ( my_hash_insert ( &sphinx_open_tables, (const byte *)pShare ) )
1250 +               {
1251 +                       SafeDelete ( pShare );
1252 +                       break;
1253 +               }
1254 +
1255 +               // all seems fine
1256 +               break;
1257 +       }
1258 +
1259 +       pthread_mutex_unlock ( &sphinx_mutex );
1260 +       SPH_RET(pShare);
1261 +}
1262 +
1263 +
1264 +// Free lock controls. We call this whenever we close a table. If the table had
1265 +// the last reference to the share then we free memory associated with it.
1266 +static int free_share ( CSphSEShare * pShare )
1267 +{
1268 +       SPH_ENTER_FUNC();
1269 +       pthread_mutex_lock ( &sphinx_mutex );
1270 +
1271 +       if ( !--pShare->m_iUseCount )
1272 +       {
1273 +               sphinx_hash_delete ( &sphinx_open_tables, (byte *)pShare );
1274 +               SafeDelete ( pShare );
1275 +       }
1276 +
1277 +       pthread_mutex_unlock ( &sphinx_mutex );
1278 +       SPH_RET(0);
1279 +}
1280 +
1281 +
1282 +#if MYSQL_VERSION_ID>50100
1283 +static handler * sphinx_create_handler ( handlerton * hton, TABLE_SHARE * table, MEM_ROOT * mem_root )
1284 +{
1285 +       sphinx_hton_ptr = hton;
1286 +       return new ( mem_root ) ha_sphinx ( hton, table );
1287 +}
1288 +#endif
1289 +
1290 +//////////////////////////////////////////////////////////////////////////////
1291 +// CLIENT-SIDE REQUEST STUFF
1292 +//////////////////////////////////////////////////////////////////////////////
1293 +
1294 +CSphSEQuery::CSphSEQuery ( const char * sQuery, int iLength, const char * sIndex )
1295 +       : m_sHost ( "" )
1296 +       , m_iPort ( 0 )
1297 +       , m_sIndex ( sIndex ? sIndex : "*" )
1298 +       , m_iOffset ( 0 )
1299 +       , m_iLimit ( 20 )
1300 +       , m_bQuery ( false )
1301 +       , m_sQuery ( "" )
1302 +       , m_pWeights ( NULL )
1303 +       , m_iWeights ( 0 )
1304 +       , m_eMode ( SPH_MATCH_ALL )
1305 +       , m_eRanker ( SPH_RANK_PROXIMITY_BM25 )
1306 +       , m_eSort ( SPH_SORT_RELEVANCE )
1307 +       , m_sSortBy ( "" )
1308 +       , m_iMaxMatches ( 1000 )
1309 +       , m_iMaxQueryTime ( 0 )
1310 +       , m_iMinID ( 0 )
1311 +       , m_iMaxID ( 0 )
1312 +       , m_iFilters ( 0 )
1313 +       , m_eGroupFunc ( SPH_GROUPBY_DAY )
1314 +       , m_sGroupBy ( "" )
1315 +       , m_sGroupSortBy ( "@group desc" )
1316 +       , m_iCutoff ( 0 )
1317 +       , m_iRetryCount ( 0 )
1318 +       , m_iRetryDelay ( 0 )
1319 +       , m_sGroupDistinct ( "" )
1320 +       , m_iIndexWeights ( 0 )
1321 +       , m_iFieldWeights ( 0 )
1322 +       , m_bGeoAnchor ( false )
1323 +       , m_sGeoLatAttr ( "" )
1324 +       , m_sGeoLongAttr ( "" )
1325 +       , m_fGeoLatitude ( 0.0f )
1326 +       , m_fGeoLongitude ( 0.0f )
1327 +       , m_sComment ( "" )
1328 +       , m_sSelect ( "" )
1329 +
1330 +       , m_pBuf ( NULL )
1331 +       , m_pCur ( NULL )
1332 +       , m_iBufLeft ( 0 )
1333 +       , m_bBufOverrun ( false )
1334 +{
1335 +       m_sQueryBuffer = new char [ iLength+2 ];
1336 +       memcpy ( m_sQueryBuffer, sQuery, iLength );
1337 +       m_sQueryBuffer[iLength] = ';';
1338 +       m_sQueryBuffer[iLength+1] = '\0';
1339 +}
1340 +
1341 +
1342 +CSphSEQuery::~CSphSEQuery ()
1343 +{
1344 +       SPH_ENTER_METHOD();
1345 +       SafeDeleteArray ( m_sQueryBuffer );
1346 +       SafeDeleteArray ( m_pWeights );
1347 +       SafeDeleteArray ( m_pBuf );
1348 +       for ( int i=0; i<m_dOverrides.elements(); i++ )
1349 +               SafeDelete ( m_dOverrides.at(i) );
1350 +       SPH_VOID_RET();
1351 +}
1352 +
1353 +
1354 +template < typename T >
1355 +int CSphSEQuery::ParseArray ( T ** ppValues, const char * sValue )
1356 +{
1357 +       SPH_ENTER_METHOD();
1358 +
1359 +       assert ( ppValues );
1360 +       assert ( !(*ppValues) );
1361 +
1362 +       const char * pValue;
1363 +       bool bPrevDigit = false;
1364 +       int iValues = 0;
1365 +
1366 +       // count the values
1367 +       for ( pValue=sValue; *pValue; pValue++ )
1368 +       {
1369 +               bool bDigit = (*pValue)>='0' && (*pValue)<='9';
1370 +               if ( bDigit && !bPrevDigit )
1371 +                       iValues++;
1372 +               bPrevDigit = bDigit;
1373 +       }
1374 +       if ( !iValues )
1375 +               SPH_RET(0);
1376 +
1377 +       // extract the values
1378 +       T * pValues = new T [ iValues ];
1379 +       *ppValues = pValues;
1380 +
1381 +       int iIndex = 0, iSign = 1;
1382 +       T uValue = 0;
1383 +
1384 +       bPrevDigit = false;
1385 +       for ( pValue=sValue ;; pValue++ )
1386 +       {
1387 +               bool bDigit = (*pValue)>='0' && (*pValue)<='9';
1388 +
1389 +               if ( bDigit )
1390 +               {
1391 +                       if ( !bPrevDigit )
1392 +                               uValue = 0;
1393 +                       uValue = uValue*10 + ( (*pValue)-'0' );
1394 +               } else if ( bPrevDigit )
1395 +               {
1396 +                       assert ( iIndex<iValues );
1397 +                       pValues [ iIndex++ ] = uValue * iSign;
1398 +                       iSign = 1;
1399 +               } else if ( *pValue=='-' )
1400 +                       iSign = -1;
1401 +
1402 +               bPrevDigit = bDigit;
1403 +               if ( !*pValue )
1404 +                       break;
1405 +       }
1406 +
1407 +       SPH_RET ( iValues );
1408 +}
1409 +
1410 +
1411 +static char * chop ( char * s )
1412 +{
1413 +       while ( *s && isspace(*s) )
1414 +               s++;
1415 +
1416 +       char * p = s + strlen(s);
1417 +       while ( p>s && isspace ( p[-1] ) )
1418 +               p--;
1419 +       *p = '\0';
1420 +
1421 +       return s;
1422 +}
1423 +
1424 +
1425 +static bool myisattr ( char c )
1426 +{
1427 +       return
1428 +               ( c>='0' && c<='9' ) ||
1429 +               ( c>='a' && c<='z' ) ||
1430 +               ( c>='A' && c<='Z' ) ||
1431 +               c=='_';
1432 +}
1433 +
1434 +
1435 +bool CSphSEQuery::ParseField ( char * sField )
1436 +{
1437 +       SPH_ENTER_METHOD();
1438 +
1439 +       // look for option name/value separator
1440 +       char * sValue = strchr ( sField, '=' );
1441 +       if ( !sValue || sValue==sField || sValue[-1]=='\\' )
1442 +       {
1443 +               // by default let's assume it's just query
1444 +               if ( sField[0] )
1445 +               {
1446 +                       if ( m_bQuery )
1447 +                       {
1448 +                               snprintf ( m_sParseError, sizeof(m_sParseError), "search query already specified; '%s' is redundant", sField );
1449 +                               SPH_RET(false);
1450 +                       } else
1451 +                       {
1452 +                               m_sQuery = sField;
1453 +                               m_bQuery = true;
1454 +
1455 +                               // unescape only 1st one
1456 +                               char *s = sField, *d = sField;
1457 +                               int iSlashes = 0;
1458 +                               while ( *s )
1459 +                               {
1460 +                                       iSlashes = ( *s=='\\' ) ? iSlashes+1 : 0;
1461 +                                       if ( ( iSlashes%2 )==0 ) *d++ = *s;
1462 +                                       s++;
1463 +                               }
1464 +                               *d = '\0';
1465 +                       }
1466 +               }
1467 +               SPH_RET(true);
1468 +       }
1469 +
1470 +       // split
1471 +       *sValue++ = '\0';
1472 +       sValue = chop ( sValue );
1473 +       int iValue = atoi ( sValue );
1474 +
1475 +       // handle options
1476 +       char * sName = chop ( sField );
1477 +
1478 +       if ( !strcmp ( sName, "query" ) )                       m_sQuery = sValue;
1479 +       else if ( !strcmp ( sName, "host" ) )           m_sHost = sValue;
1480 +       else if ( !strcmp ( sName, "port" ) )           m_iPort = iValue;
1481 +       else if ( !strcmp ( sName, "index" ) )          m_sIndex = sValue;
1482 +       else if ( !strcmp ( sName, "offset" ) )         m_iOffset = iValue;
1483 +       else if ( !strcmp ( sName, "limit" ) )          m_iLimit = iValue;
1484 +       else if ( !strcmp ( sName, "weights" ) )        m_iWeights = ParseArray<uint32> ( &m_pWeights, sValue );
1485 +       else if ( !strcmp ( sName, "minid" ) )          m_iMinID = iValue;
1486 +       else if ( !strcmp ( sName, "maxid" ) )          m_iMaxID = iValue;
1487 +       else if ( !strcmp ( sName, "maxmatches" ) )     m_iMaxMatches = iValue;
1488 +       else if ( !strcmp ( sName, "maxquerytime" ) )   m_iMaxQueryTime = iValue;
1489 +       else if ( !strcmp ( sName, "groupsort" ) )      m_sGroupSortBy = sValue;
1490 +       else if ( !strcmp ( sName, "distinct" ) )       m_sGroupDistinct = sValue;
1491 +       else if ( !strcmp ( sName, "cutoff" ) )         m_iCutoff = iValue;
1492 +       else if ( !strcmp ( sName, "comment" ) )        m_sComment = sValue;
1493 +       else if ( !strcmp ( sName, "select" ) )         m_sSelect = sValue;
1494 +
1495 +       else if ( !strcmp ( sName, "mode" ) )
1496 +       {
1497 +               m_eMode = SPH_MATCH_ALL;
1498 +               if ( !strcmp ( sValue, "any" ) )                        m_eMode = SPH_MATCH_ANY;
1499 +               else if ( !strcmp ( sValue, "phrase" ) )        m_eMode = SPH_MATCH_PHRASE;
1500 +               else if ( !strcmp ( sValue, "boolean" ) )       m_eMode = SPH_MATCH_BOOLEAN;
1501 +               else if ( !strcmp ( sValue, "ext" ) )           m_eMode = SPH_MATCH_EXTENDED;
1502 +               else if ( !strcmp ( sValue, "extended" ) )      m_eMode = SPH_MATCH_EXTENDED;
1503 +               else if ( !strcmp ( sValue, "ext2" ) )          m_eMode = SPH_MATCH_EXTENDED2;
1504 +               else if ( !strcmp ( sValue, "extended2" ) )     m_eMode = SPH_MATCH_EXTENDED2;
1505 +               else if ( !strcmp ( sValue, "all" ) )           m_eMode = SPH_MATCH_ALL;
1506 +               else if ( !strcmp ( sValue, "fullscan" ) )      m_eMode = SPH_MATCH_FULLSCAN;
1507 +               else
1508 +               {
1509 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "unknown matching mode '%s'", sValue );
1510 +                       SPH_RET(false);
1511 +               }
1512 +       } else if ( !strcmp ( sName, "ranker" ) )
1513 +       {
1514 +               m_eRanker = SPH_RANK_PROXIMITY_BM25;
1515 +               if ( !strcmp ( sValue, "proximity_bm25" ) )     m_eRanker = SPH_RANK_PROXIMITY_BM25;
1516 +               else if ( !strcmp ( sValue, "bm25" ) )          m_eRanker = SPH_RANK_BM25;
1517 +               else if ( !strcmp ( sValue, "none" ) )          m_eRanker = SPH_RANK_NONE;
1518 +               else if ( !strcmp ( sValue, "wordcount" ) )     m_eRanker = SPH_RANK_WORDCOUNT;
1519 +               else if ( !strcmp ( sValue, "proximity" ) )     m_eRanker = SPH_RANK_PROXIMITY;
1520 +               else if ( !strcmp ( sValue, "matchany" ) )      m_eRanker = SPH_RANK_MATCHANY;
1521 +               else if ( !strcmp ( sValue, "fieldmask" ) )     m_eRanker = SPH_RANK_FIELDMASK;
1522 +               else if ( !strcmp ( sValue, "sph04" ) )         m_eRanker = SPH_RANK_SPH04;
1523 +               else
1524 +               {
1525 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "unknown ranking mode '%s'", sValue );
1526 +                       SPH_RET(false);
1527 +               }
1528 +       } else if ( !strcmp ( sName, "sort" ) )
1529 +       {
1530 +               static const struct
1531 +               {
1532 +                       const char *    m_sName;
1533 +                       ESphSortOrder   m_eSort;
1534 +               } dSortModes[] =
1535 +               {
1536 +                       { "relevance",          SPH_SORT_RELEVANCE },
1537 +                       { "attr_desc:",         SPH_SORT_ATTR_DESC },
1538 +                       { "attr_asc:",          SPH_SORT_ATTR_ASC },
1539 +                       { "time_segments:",     SPH_SORT_TIME_SEGMENTS },
1540 +                       { "extended:",          SPH_SORT_EXTENDED },
1541 +                       { "expr:",                      SPH_SORT_EXPR }
1542 +               };
1543 +
1544 +               int i;
1545 +               const int nModes = sizeof(dSortModes)/sizeof(dSortModes[0]);
1546 +               for ( i=0; i<nModes; i++ )
1547 +                       if ( !strncmp ( sValue, dSortModes[i].m_sName, strlen ( dSortModes[i].m_sName ) ) )
1548 +               {
1549 +                       m_eSort = dSortModes[i].m_eSort;
1550 +                       m_sSortBy = sValue + strlen ( dSortModes[i].m_sName );
1551 +                       break;
1552 +               }
1553 +               if ( i==nModes )
1554 +               {
1555 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "unknown sorting mode '%s'", sValue );
1556 +                       SPH_RET(false);
1557 +               }
1558 +
1559 +       } else if ( !strcmp ( sName, "groupby" ) )
1560 +       {
1561 +               static const struct
1562 +               {
1563 +                       const char *    m_sName;
1564 +                       ESphGroupBy             m_eFunc;
1565 +               } dGroupModes[] =
1566 +               {
1567 +                       { "day:",       SPH_GROUPBY_DAY },
1568 +                       { "week:",      SPH_GROUPBY_WEEK },
1569 +                       { "month:",     SPH_GROUPBY_MONTH },
1570 +                       { "year:",      SPH_GROUPBY_YEAR },
1571 +                       { "attr:",      SPH_GROUPBY_ATTR },
1572 +               };
1573 +
1574 +               int i;
1575 +               const int nModes = sizeof(dGroupModes)/sizeof(dGroupModes[0]);
1576 +               for ( i=0; i<nModes; i++ )
1577 +                       if ( !strncmp ( sValue, dGroupModes[i].m_sName, strlen ( dGroupModes[i].m_sName ) ) )
1578 +               {
1579 +                       m_eGroupFunc = dGroupModes[i].m_eFunc;
1580 +                       m_sGroupBy = sValue + strlen ( dGroupModes[i].m_sName );
1581 +                       break;
1582 +               }
1583 +               if ( i==nModes )
1584 +               {
1585 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "unknown groupby mode '%s'", sValue );
1586 +                       SPH_RET(false);
1587 +               }
1588 +
1589 +       } else if ( m_iFilters<SPHINXSE_MAX_FILTERS &&
1590 +               ( !strcmp ( sName, "range" ) || !strcmp ( sName, "!range" ) || !strcmp ( sName, "floatrange" ) || !strcmp ( sName, "!floatrange" ) ) )
1591 +       {
1592 +               for ( ;; )
1593 +               {
1594 +                       char * p = sName;
1595 +                       CSphSEFilter & tFilter = m_dFilters [ m_iFilters ];
1596 +                       tFilter.m_bExclude = ( *p=='!' ); if ( tFilter.m_bExclude ) p++;
1597 +                       tFilter.m_eType = ( *p=='f' ) ? SPH_FILTER_FLOATRANGE : SPH_FILTER_RANGE;
1598 +
1599 +                       if (!( p = strchr ( sValue, ',' ) ))
1600 +                               break;
1601 +                       *p++ = '\0';
1602 +
1603 +                       tFilter.m_sAttrName = chop ( sValue );
1604 +                       sValue = p;
1605 +
1606 +                       if (!( p = strchr ( sValue, ',' ) ))
1607 +                               break;
1608 +                       *p++ = '\0';
1609 +
1610 +                       if ( tFilter.m_eType==SPH_FILTER_RANGE )
1611 +                       {
1612 +                               tFilter.m_uMinValue = strtoll ( sValue, NULL, 0 );
1613 +                               tFilter.m_uMaxValue = strtoll ( p, NULL, 0 );
1614 +                       } else
1615 +                       {
1616 +                               tFilter.m_fMinValue = (float)atof(sValue);
1617 +                               tFilter.m_fMaxValue = (float)atof(p);
1618 +                       }
1619 +
1620 +                       // all ok
1621 +                       m_iFilters++;
1622 +                       break;
1623 +               }
1624 +
1625 +       } else if ( m_iFilters<SPHINXSE_MAX_FILTERS &&
1626 +               ( !strcmp ( sName, "filter" ) || !strcmp ( sName, "!filter" ) ) )
1627 +       {
1628 +               for ( ;; )
1629 +               {
1630 +                       CSphSEFilter & tFilter = m_dFilters [ m_iFilters ];
1631 +                       tFilter.m_eType = SPH_FILTER_VALUES;
1632 +                       tFilter.m_bExclude = ( strcmp ( sName, "!filter" )==0 );
1633 +
1634 +                       // get the attr name
1635 +                       while ( (*sValue) && !myisattr(*sValue) )
1636 +                               sValue++;
1637 +                       if ( !*sValue )
1638 +                               break;
1639 +
1640 +                       tFilter.m_sAttrName = sValue;
1641 +                       while ( (*sValue) && myisattr(*sValue) )
1642 +                               sValue++;
1643 +                       if ( !*sValue )
1644 +                               break;
1645 +                       *sValue++ = '\0';
1646 +
1647 +                       // get the values
1648 +                       tFilter.m_iValues = ParseArray<longlong> ( &tFilter.m_pValues, sValue );
1649 +                       if ( !tFilter.m_iValues )
1650 +                       {
1651 +                               assert ( !tFilter.m_pValues );
1652 +                               break;
1653 +                       }
1654 +
1655 +                       // all ok
1656 +                       m_iFilters++;
1657 +                       break;
1658 +               }
1659 +
1660 +       } else if ( !strcmp ( sName, "indexweights" ) || !strcmp ( sName, "fieldweights" ) )
1661 +       {
1662 +               bool bIndex = !strcmp ( sName, "indexweights" );
1663 +               int * pCount = bIndex ? &m_iIndexWeights : &m_iFieldWeights;
1664 +               char ** pNames = bIndex ? &m_sIndexWeight[0] : &m_sFieldWeight[0];
1665 +               int * pWeights = bIndex ? &m_iIndexWeight[0] : &m_iFieldWeight[0];
1666 +
1667 +               *pCount = 0;
1668 +
1669 +               char * p = sValue;
1670 +               while ( *p && *pCount<SPHINXSE_MAX_FILTERS )
1671 +               {
1672 +                       // extract attr name
1673 +                       if ( !myisattr(*p) )
1674 +                       {
1675 +                               snprintf ( m_sParseError, sizeof(m_sParseError), "%s: index name expected near '%s'", sName, p );
1676 +                               SPH_RET(false);
1677 +                       }
1678 +
1679 +                       pNames[*pCount] = p;
1680 +                       while ( myisattr(*p) ) p++;
1681 +
1682 +                       if ( *p!=',' )
1683 +                       {
1684 +                               snprintf ( m_sParseError, sizeof(m_sParseError), "%s: comma expected near '%s'", sName, p );
1685 +                               SPH_RET(false);
1686 +                       }
1687 +                       *p++ = '\0';
1688 +
1689 +                       // extract attr value
1690 +                       char * sVal = p;
1691 +                       while ( isdigit(*p) ) p++;
1692 +                       if ( p==sVal )
1693 +                       {
1694 +                               snprintf ( m_sParseError, sizeof(m_sParseError), "%s: integer weight expected near '%s'", sName, sVal );
1695 +                               SPH_RET(false);
1696 +                       }
1697 +                       pWeights[*pCount] = atoi(sVal);
1698 +                       (*pCount)++;
1699 +
1700 +                       if ( !*p )
1701 +                               break;
1702 +                       if ( *p!=',' )
1703 +                       {
1704 +                               snprintf ( m_sParseError, sizeof(m_sParseError), "%s: comma expected near '%s'", sName, p );
1705 +                               SPH_RET(false);
1706 +                       }
1707 +                       p++;
1708 +               }
1709 +
1710 +       } else if ( !strcmp ( sName, "geoanchor" ) )
1711 +       {
1712 +               m_bGeoAnchor = false;
1713 +               for ( ;; )
1714 +               {
1715 +                       char * sLat = sValue;
1716 +                       char * p = sValue;
1717 +
1718 +                       if (!( p = strchr ( p, ',' ) )) break; *p++ = '\0';
1719 +                       char * sLong = p;
1720 +
1721 +                       if (!( p = strchr ( p, ',' ) )) break; *p++ = '\0';
1722 +                       char * sLatVal = p;
1723 +
1724 +                       if (!( p = strchr ( p, ',' ) )) break; *p++ = '\0';
1725 +                       char * sLongVal = p;
1726 +
1727 +                       m_sGeoLatAttr = chop(sLat);
1728 +                       m_sGeoLongAttr = chop(sLong);
1729 +                       m_fGeoLatitude = (float)atof ( sLatVal );
1730 +                       m_fGeoLongitude = (float)atof ( sLongVal );
1731 +                       m_bGeoAnchor = true;
1732 +                       break;
1733 +               }
1734 +               if ( !m_bGeoAnchor )
1735 +               {
1736 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "geoanchor: parse error, not enough comma-separated arguments" );
1737 +                       SPH_RET(false);
1738 +               }
1739 +       } else if ( !strcmp ( sName, "override" ) ) // name,type,id:value,id:value,...
1740 +       {
1741 +               char * sName = NULL;
1742 +               int iType = 0;
1743 +               CSphSEQuery::Override_t * pOverride = NULL;
1744 +
1745 +               // get name and type
1746 +               char * sRest = sValue;
1747 +               for ( ;; )
1748 +               {
1749 +                       sName = sRest;
1750 +                       if ( !*sName )
1751 +                               break;
1752 +                       if (!( sRest = strchr ( sRest, ',' ) ))
1753 +                               break;
1754 +                       *sRest++ = '\0';
1755 +                       char * sType = sRest;
1756 +                       if (!( sRest = strchr ( sRest, ',' ) ))
1757 +                               break;
1758 +
1759 +                       static const struct
1760 +                       {
1761 +                               const char *    m_sName;
1762 +                               int                             m_iType;
1763 +                       }
1764 +                       dAttrTypes[] =
1765 +                       {
1766 +                               { "int",                SPH_ATTR_INTEGER },
1767 +                               { "timestamp",  SPH_ATTR_TIMESTAMP },
1768 +                               { "bool",               SPH_ATTR_BOOL },
1769 +                               { "float",              SPH_ATTR_FLOAT },
1770 +                               { "bigint",             SPH_ATTR_BIGINT }
1771 +                       };
1772 +                       for ( int i=0; i<sizeof(dAttrTypes)/sizeof(*dAttrTypes); i++ )
1773 +                               if ( !strncmp ( sType, dAttrTypes[i].m_sName, sRest - sType ) )
1774 +                       {
1775 +                               iType = dAttrTypes[i].m_iType;
1776 +                               break;
1777 +                       }
1778 +                       break;
1779 +               }
1780 +
1781 +               // fail
1782 +               if ( !sName || !*sName || !iType )
1783 +               {
1784 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "override: malformed query" );
1785 +                       SPH_RET(false);
1786 +               }
1787 +
1788 +               // grab id:value pairs
1789 +               sRest++;
1790 +               while ( sRest )
1791 +               {
1792 +                       char * sId = sRest;
1793 +                       if (!( sRest = strchr ( sRest, ':' ) )) break; *sRest++ = '\0';
1794 +                       if (!( sRest - sId )) break;
1795 +
1796 +                       char * sValue = sRest;
1797 +                       if ( ( sRest = strchr ( sRest, ',' ) )!=NULL )
1798 +                               *sRest++ = '\0';
1799 +                       if ( !*sValue )
1800 +                               break;
1801 +
1802 +                       if ( !pOverride )
1803 +                       {
1804 +                               pOverride = new CSphSEQuery::Override_t;
1805 +                               pOverride->m_sName = chop(sName);
1806 +                               pOverride->m_iType = iType;
1807 +                               m_dOverrides.append ( pOverride );
1808 +                       }
1809 +
1810 +                       ulonglong uId = strtoull ( sId, NULL, 10 );
1811 +                       CSphSEQuery::Override_t::Value_t tValue;
1812 +                       if ( iType==SPH_ATTR_FLOAT )
1813 +                               tValue.m_fValue = (float)atof(sValue);
1814 +                       else if ( iType==SPH_ATTR_BIGINT )
1815 +                               tValue.m_iValue64 = strtoll ( sValue, NULL, 10 );
1816 +                       else
1817 +                               tValue.m_uValue = (uint32)strtoul ( sValue, NULL, 10 );
1818 +
1819 +                       pOverride->m_dIds.append ( uId );
1820 +                       pOverride->m_dValues.append ( tValue );
1821 +               }
1822 +
1823 +               if ( !pOverride )
1824 +               {
1825 +                       snprintf ( m_sParseError, sizeof(m_sParseError), "override: id:value mapping expected" );
1826 +                       SPH_RET(false);
1827 +               }
1828 +               SPH_RET(true);
1829 +       } else
1830 +       {
1831 +               snprintf ( m_sParseError, sizeof(m_sParseError), "unknown parameter '%s'", sName );
1832 +               SPH_RET(false);
1833 +       }
1834 +
1835 +       // !COMMIT handle syntax errors
1836 +
1837 +       SPH_RET(true);
1838 +}
1839 +
1840 +
1841 +bool CSphSEQuery::Parse ()
1842 +{
1843 +       SPH_ENTER_METHOD();
1844 +       SPH_DEBUG ( "query [[ %s ]]", m_sQueryBuffer );
1845 +
1846 +       m_bQuery = false;
1847 +       char * pCur = m_sQueryBuffer;
1848 +       char * pNext = pCur;
1849 +
1850 +       while ( ( pNext = strchr ( pNext, ';' ) )!=NULL )
1851 +       {
1852 +               // handle escaped semicolons
1853 +               if ( pNext>m_sQueryBuffer && pNext[-1]=='\\' && pNext[1]!='\0' )
1854 +               {
1855 +                       pNext++;
1856 +                       continue;
1857 +               }
1858 +
1859 +               // handle semicolon-separated clauses
1860 +               *pNext++ = '\0';
1861 +               if ( !ParseField ( pCur ) )
1862 +                       SPH_RET(false);
1863 +               pCur = pNext;
1864 +       }
1865 +
1866 +       SPH_DEBUG ( "q [[ %s ]]", m_sQuery );
1867 +
1868 +       SPH_RET(true);
1869 +}
1870 +
1871 +
1872 +void CSphSEQuery::SendBytes ( const void * pBytes, int iBytes )
1873 +{
1874 +       SPH_ENTER_METHOD();
1875 +       if ( m_iBufLeft<iBytes )
1876 +       {
1877 +               m_bBufOverrun = true;
1878 +               SPH_VOID_RET();
1879 +       }
1880 +
1881 +       memcpy ( m_pCur, pBytes, iBytes );
1882 +
1883 +       m_pCur += iBytes;
1884 +       m_iBufLeft -= iBytes;
1885 +       SPH_VOID_RET();
1886 +}
1887 +
1888 +
1889 +int CSphSEQuery::BuildRequest ( char ** ppBuffer )
1890 +{
1891 +       SPH_ENTER_METHOD();
1892 +
1893 +       // calc request length
1894 +       int iReqSize = 124 + 4*m_iWeights
1895 +               + strlen ( m_sSortBy )
1896 +               + strlen ( m_sQuery )
1897 +               + strlen ( m_sIndex )
1898 +               + strlen ( m_sGroupBy )
1899 +               + strlen ( m_sGroupSortBy )
1900 +               + strlen ( m_sGroupDistinct )
1901 +               + strlen ( m_sComment )
1902 +               + strlen ( m_sSelect );
1903 +       for ( int i=0; i<m_iFilters; i++ )
1904 +       {
1905 +               const CSphSEFilter & tFilter = m_dFilters[i];
1906 +               iReqSize += 12 + strlen ( tFilter.m_sAttrName ); // string attr-name; int type; int exclude-flag
1907 +               switch ( tFilter.m_eType )
1908 +               {
1909 +                       case SPH_FILTER_VALUES:         iReqSize += 4 + 8*tFilter.m_iValues; break;
1910 +                       case SPH_FILTER_RANGE:          iReqSize += 16; break;
1911 +                       case SPH_FILTER_FLOATRANGE:     iReqSize += 8; break;
1912 +               }
1913 +       }
1914 +       if ( m_bGeoAnchor ) // 1.14+
1915 +               iReqSize += 16 + strlen ( m_sGeoLatAttr ) + strlen ( m_sGeoLongAttr );
1916 +       for ( int i=0; i<m_iIndexWeights; i++ ) // 1.15+
1917 +               iReqSize += 8 + strlen(m_sIndexWeight[i] );
1918 +       for ( int i=0; i<m_iFieldWeights; i++ ) // 1.18+
1919 +               iReqSize += 8 + strlen(m_sFieldWeight[i] );
1920 +       // overrides
1921 +       iReqSize += 4;
1922 +       for ( int i=0; i<m_dOverrides.elements(); i++ )
1923 +       {
1924 +               CSphSEQuery::Override_t * pOverride = m_dOverrides.at(i);
1925 +               const uint32 uSize = pOverride->m_iType==SPH_ATTR_BIGINT ? 16 : 12; // id64 + value
1926 +               iReqSize += strlen ( pOverride->m_sName ) + 12 + uSize*pOverride->m_dIds.elements();
1927 +       }
1928 +       // select
1929 +       iReqSize += 4;
1930 +
1931 +       m_iBufLeft = 0;
1932 +       SafeDeleteArray ( m_pBuf );
1933 +
1934 +       m_pBuf = new char [ iReqSize ];
1935 +       if ( !m_pBuf )
1936 +               SPH_RET(-1);
1937 +
1938 +       m_pCur = m_pBuf;
1939 +       m_iBufLeft = iReqSize;
1940 +       m_bBufOverrun = false;
1941 +       (*ppBuffer) = m_pBuf;
1942 +
1943 +       // build request
1944 +       SendWord ( SEARCHD_COMMAND_SEARCH ); // command id
1945 +       SendWord ( VER_COMMAND_SEARCH ); // command version
1946 +       SendInt ( iReqSize-8 ); // packet body length
1947 +
1948 +       SendInt ( 1 ); // number of queries
1949 +       SendInt ( m_iOffset );
1950 +       SendInt ( m_iLimit );
1951 +       SendInt ( m_eMode );
1952 +       SendInt ( m_eRanker ); // 1.16+
1953 +       SendInt ( m_eSort );
1954 +       SendString ( m_sSortBy ); // sort attr
1955 +       SendString ( m_sQuery ); // query
1956 +       SendInt ( m_iWeights );
1957 +       for ( int j=0; j<m_iWeights; j++ )
1958 +               SendInt ( m_pWeights[j] ); // weights
1959 +       SendString ( m_sIndex ); // indexes
1960 +       SendInt ( 1 ); // id64 range follows
1961 +       SendUint64 ( m_iMinID ); // id/ts ranges
1962 +       SendUint64 ( m_iMaxID );
1963 +
1964 +       SendInt ( m_iFilters );
1965 +       for ( int j=0; j<m_iFilters; j++ )
1966 +       {
1967 +               const CSphSEFilter & tFilter = m_dFilters[j];
1968 +               SendString ( tFilter.m_sAttrName );
1969 +               SendInt ( tFilter.m_eType );
1970 +
1971 +               switch ( tFilter.m_eType )
1972 +               {
1973 +                       case SPH_FILTER_VALUES:
1974 +                               SendInt ( tFilter.m_iValues );
1975 +                               for ( int k=0; k<tFilter.m_iValues; k++ )
1976 +                                       SendUint64 ( tFilter.m_pValues[k] );
1977 +                               break;
1978 +
1979 +                       case SPH_FILTER_RANGE:
1980 +                               SendUint64 ( tFilter.m_uMinValue );
1981 +                               SendUint64 ( tFilter.m_uMaxValue );
1982 +                               break;
1983 +
1984 +                       case SPH_FILTER_FLOATRANGE:
1985 +                               SendFloat ( tFilter.m_fMinValue );
1986 +                               SendFloat ( tFilter.m_fMaxValue );
1987 +                               break;
1988 +               }
1989 +
1990 +               SendInt ( tFilter.m_bExclude );
1991 +       }
1992 +
1993 +       SendInt ( m_eGroupFunc );
1994 +       SendString ( m_sGroupBy );
1995 +       SendInt ( m_iMaxMatches );
1996 +       SendString ( m_sGroupSortBy );
1997 +       SendInt ( m_iCutoff ); // 1.9+
1998 +       SendInt ( m_iRetryCount ); // 1.10+
1999 +       SendInt ( m_iRetryDelay );
2000 +       SendString ( m_sGroupDistinct ); // 1.11+
2001 +       SendInt ( m_bGeoAnchor ); // 1.14+
2002 +       if ( m_bGeoAnchor )
2003 +       {
2004 +               SendString ( m_sGeoLatAttr );
2005 +               SendString ( m_sGeoLongAttr );
2006 +               SendFloat ( m_fGeoLatitude );
2007 +               SendFloat ( m_fGeoLongitude );
2008 +       }
2009 +       SendInt ( m_iIndexWeights ); // 1.15+
2010 +       for ( int i=0; i<m_iIndexWeights; i++ )
2011 +       {
2012 +               SendString ( m_sIndexWeight[i] );
2013 +               SendInt ( m_iIndexWeight[i] );
2014 +       }
2015 +       SendInt ( m_iMaxQueryTime ); // 1.17+
2016 +       SendInt ( m_iFieldWeights ); // 1.18+
2017 +       for ( int i=0; i<m_iFieldWeights; i++ )
2018 +       {
2019 +               SendString ( m_sFieldWeight[i] );
2020 +               SendInt ( m_iFieldWeight[i] );
2021 +       }
2022 +       SendString ( m_sComment );
2023 +
2024 +       // overrides
2025 +       SendInt ( m_dOverrides.elements() );
2026 +       for ( int i=0; i<m_dOverrides.elements(); i++ )
2027 +       {
2028 +               CSphSEQuery::Override_t * pOverride = m_dOverrides.at(i);
2029 +               SendString ( pOverride->m_sName );
2030 +               SendDword ( pOverride->m_iType );
2031 +               SendInt ( pOverride->m_dIds.elements() );
2032 +               for ( int j=0; j<pOverride->m_dIds.elements(); j++ )
2033 +               {
2034 +                       SendUint64 ( pOverride->m_dIds.at(j) );
2035 +                       if ( pOverride->m_iType==SPH_ATTR_FLOAT )
2036 +                               SendFloat ( pOverride->m_dValues.at(j).m_fValue );
2037 +                       else if ( pOverride->m_iType==SPH_ATTR_BIGINT )
2038 +                               SendUint64 ( pOverride->m_dValues.at(j).m_iValue64 );
2039 +                       else
2040 +                               SendDword ( pOverride->m_dValues.at(j).m_uValue );
2041 +               }
2042 +       }
2043 +
2044 +       // select
2045 +       SendString ( m_sSelect );
2046 +
2047 +       // detect buffer overruns and underruns, and report internal error
2048 +       if ( m_bBufOverrun || m_iBufLeft!=0 || m_pCur-m_pBuf!=iReqSize )
2049 +               SPH_RET(-1);
2050 +
2051 +       // all fine
2052 +       SPH_RET ( iReqSize );
2053 +}
2054 +
2055 +//////////////////////////////////////////////////////////////////////////////
2056 +// SPHINX HANDLER
2057 +//////////////////////////////////////////////////////////////////////////////
2058 +
2059 +static const char * ha_sphinx_exts[] = { NullS };
2060 +
2061 +
2062 +#if MYSQL_VERSION_ID<50100
2063 +ha_sphinx::ha_sphinx ( TABLE_ARG * table )
2064 +       : handler ( &sphinx_hton, table )
2065 +#else
2066 +ha_sphinx::ha_sphinx ( handlerton * hton, TABLE_ARG * table )
2067 +       : handler ( hton, table )
2068 +#endif
2069 +       , m_pShare ( NULL )
2070 +       , m_iMatchesTotal ( 0 )
2071 +       , m_iCurrentPos ( 0 )
2072 +       , m_pCurrentKey ( NULL )
2073 +       , m_iCurrentKeyLen ( 0 )
2074 +       , m_pResponse ( NULL )
2075 +       , m_pResponseEnd ( NULL )
2076 +       , m_pCur ( NULL )
2077 +       , m_bUnpackError ( false )
2078 +       , m_iFields ( 0 )
2079 +       , m_dFields ( NULL )
2080 +       , m_iAttrs ( 0 )
2081 +       , m_dAttrs ( NULL )
2082 +       , m_bId64 ( 0 )
2083 +       , m_dUnboundFields ( NULL )
2084 +{
2085 +       SPH_ENTER_METHOD();
2086 +       if ( current_thd )
2087 +               current_thd->variables.engine_condition_pushdown = true;
2088 +       SPH_VOID_RET();
2089 +}
2090 +
2091 +
2092 +// If frm_error() is called then we will use this to to find out what file extentions
2093 +// exist for the storage engine. This is also used by the default rename_table and
2094 +// delete_table method in handler.cc.
2095 +const char ** ha_sphinx::bas_ext() const
2096 +{
2097 +       return ha_sphinx_exts;
2098 +}
2099 +
2100 +
2101 +// Used for opening tables. The name will be the name of the file.
2102 +// A table is opened when it needs to be opened. For instance
2103 +// when a request comes in for a select on the table (tables are not
2104 +// open and closed for each request, they are cached).
2105 +//
2106 +// Called from handler.cc by handler::ha_open(). The server opens all tables by
2107 +// calling ha_open() which then calls the handler specific open().
2108 +int ha_sphinx::open ( const char * name, int, uint )
2109 +{
2110 +       SPH_ENTER_METHOD();
2111 +       m_pShare = get_share ( name, table );
2112 +       if ( !m_pShare )
2113 +               SPH_RET(1);
2114 +
2115 +       thr_lock_data_init ( &m_pShare->m_tLock, &m_tLock, NULL );
2116 +
2117 +       #if MYSQL_VERSION_ID>50100
2118 +       *thd_ha_data ( table->in_use, ht ) = NULL;
2119 +       #else
2120 +       table->in_use->ha_data [ sphinx_hton.slot ] = NULL;
2121 +       #endif
2122 +
2123 +       SPH_RET(0);
2124 +}
2125 +
2126 +
2127 +int ha_sphinx::Connect ( const char * sHost, ushort uPort )
2128 +{
2129 +       struct sockaddr_in sin;
2130 +#ifndef __WIN__
2131 +       struct sockaddr_un saun;
2132 +#endif
2133 +
2134 +       int iDomain = 0;
2135 +       int iSockaddrSize = 0;
2136 +       struct sockaddr * pSockaddr = NULL;
2137 +
2138 +       in_addr_t ip_addr;
2139 +
2140 +       if ( uPort )
2141 +       {
2142 +               iDomain = AF_INET;
2143 +               iSockaddrSize = sizeof(sin);
2144 +               pSockaddr = (struct sockaddr *) &sin;
2145 +
2146 +               memset ( &sin, 0, sizeof(sin) );
2147 +               sin.sin_family = AF_INET;
2148 +               sin.sin_port = htons(uPort);
2149 +
2150 +               // prepare host address
2151 +               if ( (int)( ip_addr = inet_addr(sHost) )!=(int)INADDR_NONE )
2152 +               {
2153 +                       memcpy ( &sin.sin_addr, &ip_addr, sizeof(ip_addr) );
2154 +               } else
2155 +               {
2156 +                       int tmp_errno;
2157 +                       bool bError = false;
2158 +
2159 +#if MYSQL_VERSION_ID>=50515
2160 +                       struct addrinfo tmp_hostent, *hp;
2161 +                       tmp_errno = getaddrinfo ( sHost, NULL, &tmp_hostent, &hp );
2162 +                       if ( !tmp_errno )
2163 +                       {
2164 +                               freeaddrinfo ( hp );
2165 +                               bError = true;
2166 +                       }
2167 +#else
2168 +                       struct hostent tmp_hostent, *hp;
2169 +                       char buff2 [ GETHOSTBYNAME_BUFF_SIZE ];
2170 +                       hp = my_gethostbyname_r ( sHost, &tmp_hostent, buff2, sizeof(buff2), &tmp_errno );
2171 +                       if ( !hp )
2172 +                       {
2173 +                               my_gethostbyname_r_free();
2174 +                               bError = true;
2175 +                       }
2176 +#endif
2177 +
2178 +                       if ( bError )
2179 +                       {
2180 +                               char sError[256];
2181 +                               my_snprintf ( sError, sizeof(sError), "failed to resolve searchd host (name=%s)", sHost );
2182 +
2183 +                               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
2184 +                               SPH_RET(-1);
2185 +                       }
2186 +
2187 +#if MYSQL_VERSION_ID>=50515
2188 +                       memcpy ( &sin.sin_addr, hp->ai_addr, Min ( sizeof(sin.sin_addr), (size_t)hp->ai_addrlen ) );
2189 +                       freeaddrinfo ( hp );
2190 +#else
2191 +                       memcpy ( &sin.sin_addr, hp->h_addr, Min ( sizeof(sin.sin_addr), (size_t)hp->h_length ) );
2192 +                       my_gethostbyname_r_free();
2193 +#endif
2194 +               }
2195 +       } else
2196 +       {
2197 +#ifndef __WIN__
2198 +               iDomain = AF_UNIX;
2199 +               iSockaddrSize = sizeof(saun);
2200 +               pSockaddr = (struct sockaddr *) &saun;
2201 +
2202 +               memset ( &saun, 0, sizeof(saun) );
2203 +               saun.sun_family = AF_UNIX;
2204 +               strncpy ( saun.sun_path, sHost, sizeof(saun.sun_path)-1 );
2205 +#else
2206 +               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), "UNIX sockets are not supported on Windows" );
2207 +               SPH_RET(-1);
2208 +#endif
2209 +       }
2210 +
2211 +       char sError[512];
2212 +       int iSocket = socket ( iDomain, SOCK_STREAM, 0 );
2213 +
2214 +       if ( iSocket<0 )
2215 +       {
2216 +               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), "failed to create client socket" );
2217 +               SPH_RET(-1);
2218 +       }
2219 +
2220 +       if ( connect ( iSocket, pSockaddr, iSockaddrSize )<0 )
2221 +       {
2222 +               sphSockClose ( iSocket );
2223 +               my_snprintf ( sError, sizeof(sError), "failed to connect to searchd (host=%s, errno=%d, port=%d)",
2224 +                       sHost, errno, (int)uPort );
2225 +               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
2226 +               SPH_RET(-1);
2227 +       }
2228 +
2229 +       return iSocket;
2230 +}
2231 +
2232 +
2233 +int ha_sphinx::ConnectAPI ( const char * sQueryHost, int iQueryPort )
2234 +{
2235 +       SPH_ENTER_METHOD();
2236 +
2237 +       const char * sHost = ( sQueryHost && *sQueryHost ) ? sQueryHost : m_pShare->m_sHost;
2238 +       ushort uPort = iQueryPort ? (ushort)iQueryPort : m_pShare->m_iPort;
2239 +
2240 +       int iSocket = Connect ( sHost, uPort );
2241 +       if ( iSocket<0 )
2242 +               SPH_RET ( iSocket );
2243 +
2244 +       char sError[512];
2245 +
2246 +       int version;
2247 +       if ( ::recv ( iSocket, (char *)&version, sizeof(version), 0 )!=sizeof(version) )
2248 +       {
2249 +               sphSockClose ( iSocket );
2250 +               my_snprintf ( sError, sizeof(sError), "failed to receive searchd version (host=%s, port=%d)",
2251 +                       sHost, (int)uPort );
2252 +               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
2253 +               SPH_RET(-1);
2254 +       }
2255 +
2256 +       uint uClientVersion = htonl ( SPHINX_SEARCHD_PROTO );
2257 +       if ( ::send ( iSocket, (char*)&uClientVersion, sizeof(uClientVersion), 0 )!=sizeof(uClientVersion) )
2258 +       {
2259 +               sphSockClose ( iSocket );
2260 +               my_snprintf ( sError, sizeof(sError), "failed to send client version (host=%s, port=%d)",
2261 +                       sHost, (int)uPort );
2262 +               my_error ( ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), sError );
2263 +               SPH_RET(-1);
2264 +       }
2265 +
2266 +       SPH_RET ( iSocket );
2267 +}
2268 +
2269 +
2270 +// Closes a table. We call the free_share() function to free any resources
2271 +// that we have allocated in the "shared" structure.
2272 +//
2273 +// Called from sql_base.cc, sql_select.cc, and table.cc.
2274 +// In sql_select.cc it is only used to close up temporary tables or during
2275 +// the process where a temporary table is converted over to being a
2276 +// myisam table.
2277 +// For sql_base.cc look at close_data_tables().
2278 +int ha_sphinx::close()
2279 +{
2280 +       SPH_ENTER_METHOD();
2281 +       SPH_RET ( free_share ( m_pShare ) );
2282 +}
2283 +
2284 +
2285 +int ha_sphinx::HandleMysqlError ( MYSQL * pConn, int iErrCode )
2286 +{
2287 +       CSphSEThreadData * pTls = GetTls ();
2288 +       if ( pTls )
2289 +       {
2290 +               strncpy ( pTls->m_tStats.m_sLastMessage, mysql_error ( pConn ), sizeof ( pTls->m_tStats.m_sLastMessage ) );
2291 +               pTls->m_tStats.m_bLastError = true;
2292 +       }
2293 +
2294 +       mysql_close ( pConn );
2295 +
2296 +       my_error ( iErrCode, MYF(0), pTls->m_tStats.m_sLastMessage );
2297 +       return -1;
2298 +}
2299 +
2300 +
2301 +int ha_sphinx::extra ( enum ha_extra_function op )
2302 +{
2303 +       CSphSEThreadData * pTls = GetTls();
2304 +       if ( pTls )
2305 +       {
2306 +               if ( op==HA_EXTRA_WRITE_CAN_REPLACE )
2307 +                       pTls->m_bReplace = true;
2308 +               else if ( op==HA_EXTRA_WRITE_CANNOT_REPLACE )
2309 +                       pTls->m_bReplace = false;
2310 +       }
2311 +       return 0;
2312 +}
2313 +
2314 +
2315 +int ha_sphinx::write_row ( byte * )
2316 +{
2317 +       SPH_ENTER_METHOD();
2318 +       if ( !m_pShare || !m_pShare->m_bSphinxQL )
2319 +               SPH_RET ( HA_ERR_WRONG_COMMAND );
2320 +
2321 +       // SphinxQL inserts only, pretty much similar to abandoned federated
2322 +       char sQueryBuf[1024];
2323 +       char sValueBuf[1024];
2324 +
2325 +       String sQuery ( sQueryBuf, sizeof(sQueryBuf), &my_charset_bin );
2326 +       String sValue ( sValueBuf, sizeof(sQueryBuf), &my_charset_bin );
2327 +       sQuery.length ( 0 );
2328 +       sValue.length ( 0 );
2329 +
2330 +       CSphSEThreadData * pTls = GetTls ();
2331 +       sQuery.append ( pTls && pTls->m_bReplace ? "REPLACE INTO " : "INSERT INTO " );
2332 +       sQuery.append ( m_pShare->m_sIndex );
2333 +       sQuery.append ( " (" );
2334 +
2335 +       for ( Field ** ppField = table->field; *ppField; ppField++ )
2336 +       {
2337 +               sQuery.append ( (*ppField)->field_name );
2338 +               if ( ppField[1] )
2339 +                       sQuery.append ( ", " );
2340 +       }
2341 +       sQuery.append ( ") VALUES (" );
2342 +
2343 +       for ( Field ** ppField = table->field; *ppField; ppField++ )
2344 +       {
2345 +               if ( (*ppField)->is_null() )
2346 +               {
2347 +                       sQuery.append ( "''" );
2348 +
2349 +               } else
2350 +               {
2351 +                       if ( (*ppField)->type()==MYSQL_TYPE_TIMESTAMP )
2352 +                       {
2353 +                               Item_field * pWrap = new Item_field ( *ppField ); // autofreed by query arena, I assume
2354 +                               Item_func_unix_timestamp * pConv = new Item_func_unix_timestamp ( pWrap );
2355 +                               pConv->quick_fix_field();
2356 +                               unsigned int uTs = (unsigned int) pConv->val_int();
2357 +
2358 +                               snprintf ( sValueBuf, sizeof(sValueBuf), "'%u'", uTs );
2359 +                               sQuery.append ( sValueBuf );
2360 +
2361 +                       } else
2362 +                       {
2363 +                               (*ppField)->val_str ( &sValue );
2364 +                               sQuery.append ( "'" );
2365 +                               sValue.print ( &sQuery );
2366 +                               sQuery.append ( "'" );
2367 +                               sValue.length(0);
2368 +                       }
2369 +               }
2370 +
2371 +               if ( ppField[1] )
2372 +                       sQuery.append ( ", " );
2373 +       }
2374 +       sQuery.append ( ")" );
2375 +
2376 +       // FIXME? pretty inefficient to reconnect every time under high load,
2377 +       // but this was intentionally written for a low load scenario..
2378 +       MYSQL * pConn = mysql_init ( NULL );
2379 +       if ( !pConn )
2380 +               SPH_RET ( ER_OUT_OF_RESOURCES );
2381 +
2382 +       unsigned int uTimeout = 1;
2383 +       mysql_options ( pConn, MYSQL_OPT_CONNECT_TIMEOUT, (const char*)&uTimeout );
2384 +
2385 +       if ( !mysql_real_connect ( pConn, m_pShare->m_sHost, "root", "", "", m_pShare->m_iPort, m_pShare->m_sSocket, 0 ) )
2386 +               SPH_RET ( HandleMysqlError ( pConn, ER_CONNECT_TO_FOREIGN_DATA_SOURCE ) );
2387 +
2388 +       if ( mysql_real_query ( pConn, sQuery.ptr(), sQuery.length() ) )
2389 +               SPH_RET ( HandleMysqlError ( pConn, ER_QUERY_ON_FOREIGN_DATA_SOURCE ) );
2390 +
2391 +       // all ok!
2392 +       mysql_close ( pConn );
2393 +       SPH_RET(0);
2394 +}
2395 +
2396 +
2397 +static inline bool IsIntegerFieldType ( enum_field_types eType )
2398 +{
2399 +       return eType==MYSQL_TYPE_LONG || eType==MYSQL_TYPE_LONGLONG;
2400 +}
2401 +
2402 +
2403 +static inline bool IsIDField ( Field * pField )
2404 +{
2405 +       enum_field_types eType = pField->type();
2406 +
2407 +       if ( eType==MYSQL_TYPE_LONGLONG )
2408 +               return true;
2409 +
2410 +       if ( eType==MYSQL_TYPE_LONG && ((Field_num*)pField)->unsigned_flag )
2411 +               return true;
2412 +
2413 +       return false;
2414 +}
2415 +
2416 +
2417 +int ha_sphinx::delete_row ( const byte * )
2418 +{
2419 +       SPH_ENTER_METHOD();
2420 +       if ( !m_pShare || !m_pShare->m_bSphinxQL )
2421 +               SPH_RET ( HA_ERR_WRONG_COMMAND );
2422 +
2423 +       char sQueryBuf[1024];
2424 +       String sQuery ( sQueryBuf, sizeof(sQueryBuf), &my_charset_bin );
2425 +       sQuery.length ( 0 );
2426 +
2427 +       sQuery.append ( "DELETE FROM " );
2428 +       sQuery.append ( m_pShare->m_sIndex );
2429 +       sQuery.append ( " WHERE id=" );
2430 +
2431 +       char sValue[32];
2432 +       snprintf ( sValue, sizeof(sValue), "%lld", table->field[0]->val_int() );
2433 +       sQuery.append ( sValue );
2434 +
2435 +       // FIXME? pretty inefficient to reconnect every time under high load,
2436 +       // but this was intentionally written for a low load scenario..
2437 +       MYSQL * pConn = mysql_init ( NULL );
2438 +       if ( !pConn )
2439 +               SPH_RET ( ER_OUT_OF_RESOURCES );
2440 +
2441 +       unsigned int uTimeout = 1;
2442 +       mysql_options ( pConn, MYSQL_OPT_CONNECT_TIMEOUT, (const char*)&uTimeout );
2443 +
2444 +       if ( !mysql_real_connect ( pConn, m_pShare->m_sHost, "root", "", "", m_pShare->m_iPort, m_pShare->m_sSocket, 0 ) )
2445 +               SPH_RET ( HandleMysqlError ( pConn, ER_CONNECT_TO_FOREIGN_DATA_SOURCE ) );
2446 +
2447 +       if ( mysql_real_query ( pConn, sQuery.ptr(), sQuery.length() ) )
2448 +               SPH_RET ( HandleMysqlError ( pConn, ER_QUERY_ON_FOREIGN_DATA_SOURCE ) );
2449 +
2450 +       // all ok!
2451 +       mysql_close ( pConn );
2452 +       SPH_RET(0);
2453 +}
2454 +
2455 +
2456 +int ha_sphinx::update_row ( const byte *, byte * )
2457 +{
2458 +       SPH_ENTER_METHOD();
2459 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
2460 +}
2461 +
2462 +
2463 +// keynr is key (index) number
2464 +// sorted is 1 if result MUST be sorted according to index
2465 +int ha_sphinx::index_init ( uint keynr, bool )
2466 +{
2467 +       SPH_ENTER_METHOD();
2468 +       active_index = keynr;
2469 +
2470 +       CSphSEThreadData * pTls = GetTls();
2471 +       if ( pTls )
2472 +               pTls->m_bCondDone = false;
2473 +
2474 +       SPH_RET(0);
2475 +}
2476 +
2477 +
2478 +int ha_sphinx::index_end()
2479 +{
2480 +       SPH_ENTER_METHOD();
2481 +       SPH_RET(0);
2482 +}
2483 +
2484 +
2485 +uint32 ha_sphinx::UnpackDword ()
2486 +{
2487 +       if ( m_pCur+sizeof(uint32)>m_pResponseEnd ) // NOLINT
2488 +       {
2489 +               m_pCur = m_pResponseEnd;
2490 +               m_bUnpackError = true;
2491 +               return 0;
2492 +       }
2493 +
2494 +       uint32 uRes = ntohl ( sphUnalignedRead ( *(uint32*)m_pCur ) );
2495 +       m_pCur += sizeof(uint32); // NOLINT
2496 +       return uRes;
2497 +}
2498 +
2499 +
2500 +char * ha_sphinx::UnpackString ()
2501 +{
2502 +       uint32 iLen = UnpackDword ();
2503 +       if ( !iLen )
2504 +               return NULL;
2505 +
2506 +       if ( m_pCur+iLen>m_pResponseEnd )
2507 +       {
2508 +               m_pCur = m_pResponseEnd;
2509 +               m_bUnpackError = true;
2510 +               return NULL;
2511 +       }
2512 +
2513 +       char * sRes = new char [ 1+iLen ];
2514 +       memcpy ( sRes, m_pCur, iLen );
2515 +       sRes[iLen] = '\0';
2516 +       m_pCur += iLen;
2517 +       return sRes;
2518 +}
2519 +
2520 +
2521 +static inline const char * FixNull ( const char * s )
2522 +{
2523 +       return s ? s : "(null)";
2524 +}
2525 +
2526 +
2527 +bool ha_sphinx::UnpackSchema ()
2528 +{
2529 +       SPH_ENTER_METHOD();
2530 +
2531 +       // cleanup
2532 +       if ( m_dFields )
2533 +               for ( int i=0; i<(int)m_iFields; i++ )
2534 +                       SafeDeleteArray ( m_dFields[i] );
2535 +       SafeDeleteArray ( m_dFields );
2536 +
2537 +       // unpack network packet
2538 +       uint32 uStatus = UnpackDword ();
2539 +       char * sMessage = NULL;
2540 +
2541 +       if ( uStatus!=SEARCHD_OK )
2542 +       {
2543 +               sMessage = UnpackString ();
2544 +               CSphSEThreadData * pTls = GetTls ();
2545 +               if ( pTls )
2546 +               {
2547 +                       strncpy ( pTls->m_tStats.m_sLastMessage, sMessage, sizeof(pTls->m_tStats.m_sLastMessage) );
2548 +                       pTls->m_tStats.m_bLastError = ( uStatus==SEARCHD_ERROR );
2549 +               }
2550 +
2551 +               if ( uStatus==SEARCHD_ERROR )
2552 +               {
2553 +                       char sError[1024];
2554 +                       my_snprintf ( sError, sizeof(sError), "searchd error: %s", sMessage );
2555 +                       my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sError );
2556 +                       SafeDeleteArray ( sMessage );
2557 +                       SPH_RET ( false );
2558 +               }
2559 +       }
2560 +
2561 +       m_iFields = UnpackDword ();
2562 +       m_dFields = new char * [ m_iFields ];
2563 +       if ( !m_dFields )
2564 +       {
2565 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: UnpackSchema() failed (fields alloc error)" );
2566 +               SPH_RET(false);
2567 +       }
2568 +
2569 +       for ( uint32 i=0; i<m_iFields; i++ )
2570 +               m_dFields[i] = UnpackString ();
2571 +
2572 +       SafeDeleteArray ( m_dAttrs );
2573 +       m_iAttrs = UnpackDword ();
2574 +       m_dAttrs = new CSphSEAttr [ m_iAttrs ];
2575 +       if ( !m_dAttrs )
2576 +       {
2577 +               for ( int i=0; i<(int)m_iFields; i++ )
2578 +                       SafeDeleteArray ( m_dFields[i] );
2579 +               SafeDeleteArray ( m_dFields );
2580 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: UnpackSchema() failed (attrs alloc error)" );
2581 +               SPH_RET(false);
2582 +       }
2583 +
2584 +       for ( uint32 i=0; i<m_iAttrs; i++ )
2585 +       {
2586 +               m_dAttrs[i].m_sName = UnpackString ();
2587 +               m_dAttrs[i].m_uType = UnpackDword ();
2588 +               if ( m_bUnpackError ) // m_sName may be null
2589 +                       break;
2590 +
2591 +               m_dAttrs[i].m_iField = -1;
2592 +               for ( int j=SPHINXSE_SYSTEM_COLUMNS; j<m_pShare->m_iTableFields; j++ )
2593 +               {
2594 +                       const char * sTableField = m_pShare->m_sTableField[j];
2595 +                       const char * sAttrField = m_dAttrs[i].m_sName;
2596 +                       if ( m_dAttrs[i].m_sName[0]=='@' )
2597 +                       {
2598 +                               const char * sAtPrefix = "_sph_";
2599 +                               if ( strncmp ( sTableField, sAtPrefix, strlen(sAtPrefix) ) )
2600 +                                       continue;
2601 +                               sTableField += strlen(sAtPrefix);
2602 +                               sAttrField++;
2603 +                       }
2604 +
2605 +                       if ( !strcasecmp ( sAttrField, sTableField ) )
2606 +                       {
2607 +                               // we're almost good, but
2608 +                               // let's enforce that timestamp columns can only receive timestamp attributes
2609 +                               if ( m_pShare->m_eTableFieldType[j]!=MYSQL_TYPE_TIMESTAMP || m_dAttrs[i].m_uType==SPH_ATTR_TIMESTAMP )
2610 +                                       m_dAttrs[i].m_iField = j;
2611 +                               break;
2612 +                       }
2613 +               }
2614 +       }
2615 +
2616 +       m_iMatchesTotal = UnpackDword ();
2617 +
2618 +       m_bId64 = UnpackDword ();
2619 +       if ( m_bId64 && m_pShare->m_eTableFieldType[0]!=MYSQL_TYPE_LONGLONG )
2620 +       {
2621 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: 1st column must be bigint to accept 64-bit DOCIDs" );
2622 +               SPH_RET(false);
2623 +       }
2624 +
2625 +       // network packet unpacked; build unbound fields map
2626 +       SafeDeleteArray ( m_dUnboundFields );
2627 +       m_dUnboundFields = new int [ m_pShare->m_iTableFields ];
2628 +
2629 +       for ( int i=0; i<m_pShare->m_iTableFields; i++ )
2630 +       {
2631 +               if ( i<SPHINXSE_SYSTEM_COLUMNS )
2632 +                       m_dUnboundFields[i] = SPH_ATTR_NONE;
2633 +
2634 +               else if ( m_pShare->m_eTableFieldType[i]==MYSQL_TYPE_TIMESTAMP )
2635 +                       m_dUnboundFields[i] = SPH_ATTR_TIMESTAMP;
2636 +
2637 +               else
2638 +                       m_dUnboundFields[i] = SPH_ATTR_INTEGER;
2639 +       }
2640 +
2641 +       for ( uint32 i=0; i<m_iAttrs; i++ )
2642 +               if ( m_dAttrs[i].m_iField>=0 )
2643 +                       m_dUnboundFields [ m_dAttrs[i].m_iField ] = SPH_ATTR_NONE;
2644 +
2645 +       if ( m_bUnpackError )
2646 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: UnpackSchema() failed (unpack error)" );
2647 +
2648 +       SPH_RET ( !m_bUnpackError );
2649 +}
2650 +
2651 +
2652 +bool ha_sphinx::UnpackStats ( CSphSEStats * pStats )
2653 +{
2654 +       assert ( pStats );
2655 +
2656 +       char * pCurSave = m_pCur;
2657 +       for ( uint i=0; i<m_iMatchesTotal && m_pCur<m_pResponseEnd-sizeof(uint32); i++ ) // NOLINT
2658 +       {
2659 +               m_pCur += m_bId64 ? 12 : 8; // skip id+weight
2660 +               for ( uint32 i=0; i<m_iAttrs && m_pCur<m_pResponseEnd-sizeof(uint32); i++ ) // NOLINT
2661 +               {
2662 +                       if ( m_dAttrs[i].m_uType & SPH_ATTR_MULTI )
2663 +                       {
2664 +                               // skip MVA list
2665 +                               uint32 uCount = UnpackDword ();
2666 +                               m_pCur += uCount*4;
2667 +                       } else // skip normal value
2668 +                               m_pCur += m_dAttrs[i].m_uType==SPH_ATTR_BIGINT ? 8 : 4;
2669 +               }
2670 +       }
2671 +
2672 +       pStats->m_iMatchesTotal = UnpackDword ();
2673 +       pStats->m_iMatchesFound = UnpackDword ();
2674 +       pStats->m_iQueryMsec = UnpackDword ();
2675 +       pStats->m_iWords = UnpackDword ();
2676 +
2677 +       if ( m_bUnpackError )
2678 +               return false;
2679 +
2680 +       SafeDeleteArray ( pStats->m_dWords );
2681 +       if ( pStats->m_iWords<0 || pStats->m_iWords>=SPHINXSE_MAX_KEYWORDSTATS )
2682 +               return false;
2683 +       pStats->m_dWords = new CSphSEWordStats [ pStats->m_iWords ];
2684 +       if ( !pStats->m_dWords )
2685 +               return false;
2686 +
2687 +       for ( int i=0; i<pStats->m_iWords; i++ )
2688 +       {
2689 +               CSphSEWordStats & tWord = pStats->m_dWords[i];
2690 +               tWord.m_sWord = UnpackString ();
2691 +               tWord.m_iDocs = UnpackDword ();
2692 +               tWord.m_iHits = UnpackDword ();
2693 +       }
2694 +
2695 +       if ( m_bUnpackError )
2696 +               return false;
2697 +
2698 +       m_pCur = pCurSave;
2699 +       return true;
2700 +}
2701 +
2702 +
2703 +/// condition pushdown implementation, to properly intercept WHERE clauses on my columns
2704 +const COND * ha_sphinx::cond_push ( const COND * cond )
2705 +{
2706 +       // catch the simplest case: query_column="some text"
2707 +       for ( ;; )
2708 +       {
2709 +               if ( cond->type()!=COND::FUNC_ITEM )
2710 +                       break;
2711 +
2712 +               Item_func * condf = (Item_func *)cond;
2713 +               if ( condf->functype()!=Item_func::EQ_FUNC || condf->argument_count()!=2 )
2714 +                       break;
2715 +
2716 +               // get my tls
2717 +               CSphSEThreadData * pTls = GetTls ();
2718 +               if ( !pTls )
2719 +                       break;
2720 +
2721 +               Item ** args = condf->arguments();
2722 +               if ( !m_pShare->m_bSphinxQL )
2723 +               {
2724 +                       // on non-QL tables, intercept query=value condition for SELECT
2725 +                       if (!( args[0]->type()==COND::FIELD_ITEM && args[1]->type()==COND::STRING_ITEM ))
2726 +                               break;
2727 +
2728 +                       Item_field * pField = (Item_field *) args[0];
2729 +                       if ( pField->field->field_index!=2 ) // FIXME! magic key index
2730 +                               break;
2731 +
2732 +                       // copy the query, and let know that we intercepted this condition
2733 +                       Item_string * pString = (Item_string *) args[1];
2734 +                       pTls->m_bQuery = true;
2735 +                       strncpy ( pTls->m_sQuery, pString->str_value.c_ptr(), sizeof(pTls->m_sQuery) );
2736 +                       pTls->m_sQuery[sizeof(pTls->m_sQuery)-1] = '\0';
2737 +                       pTls->m_pQueryCharset = pString->str_value.charset();
2738 +
2739 +               } else
2740 +               {
2741 +                       if (!( args[0]->type()==COND::FIELD_ITEM && args[1]->type()==COND::INT_ITEM ))
2742 +                               break;
2743 +
2744 +                       // on QL tables, intercept id=value condition for DELETE
2745 +                       Item_field * pField = (Item_field *) args[0];
2746 +                       if ( pField->field->field_index!=0 ) // FIXME! magic key index
2747 +                               break;
2748 +
2749 +                       Item_int * pVal = (Item_int *) args[1];
2750 +                       pTls->m_iCondId = pVal->val_int();
2751 +                       pTls->m_bCondId = true;
2752 +               }
2753 +
2754 +               // we intercepted this condition
2755 +               return NULL;
2756 +       }
2757 +
2758 +       // don't change anything
2759 +       return cond;
2760 +}
2761 +
2762 +
2763 +/// condition popup
2764 +void ha_sphinx::cond_pop ()
2765 +{
2766 +       CSphSEThreadData * pTls = GetTls ();
2767 +       if ( pTls )
2768 +               pTls->m_bQuery = false;
2769 +}
2770 +
2771 +
2772 +/// get TLS (maybe allocate it, too)
2773 +CSphSEThreadData * ha_sphinx::GetTls()
2774 +{
2775 +       // where do we store that pointer in today's version?
2776 +       CSphSEThreadData ** ppTls;
2777 +#if MYSQL_VERSION_ID>50100
2778 +       ppTls = (CSphSEThreadData**) thd_ha_data ( table->in_use, ht );
2779 +#else
2780 +       ppTls = (CSphSEThreadData**) &current_thd->ha_data[sphinx_hton.slot];
2781 +#endif // >50100
2782 +
2783 +       // allocate if needed
2784 +       if ( !*ppTls )
2785 +               *ppTls = new CSphSEThreadData ();
2786 +
2787 +       // errors will be handled by caller
2788 +       return *ppTls;
2789 +}
2790 +
2791 +
2792 +// Positions an index cursor to the index specified in the handle. Fetches the
2793 +// row if available. If the key value is null, begin at the first key of the
2794 +// index.
2795 +int ha_sphinx::index_read ( byte * buf, const byte * key, uint key_len, enum ha_rkey_function )
2796 +{
2797 +       SPH_ENTER_METHOD();
2798 +       char sError[256];
2799 +
2800 +       // set new data for thd->ha_data, it is used in show_status
2801 +       CSphSEThreadData * pTls = GetTls();
2802 +       if ( !pTls )
2803 +       {
2804 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: TLS malloc() failed" );
2805 +               SPH_RET ( HA_ERR_END_OF_FILE );
2806 +       }
2807 +       pTls->m_tStats.Reset ();
2808 +
2809 +       // sphinxql table, just return the key once
2810 +       if ( m_pShare->m_bSphinxQL )
2811 +       {
2812 +               // over and out
2813 +               if ( pTls->m_bCondDone )
2814 +                       SPH_RET ( HA_ERR_END_OF_FILE );
2815 +
2816 +               // return a value from pushdown, if any
2817 +               if ( pTls->m_bCondId )
2818 +               {
2819 +                       table->field[0]->store ( pTls->m_iCondId, 1 );
2820 +                       pTls->m_bCondDone = true;
2821 +                       SPH_RET(0);
2822 +               }
2823 +
2824 +               // return a value from key
2825 +               longlong iRef = 0;
2826 +               if ( key_len==4 )
2827 +                       iRef = uint4korr ( key );
2828 +               else if ( key_len==8 )
2829 +                       iRef = uint8korr ( key );
2830 +               else
2831 +               {
2832 +                       my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: unexpected key length" );
2833 +                       SPH_RET ( HA_ERR_END_OF_FILE );
2834 +               }
2835 +
2836 +               table->field[0]->store ( iRef, 1 );
2837 +               pTls->m_bCondDone = true;
2838 +               SPH_RET(0);
2839 +       }
2840 +
2841 +       // parse query
2842 +       if ( pTls->m_bQuery )
2843 +       {
2844 +               // we have a query from condition pushdown
2845 +               m_pCurrentKey = (const byte *) pTls->m_sQuery;
2846 +               m_iCurrentKeyLen = strlen(pTls->m_sQuery);
2847 +       } else
2848 +       {
2849 +               // just use the key (might be truncated)
2850 +               m_pCurrentKey = key+HA_KEY_BLOB_LENGTH;
2851 +               m_iCurrentKeyLen = uint2korr(key); // or maybe key_len?
2852 +               pTls->m_pQueryCharset = m_pShare ? m_pShare->m_pTableQueryCharset : NULL;
2853 +       }
2854 +
2855 +       CSphSEQuery q ( (const char*)m_pCurrentKey, m_iCurrentKeyLen, m_pShare->m_sIndex );
2856 +       if ( !q.Parse () )
2857 +       {
2858 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), q.m_sParseError );
2859 +               SPH_RET ( HA_ERR_END_OF_FILE );
2860 +       }
2861 +
2862 +       // do connect
2863 +       int iSocket = ConnectAPI ( q.m_sHost, q.m_iPort );
2864 +       if ( iSocket<0 )
2865 +               SPH_RET ( HA_ERR_END_OF_FILE );
2866 +
2867 +       // my buffer
2868 +       char * pBuffer; // will be free by CSphSEQuery dtor; do NOT free manually
2869 +       int iReqLen = q.BuildRequest ( &pBuffer );
2870 +
2871 +       if ( iReqLen<=0 )
2872 +       {
2873 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: q.BuildRequest() failed" );
2874 +               SPH_RET ( HA_ERR_END_OF_FILE );
2875 +       }
2876 +
2877 +       // send request
2878 +       ::send ( iSocket, pBuffer, iReqLen, 0 );
2879 +
2880 +       // receive reply
2881 +       char sHeader[8];
2882 +       int iGot = ::recv ( iSocket, sHeader, sizeof(sHeader), RECV_FLAGS );
2883 +       if ( iGot!=sizeof(sHeader) )
2884 +       {
2885 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "failed to receive response header (searchd went away?)" );
2886 +               SPH_RET ( HA_ERR_END_OF_FILE );
2887 +       }
2888 +
2889 +       short int uRespStatus = ntohs ( sphUnalignedRead ( *(short int*)( &sHeader[0] ) ) );
2890 +       short int uRespVersion = ntohs ( sphUnalignedRead ( *(short int*)( &sHeader[2] ) ) );
2891 +       uint uRespLength = ntohl ( sphUnalignedRead ( *(uint *)( &sHeader[4] ) ) );
2892 +       SPH_DEBUG ( "got response header (status=%d version=%d length=%d)",
2893 +               uRespStatus, uRespVersion, uRespLength );
2894 +
2895 +       SafeDeleteArray ( m_pResponse );
2896 +       if ( uRespLength<=SPHINXSE_MAX_ALLOC )
2897 +               m_pResponse = new char [ uRespLength+1 ];
2898 +
2899 +       if ( !m_pResponse )
2900 +       {
2901 +               my_snprintf ( sError, sizeof(sError), "bad searchd response length (length=%u)", uRespLength );
2902 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sError );
2903 +               SPH_RET ( HA_ERR_END_OF_FILE );
2904 +       }
2905 +
2906 +       int iRecvLength = 0;
2907 +       while ( iRecvLength<(int)uRespLength )
2908 +       {
2909 +               int iRecv = ::recv ( iSocket, m_pResponse+iRecvLength, uRespLength-iRecvLength, RECV_FLAGS );
2910 +               if ( iRecv<0 )
2911 +                       break;
2912 +               iRecvLength += iRecv;
2913 +       }
2914 +
2915 +       ::closesocket ( iSocket );
2916 +       iSocket = -1;
2917 +
2918 +       if ( iRecvLength!=(int)uRespLength )
2919 +       {
2920 +               my_snprintf ( sError, sizeof(sError), "net read error (expected=%d, got=%d)", uRespLength, iRecvLength );
2921 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sError );
2922 +               SPH_RET ( HA_ERR_END_OF_FILE );
2923 +       }
2924 +
2925 +       // we'll have a message, at least
2926 +       pTls->m_bStats = true;
2927 +
2928 +       // parse reply
2929 +       m_iCurrentPos = 0;
2930 +       m_pCur = m_pResponse;
2931 +       m_pResponseEnd = m_pResponse + uRespLength;
2932 +       m_bUnpackError = false;
2933 +
2934 +       if ( uRespStatus!=SEARCHD_OK )
2935 +       {
2936 +               char * sMessage = UnpackString ();
2937 +               if ( !sMessage )
2938 +               {
2939 +                       my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "no valid response from searchd (status=%d, resplen=%d)",
2940 +                               uRespStatus, uRespLength );
2941 +                       SPH_RET ( HA_ERR_END_OF_FILE );
2942 +               }
2943 +
2944 +               strncpy ( pTls->m_tStats.m_sLastMessage, sMessage, sizeof(pTls->m_tStats.m_sLastMessage) );
2945 +               SafeDeleteArray ( sMessage );
2946 +
2947 +               if ( uRespStatus!=SEARCHD_WARNING )
2948 +               {
2949 +                       my_snprintf ( sError, sizeof(sError), "searchd error: %s", pTls->m_tStats.m_sLastMessage );
2950 +                       my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), sError );
2951 +
2952 +                       pTls->m_tStats.m_bLastError = true;
2953 +                       SPH_RET ( HA_ERR_END_OF_FILE );
2954 +               }
2955 +       }
2956 +
2957 +       if ( !UnpackSchema () )
2958 +               SPH_RET ( HA_ERR_END_OF_FILE );
2959 +
2960 +       if ( !UnpackStats ( &pTls->m_tStats ) )
2961 +       {
2962 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: UnpackStats() failed" );
2963 +               SPH_RET ( HA_ERR_END_OF_FILE );
2964 +       }
2965 +
2966 +       SPH_RET ( get_rec ( buf, key, key_len ) );
2967 +}
2968 +
2969 +
2970 +// Positions an index cursor to the index specified in key. Fetches the
2971 +// row if any. This is only used to read whole keys.
2972 +int ha_sphinx::index_read_idx ( byte *, uint, const byte *, uint, enum ha_rkey_function )
2973 +{
2974 +       SPH_ENTER_METHOD();
2975 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
2976 +}
2977 +
2978 +
2979 +// Used to read forward through the index.
2980 +int ha_sphinx::index_next ( byte * buf )
2981 +{
2982 +       SPH_ENTER_METHOD();
2983 +       SPH_RET ( get_rec ( buf, m_pCurrentKey, m_iCurrentKeyLen ) );
2984 +}
2985 +
2986 +
2987 +int ha_sphinx::index_next_same ( byte * buf, const byte * key, uint keylen )
2988 +{
2989 +       SPH_ENTER_METHOD();
2990 +       SPH_RET ( get_rec ( buf, key, keylen ) );
2991 +}
2992 +
2993 +
2994 +int ha_sphinx::get_rec ( byte * buf, const byte *, uint )
2995 +{
2996 +       SPH_ENTER_METHOD();
2997 +
2998 +       if ( m_iCurrentPos>=m_iMatchesTotal )
2999 +       {
3000 +               SafeDeleteArray ( m_pResponse );
3001 +               SPH_RET ( HA_ERR_END_OF_FILE );
3002 +       }
3003 +
3004 +       #if MYSQL_VERSION_ID>50100
3005 +       my_bitmap_map * org_bitmap = dbug_tmp_use_all_columns ( table, table->write_set );
3006 +       #endif
3007 +       Field ** field = table->field;
3008 +
3009 +       // unpack and return the match
3010 +       longlong uMatchID = UnpackDword ();
3011 +       if ( m_bId64 )
3012 +               uMatchID = ( uMatchID<<32 ) + UnpackDword();
3013 +       uint32 uMatchWeight = UnpackDword ();
3014 +
3015 +       field[0]->store ( uMatchID, 1 );
3016 +       field[1]->store ( uMatchWeight, 1 );
3017 +       field[2]->store ( (const char*)m_pCurrentKey, m_iCurrentKeyLen, &my_charset_bin );
3018 +
3019 +       for ( uint32 i=0; i<m_iAttrs; i++ )
3020 +       {
3021 +               longlong iValue64 = 0;
3022 +               uint32 uValue = UnpackDword ();
3023 +               if ( m_dAttrs[i].m_uType==SPH_ATTR_BIGINT )
3024 +                       iValue64 = ( (longlong)uValue<<32 ) | UnpackDword();
3025 +               if ( m_dAttrs[i].m_iField<0 )
3026 +               {
3027 +                       // skip MVA
3028 +                       if ( m_dAttrs[i].m_uType & SPH_ATTR_MULTI )
3029 +                               for ( ; uValue>0 && !m_bUnpackError; uValue-- )
3030 +                                       UnpackDword();
3031 +                       continue;
3032 +               }
3033 +
3034 +               Field * af = field [ m_dAttrs[i].m_iField ];
3035 +               switch ( m_dAttrs[i].m_uType )
3036 +               {
3037 +                       case SPH_ATTR_INTEGER:
3038 +                       case SPH_ATTR_ORDINAL:
3039 +                       case SPH_ATTR_BOOL:
3040 +                               af->store ( uValue, 1 );
3041 +                               break;
3042 +
3043 +                       case SPH_ATTR_FLOAT:
3044 +                               af->store ( sphDW2F(uValue) );
3045 +                               break;
3046 +
3047 +                       case SPH_ATTR_TIMESTAMP:
3048 +                               if ( af->type()==MYSQL_TYPE_TIMESTAMP )
3049 +                                       longstore ( af->ptr, uValue ); // because store() does not accept timestamps
3050 +                               else
3051 +                                       af->store ( uValue, 1 );
3052 +                               break;
3053 +
3054 +                       case SPH_ATTR_BIGINT:
3055 +                               af->store ( iValue64, 0 );
3056 +                               break;
3057 +
3058 +                       case ( SPH_ATTR_MULTI | SPH_ATTR_INTEGER ):
3059 +                               if ( uValue<=0 )
3060 +                               {
3061 +                                       // shortcut, empty MVA set
3062 +                                       af->store ( "", 0, &my_charset_bin );
3063 +
3064 +                               } else
3065 +                               {
3066 +                                       // convert MVA set to comma-separated string
3067 +                                       char sBuf[1024]; // FIXME! magic size
3068 +                                       char * pCur = sBuf;
3069 +
3070 +                                       for ( ; uValue>0 && !m_bUnpackError; uValue-- )
3071 +                                       {
3072 +                                               uint32 uEntry = UnpackDword ();
3073 +                                               if ( pCur < sBuf+sizeof(sBuf)-16 ) // 10 chars per 32bit value plus some safety bytes
3074 +                                               {
3075 +                                                       snprintf ( pCur, sBuf+sizeof(sBuf)-pCur, "%u", uEntry );
3076 +                                                       while ( *pCur ) *pCur++;
3077 +                                                       if ( uValue>1 )
3078 +                                                               *pCur++ = ','; // non-trailing commas
3079 +                                               }
3080 +                                       }
3081 +
3082 +                                       af->store ( sBuf, pCur-sBuf, &my_charset_bin );
3083 +                               }
3084 +                               break;
3085 +
3086 +                       default:
3087 +                               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: unhandled attr type" );
3088 +                               SafeDeleteArray ( m_pResponse );
3089 +                               SPH_RET ( HA_ERR_END_OF_FILE );
3090 +               }
3091 +       }
3092 +
3093 +       if ( m_bUnpackError )
3094 +       {
3095 +               my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0), "INTERNAL ERROR: response unpacker failed" );
3096 +               SafeDeleteArray ( m_pResponse );
3097 +               SPH_RET ( HA_ERR_END_OF_FILE );
3098 +       }
3099 +
3100 +       // zero out unmapped fields
3101 +       for ( int i=SPHINXSE_SYSTEM_COLUMNS; i<(int)table->s->fields; i++ )
3102 +               if ( m_dUnboundFields[i]!=SPH_ATTR_NONE )
3103 +                       switch ( m_dUnboundFields[i] )
3104 +       {
3105 +               case SPH_ATTR_INTEGER:          table->field[i]->store ( 0, 1 ); break;
3106 +               case SPH_ATTR_TIMESTAMP:        longstore ( table->field[i]->ptr, 0 ); break;
3107 +               default:
3108 +                       my_error ( ER_QUERY_ON_FOREIGN_DATA_SOURCE, MYF(0),
3109 +                               "INTERNAL ERROR: unhandled unbound field type %d", m_dUnboundFields[i] );
3110 +                       SafeDeleteArray ( m_pResponse );
3111 +                       SPH_RET ( HA_ERR_END_OF_FILE );
3112 +       }
3113 +
3114 +       memset ( buf, 0, table->s->null_bytes );
3115 +       m_iCurrentPos++;
3116 +
3117 +       #if MYSQL_VERSION_ID > 50100
3118 +       dbug_tmp_restore_column_map ( table->write_set, org_bitmap );
3119 +       #endif
3120 +
3121 +       SPH_RET(0);
3122 +}
3123 +
3124 +
3125 +// Used to read backwards through the index.
3126 +int ha_sphinx::index_prev ( byte * )
3127 +{
3128 +       SPH_ENTER_METHOD();
3129 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
3130 +}
3131 +
3132 +
3133 +// index_first() asks for the first key in the index.
3134 +//
3135 +// Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
3136 +// and sql_select.cc.
3137 +int ha_sphinx::index_first ( byte * )
3138 +{
3139 +       SPH_ENTER_METHOD();
3140 +       SPH_RET ( HA_ERR_END_OF_FILE );
3141 +}
3142 +
3143 +// index_last() asks for the last key in the index.
3144 +//
3145 +// Called from opt_range.cc, opt_sum.cc, sql_handler.cc,
3146 +// and sql_select.cc.
3147 +int ha_sphinx::index_last ( byte * )
3148 +{
3149 +       SPH_ENTER_METHOD();
3150 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
3151 +}
3152 +
3153 +
3154 +int ha_sphinx::rnd_init ( bool )
3155 +{
3156 +       SPH_ENTER_METHOD();
3157 +       SPH_RET(0);
3158 +}
3159 +
3160 +
3161 +int ha_sphinx::rnd_end()
3162 +{
3163 +       SPH_ENTER_METHOD();
3164 +       SPH_RET(0);
3165 +}
3166 +
3167 +
3168 +int ha_sphinx::rnd_next ( byte * )
3169 +{
3170 +       SPH_ENTER_METHOD();
3171 +       SPH_RET ( HA_ERR_END_OF_FILE );
3172 +}
3173 +
3174 +
3175 +void ha_sphinx::position ( const byte * )
3176 +{
3177 +       SPH_ENTER_METHOD();
3178 +       SPH_VOID_RET();
3179 +}
3180 +
3181 +
3182 +// This is like rnd_next, but you are given a position to use
3183 +// to determine the row. The position will be of the type that you stored in
3184 +// ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key
3185 +// or position you saved when position() was called.
3186 +// Called from filesort.cc records.cc sql_insert.cc sql_select.cc sql_update.cc.
3187 +int ha_sphinx::rnd_pos ( byte *, byte * )
3188 +{
3189 +       SPH_ENTER_METHOD();
3190 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
3191 +}
3192 +
3193 +
3194 +#if MYSQL_VERSION_ID>=50030
3195 +int ha_sphinx::info ( uint )
3196 +#else
3197 +void ha_sphinx::info ( uint )
3198 +#endif
3199 +{
3200 +       SPH_ENTER_METHOD();
3201 +
3202 +       if ( table->s->keys>0 )
3203 +               table->key_info[0].rec_per_key[0] = 1;
3204 +
3205 +       #if MYSQL_VERSION_ID>50100
3206 +       stats.records = 20;
3207 +       #else
3208 +       records = 20;
3209 +       #endif
3210 +
3211 +#if MYSQL_VERSION_ID>=50030
3212 +       SPH_RET(0);
3213 +#else
3214 +       SPH_VOID_RET();
3215 +#endif
3216 +}
3217 +
3218 +
3219 +int ha_sphinx::reset ()
3220 +{
3221 +       SPH_ENTER_METHOD();
3222 +       CSphSEThreadData * pTls = GetTls ();
3223 +       if ( pTls )
3224 +               pTls->m_bQuery = false;
3225 +       SPH_RET(0);
3226 +}
3227 +
3228 +
3229 +int ha_sphinx::delete_all_rows()
3230 +{
3231 +       SPH_ENTER_METHOD();
3232 +       SPH_RET ( HA_ERR_WRONG_COMMAND );
3233 +}
3234 +
3235 +
3236 +// First you should go read the section "locking functions for mysql" in
3237 +// lock.cc to understand this.
3238 +// This create a lock on the table. If you are implementing a storage engine
3239 +// that can handle transacations look at ha_berkely.cc to see how you will
3240 +// want to go about doing this. Otherwise you should consider calling flock()
3241 +// here.
3242 +//
3243 +// Called from lock.cc by lock_external() and unlock_external(). Also called
3244 +// from sql_table.cc by copy_data_between_tables().
3245 +int ha_sphinx::external_lock ( THD *, int )
3246 +{
3247 +       SPH_ENTER_METHOD();
3248 +       SPH_RET(0);
3249 +}
3250 +
3251 +
3252 +THR_LOCK_DATA ** ha_sphinx::store_lock ( THD *, THR_LOCK_DATA ** to,
3253 +       enum thr_lock_type lock_type )
3254 +{
3255 +       SPH_ENTER_METHOD();
3256 +
3257 +       if ( lock_type!=TL_IGNORE && m_tLock.type==TL_UNLOCK )
3258 +               m_tLock.type = lock_type;
3259 +
3260 +       *to++ = &m_tLock;
3261 +       SPH_RET(to);
3262 +}
3263 +
3264 +
3265 +int ha_sphinx::delete_table ( const char * )
3266 +{
3267 +       SPH_ENTER_METHOD();
3268 +       SPH_RET(0);
3269 +}
3270 +
3271 +
3272 +// Renames a table from one name to another from alter table call.
3273 +//
3274 +// If you do not implement this, the default rename_table() is called from
3275 +// handler.cc and it will delete all files with the file extentions returned
3276 +// by bas_ext().
3277 +//
3278 +// Called from sql_table.cc by mysql_rename_table().
3279 +int ha_sphinx::rename_table ( const char *, const char * )
3280 +{
3281 +       SPH_ENTER_METHOD();
3282 +       SPH_RET(0);
3283 +}
3284 +
3285 +
3286 +// Given a starting key, and an ending key estimate the number of rows that
3287 +// will exist between the two. end_key may be empty which in case determine
3288 +// if start_key matches any rows.
3289 +//
3290 +// Called from opt_range.cc by check_quick_keys().
3291 +ha_rows ha_sphinx::records_in_range ( uint, key_range *, key_range * )
3292 +{
3293 +       SPH_ENTER_METHOD();
3294 +       SPH_RET(3); // low number to force index usage
3295 +}
3296 +
3297 +
3298 +// create() is called to create a database. The variable name will have the name
3299 +// of the table. When create() is called you do not need to worry about opening
3300 +// the table. Also, the FRM file will have already been created so adjusting
3301 +// create_info will not do you any good. You can overwrite the frm file at this
3302 +// point if you wish to change the table definition, but there are no methods
3303 +// currently provided for doing that.
3304 +//
3305 +// Called from handle.cc by ha_create_table().
3306 +int ha_sphinx::create ( const char * name, TABLE * table, HA_CREATE_INFO * )
3307 +{
3308 +       SPH_ENTER_METHOD();
3309 +       char sError[256];
3310 +
3311 +       CSphSEShare tInfo;
3312 +       if ( !ParseUrl ( &tInfo, table, true ) )
3313 +               SPH_RET(-1);
3314 +
3315 +       // check SphinxAPI table
3316 +       for ( ; !tInfo.m_bSphinxQL; )
3317 +       {
3318 +               // check system fields (count and types)
3319 +               if ( table->s->fields<SPHINXSE_SYSTEM_COLUMNS )
3320 +               {
3321 +                       my_snprintf ( sError, sizeof(sError), "%s: there MUST be at least %d columns",
3322 +                               name, SPHINXSE_SYSTEM_COLUMNS );
3323 +                       break;
3324 +               }
3325 +
3326 +               if ( !IsIDField ( table->field[0] ) )
3327 +               {
3328 +                       my_snprintf ( sError, sizeof(sError), "%s: 1st column (docid) MUST be unsigned integer or bigint", name );
3329 +                       break;
3330 +               }
3331 +
3332 +               if ( !IsIntegerFieldType ( table->field[1]->type() ) )
3333 +               {
3334 +                       my_snprintf ( sError, sizeof(sError), "%s: 2nd column (weight) MUST be integer or bigint", name );
3335 +                       break;
3336 +               }
3337 +
3338 +               enum_field_types f2 = table->field[2]->type();
3339 +               if ( f2!=MYSQL_TYPE_VARCHAR
3340 +                       && f2!=MYSQL_TYPE_BLOB && f2!=MYSQL_TYPE_MEDIUM_BLOB && f2!=MYSQL_TYPE_LONG_BLOB && f2!=MYSQL_TYPE_TINY_BLOB )
3341 +               {
3342 +                       my_snprintf ( sError, sizeof(sError), "%s: 3rd column (search query) MUST be varchar or text", name );
3343 +                       break;
3344 +               }
3345 +
3346 +               // check attributes
3347 +               int i;
3348 +               for ( i=3; i<(int)table->s->fields; i++ )
3349 +               {
3350 +                       enum_field_types eType = table->field[i]->type();
3351 +                       if ( eType!=MYSQL_TYPE_TIMESTAMP && !IsIntegerFieldType(eType) && eType!=MYSQL_TYPE_VARCHAR && eType!=MYSQL_TYPE_FLOAT )
3352 +                       {
3353 +                               my_snprintf ( sError, sizeof(sError), "%s: %dth column (attribute %s) MUST be integer, bigint, timestamp, varchar, or float",
3354 +                                       name, i+1, table->field[i]->field_name );
3355 +                               break;
3356 +                       }
3357 +               }
3358 +
3359 +               if ( i!=(int)table->s->fields )
3360 +                       break;
3361 +
3362 +               // check index
3363 +               if (
3364 +                       table->s->keys!=1 ||
3365 +                       table->key_info[0].key_parts!=1 ||
3366 +                       strcasecmp ( table->key_info[0].key_part[0].field->field_name, table->field[2]->field_name ) )
3367 +               {
3368 +                       my_snprintf ( sError, sizeof(sError), "%s: there must be an index on '%s' column",
3369 +                               name, table->field[2]->field_name );
3370 +                       break;
3371 +               }
3372 +
3373 +               // all good
3374 +               sError[0] = '\0';
3375 +               break;
3376 +       }
3377 +
3378 +       // check SphinxQL table
3379 +       for ( ; tInfo.m_bSphinxQL; )
3380 +       {
3381 +               sError[0] = '\0';
3382 +
3383 +               // check that 1st column is id, is of int type, and has an index
3384 +               if ( strcmp ( table->field[0]->field_name, "id" ) )
3385 +               {
3386 +                       my_snprintf ( sError, sizeof(sError), "%s: 1st column must be called 'id'", name );
3387 +                       break;
3388 +               }
3389 +
3390 +               if ( !IsIDField ( table->field[0] ) )
3391 +               {
3392 +                       my_snprintf ( sError, sizeof(sError), "%s: 'id' column must be INT UNSIGNED or BIGINT", name );
3393 +                       break;
3394 +               }
3395 +
3396 +               // check index
3397 +               if (
3398 +                       table->s->keys!=1 ||
3399 +                       table->key_info[0].key_parts!=1 ||
3400 +                       strcasecmp ( table->key_info[0].key_part[0].field->field_name, "id" ) )
3401 +               {
3402 +                       my_snprintf ( sError, sizeof(sError), "%s: 'id' column must be indexed", name );
3403 +                       break;
3404 +               }
3405 +
3406 +               // check column types
3407 +               for ( int i=1; i<(int)table->s->fields; i++ )
3408 +               {
3409 +                       enum_field_types eType = table->field[i]->type();
3410 +                       if ( eType!=MYSQL_TYPE_TIMESTAMP && !IsIntegerFieldType(eType) && eType!=MYSQL_TYPE_VARCHAR && eType!=MYSQL_TYPE_FLOAT )
3411 +                       {
3412 +                               my_snprintf ( sError, sizeof(sError), "%s: column %s is of unsupported type (use int/bigint/timestamp/varchar/float)",
3413 +                                       name, i+1, table->field[i]->field_name );
3414 +                               break;
3415 +                       }
3416 +               }
3417 +               if ( sError[0] )
3418 +                       break;
3419 +
3420 +               // all good
3421 +               break;
3422 +       }
3423 +
3424 +       // report and bail
3425 +       if ( sError[0] )
3426 +       {
3427 +               my_error ( ER_CANT_CREATE_TABLE, MYF(0), sError, -1 );
3428 +               SPH_RET(-1);
3429 +       }
3430 +
3431 +       SPH_RET(0);
3432 +}
3433 +
3434 +// show functions
3435 +
3436 +#if MYSQL_VERSION_ID<50100
3437 +#define SHOW_VAR_FUNC_BUFF_SIZE 1024
3438 +#endif
3439 +
3440 +CSphSEStats * sphinx_get_stats ( THD * thd, SHOW_VAR * out )
3441 +{
3442 +#if MYSQL_VERSION_ID>50100
3443 +       if ( sphinx_hton_ptr )
3444 +       {
3445 +               CSphSEThreadData *pTls = (CSphSEThreadData *) *thd_ha_data ( thd, sphinx_hton_ptr );
3446 +
3447 +               if ( pTls && pTls->m_bStats )
3448 +                       return &pTls->m_tStats;
3449 +       }
3450 +#else
3451 +       CSphSEThreadData *pTls = (CSphSEThreadData *) thd->ha_data[sphinx_hton.slot];
3452 +       if ( pTls && pTls->m_bStats )
3453 +               return &pTls->m_tStats;
3454 +#endif
3455 +
3456 +       out->type = SHOW_CHAR;
3457 +       out->value = "";
3458 +       return 0;
3459 +}
3460 +
3461 +int sphinx_showfunc_total ( THD * thd, SHOW_VAR * out, char * )
3462 +{
3463 +       CSphSEStats * pStats = sphinx_get_stats ( thd, out );
3464 +       if ( pStats )
3465 +       {
3466 +               out->type = SHOW_INT;
3467 +               out->value = (char *) &pStats->m_iMatchesTotal;
3468 +       }
3469 +       return 0;
3470 +}
3471 +
3472 +int sphinx_showfunc_total_found ( THD * thd, SHOW_VAR * out, char * )
3473 +{
3474 +       CSphSEStats * pStats = sphinx_get_stats ( thd, out );
3475 +       if ( pStats )
3476 +       {
3477 +               out->type = SHOW_INT;
3478 +               out->value = (char *) &pStats->m_iMatchesFound;
3479 +       }
3480 +       return 0;
3481 +}
3482 +
3483 +int sphinx_showfunc_time ( THD * thd, SHOW_VAR * out, char * )
3484 +{
3485 +       CSphSEStats * pStats = sphinx_get_stats ( thd, out );
3486 +       if ( pStats )
3487 +       {
3488 +               out->type = SHOW_INT;
3489 +               out->value = (char *) &pStats->m_iQueryMsec;
3490 +       }
3491 +       return 0;
3492 +}
3493 +
3494 +int sphinx_showfunc_word_count ( THD * thd, SHOW_VAR * out, char * )
3495 +{
3496 +       CSphSEStats * pStats = sphinx_get_stats ( thd, out );
3497 +       if ( pStats )
3498 +       {
3499 +               out->type = SHOW_INT;
3500 +               out->value = (char *) &pStats->m_iWords;
3501 +       }
3502 +       return 0;
3503 +}
3504 +
3505 +int sphinx_showfunc_words ( THD * thd, SHOW_VAR * out, char * sBuffer )
3506 +{
3507 +#if MYSQL_VERSION_ID>50100
3508 +       if ( sphinx_hton_ptr )
3509 +       {
3510 +               CSphSEThreadData * pTls = (CSphSEThreadData *) *thd_ha_data ( thd, sphinx_hton_ptr );
3511 +#else
3512 +       {
3513 +               CSphSEThreadData * pTls = (CSphSEThreadData *) thd->ha_data[sphinx_hton.slot];
3514 +#endif
3515 +               if ( pTls && pTls->m_bStats )
3516 +               {
3517 +                       CSphSEStats * pStats = &pTls->m_tStats;
3518 +                       if ( pStats && pStats->m_iWords )
3519 +                       {
3520 +                               uint uBuffLen = 0;
3521 +
3522 +                               out->type = SHOW_CHAR;
3523 +                               out->value = sBuffer;
3524 +
3525 +                               // the following is partially based on code in sphinx_show_status()
3526 +                               sBuffer[0] = 0;
3527 +                               for ( int i=0; i<pStats->m_iWords; i++ )
3528 +                               {
3529 +                                       CSphSEWordStats & tWord = pStats->m_dWords[i];
3530 +                                       uBuffLen = my_snprintf ( sBuffer, SHOW_VAR_FUNC_BUFF_SIZE, "%s%s:%d:%d ", sBuffer,
3531 +                                               tWord.m_sWord, tWord.m_iDocs, tWord.m_iHits );
3532 +                               }
3533 +
3534 +                               if ( uBuffLen > 0 )
3535 +                               {
3536 +                                       // trim last space
3537 +                                       sBuffer [ --uBuffLen ] = 0;
3538 +
3539 +                                       if ( pTls->m_pQueryCharset )
3540 +                                       {