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