riddle 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +16 -0
  3. data/Gemfile +6 -0
  4. data/HISTORY +45 -0
  5. data/LICENCE +20 -0
  6. data/README.textile +5 -3
  7. data/Rakefile +23 -0
  8. data/lib/riddle.rb +1 -0
  9. data/lib/riddle/0.9.9/configuration/searchd.rb +10 -8
  10. data/lib/riddle/auto_version.rb +2 -2
  11. data/lib/riddle/client.rb +117 -118
  12. data/lib/riddle/configuration.rb +6 -6
  13. data/lib/riddle/configuration/distributed_index.rb +16 -16
  14. data/lib/riddle/configuration/sql_source.rb +5 -5
  15. data/lib/riddle/controller.rb +28 -25
  16. data/lib/riddle/query.rb +31 -20
  17. data/lib/riddle/query/select.rb +69 -8
  18. data/lib/riddle/version.rb +3 -0
  19. data/riddle.gemspec +25 -0
  20. data/spec/fixtures/.gitignore +2 -0
  21. data/spec/fixtures/data/0.9.9/anchor.bin +0 -0
  22. data/spec/fixtures/data/0.9.9/any.bin +0 -0
  23. data/spec/fixtures/data/0.9.9/boolean.bin +0 -0
  24. data/spec/fixtures/data/0.9.9/comment.bin +0 -0
  25. data/spec/fixtures/data/0.9.9/distinct.bin +0 -0
  26. data/spec/fixtures/data/0.9.9/field_weights.bin +0 -0
  27. data/spec/fixtures/data/0.9.9/filter.bin +0 -0
  28. data/spec/fixtures/data/0.9.9/filter_array.bin +0 -0
  29. data/spec/fixtures/data/0.9.9/filter_array_exclude.bin +0 -0
  30. data/spec/fixtures/data/0.9.9/filter_boolean.bin +0 -0
  31. data/spec/fixtures/data/0.9.9/filter_floats.bin +0 -0
  32. data/spec/fixtures/data/0.9.9/filter_floats_exclude.bin +0 -0
  33. data/spec/fixtures/data/0.9.9/filter_range.bin +0 -0
  34. data/spec/fixtures/data/0.9.9/filter_range_exclude.bin +0 -0
  35. data/spec/fixtures/data/0.9.9/group.bin +0 -0
  36. data/spec/fixtures/data/0.9.9/index.bin +0 -0
  37. data/spec/fixtures/data/0.9.9/index_weights.bin +0 -0
  38. data/spec/fixtures/data/0.9.9/keywords_with_hits.bin +0 -0
  39. data/spec/fixtures/data/0.9.9/keywords_without_hits.bin +0 -0
  40. data/spec/fixtures/data/0.9.9/overrides.bin +0 -0
  41. data/spec/fixtures/data/0.9.9/phrase.bin +0 -0
  42. data/spec/fixtures/data/0.9.9/rank_mode.bin +0 -0
  43. data/spec/fixtures/data/0.9.9/select.bin +0 -0
  44. data/spec/fixtures/data/0.9.9/simple.bin +0 -0
  45. data/spec/fixtures/data/0.9.9/sort.bin +0 -0
  46. data/spec/fixtures/data/0.9.9/update_simple.bin +0 -0
  47. data/spec/fixtures/data/0.9.9/weights.bin +0 -0
  48. data/spec/fixtures/data/1.10/anchor.bin +0 -0
  49. data/spec/fixtures/data/1.10/any.bin +0 -0
  50. data/spec/fixtures/data/1.10/boolean.bin +0 -0
  51. data/spec/fixtures/data/1.10/comment.bin +0 -0
  52. data/spec/fixtures/data/1.10/distinct.bin +0 -0
  53. data/spec/fixtures/data/1.10/field_weights.bin +0 -0
  54. data/spec/fixtures/data/1.10/filter.bin +0 -0
  55. data/spec/fixtures/data/1.10/filter_array.bin +0 -0
  56. data/spec/fixtures/data/1.10/filter_array_exclude.bin +0 -0
  57. data/spec/fixtures/data/1.10/filter_boolean.bin +0 -0
  58. data/spec/fixtures/data/1.10/filter_floats.bin +0 -0
  59. data/spec/fixtures/data/1.10/filter_floats_exclude.bin +0 -0
  60. data/spec/fixtures/data/1.10/filter_range.bin +0 -0
  61. data/spec/fixtures/data/1.10/filter_range_exclude.bin +0 -0
  62. data/spec/fixtures/data/1.10/group.bin +0 -0
  63. data/spec/fixtures/data/1.10/index.bin +0 -0
  64. data/spec/fixtures/data/1.10/index_weights.bin +0 -0
  65. data/spec/fixtures/data/1.10/keywords_with_hits.bin +0 -0
  66. data/spec/fixtures/data/1.10/keywords_without_hits.bin +0 -0
  67. data/spec/fixtures/data/1.10/overrides.bin +0 -0
  68. data/spec/fixtures/data/1.10/phrase.bin +0 -0
  69. data/spec/fixtures/data/1.10/rank_mode.bin +0 -0
  70. data/spec/fixtures/data/1.10/select.bin +0 -0
  71. data/spec/fixtures/data/1.10/simple.bin +0 -0
  72. data/spec/fixtures/data/1.10/sort.bin +0 -0
  73. data/spec/fixtures/data/1.10/update_simple.bin +0 -0
  74. data/spec/fixtures/data/1.10/weights.bin +0 -0
  75. data/spec/fixtures/data/2.0.1/anchor.bin +0 -0
  76. data/spec/fixtures/data/2.0.1/any.bin +0 -0
  77. data/spec/fixtures/data/2.0.1/boolean.bin +0 -0
  78. data/spec/fixtures/data/2.0.1/comment.bin +0 -0
  79. data/spec/fixtures/data/2.0.1/distinct.bin +0 -0
  80. data/spec/fixtures/data/2.0.1/field_weights.bin +0 -0
  81. data/spec/fixtures/data/2.0.1/filter.bin +0 -0
  82. data/spec/fixtures/data/2.0.1/filter_array.bin +0 -0
  83. data/spec/fixtures/data/2.0.1/filter_array_exclude.bin +0 -0
  84. data/spec/fixtures/data/2.0.1/filter_boolean.bin +0 -0
  85. data/spec/fixtures/data/2.0.1/filter_floats.bin +0 -0
  86. data/spec/fixtures/data/2.0.1/filter_floats_exclude.bin +0 -0
  87. data/spec/fixtures/data/2.0.1/filter_range.bin +0 -0
  88. data/spec/fixtures/data/2.0.1/filter_range_exclude.bin +0 -0
  89. data/spec/fixtures/data/2.0.1/group.bin +0 -0
  90. data/spec/fixtures/data/2.0.1/index.bin +0 -0
  91. data/spec/fixtures/data/2.0.1/index_weights.bin +0 -0
  92. data/spec/fixtures/data/2.0.1/keywords_with_hits.bin +0 -0
  93. data/spec/fixtures/data/2.0.1/keywords_without_hits.bin +0 -0
  94. data/spec/fixtures/data/2.0.1/overrides.bin +0 -0
  95. data/spec/fixtures/data/2.0.1/phrase.bin +0 -0
  96. data/spec/fixtures/data/2.0.1/rank_mode.bin +0 -0
  97. data/spec/fixtures/data/2.0.1/select.bin +0 -0
  98. data/spec/fixtures/data/2.0.1/simple.bin +0 -0
  99. data/spec/fixtures/data/2.0.1/sort.bin +0 -0
  100. data/spec/fixtures/data/2.0.1/update_simple.bin +0 -0
  101. data/spec/fixtures/data/2.0.1/weights.bin +0 -0
  102. data/spec/fixtures/data_generator.0.9.8.php +208 -0
  103. data/spec/fixtures/data_generator.0.9.9.php +5 -0
  104. data/spec/fixtures/data_generator.1.10.php +5 -0
  105. data/spec/fixtures/data_generator.2.0.1.php +5 -0
  106. data/spec/fixtures/data_generator.php +223 -0
  107. data/spec/fixtures/sphinxapi.0.9.8.php +1228 -0
  108. data/spec/fixtures/sphinxapi.0.9.9.php +1646 -0
  109. data/spec/fixtures/sphinxapi.1.10.php +1728 -0
  110. data/spec/fixtures/sphinxapi.2.0.1.php +1731 -0
  111. data/spec/fixtures/sql/conf.example.yml +3 -0
  112. data/spec/fixtures/sql/data.sql +25000 -0
  113. data/spec/fixtures/sql/data.tsv +25000 -0
  114. data/spec/fixtures/sql/structure.sql +16 -0
  115. data/spec/functional/connection_spec.rb +10 -12
  116. data/spec/functional/excerpt_spec.rb +1 -1
  117. data/spec/functional/keywords_spec.rb +1 -1
  118. data/spec/functional/persistance_spec.rb +1 -1
  119. data/spec/functional/search_spec.rb +1 -1
  120. data/spec/functional/status_spec.rb +1 -1
  121. data/spec/functional/update_spec.rb +1 -1
  122. data/spec/riddle/auto_version_spec.rb +18 -10
  123. data/spec/riddle/query/select_spec.rb +78 -14
  124. data/spec/riddle/query_spec.rb +5 -3
  125. data/spec/spec_helper.rb +13 -15
  126. data/spec/support/binary_fixtures.rb +18 -0
  127. data/spec/support/sphinx.rb +135 -0
  128. data/spec/unit/client_spec.rb +150 -142
  129. data/spec/unit/configuration/distributed_index_spec.rb +15 -15
  130. data/spec/unit/configuration/searchd_spec.rb +28 -3
  131. data/spec/unit/configuration_spec.rb +6 -6
  132. metadata +254 -68
  133. data/spec/sphinx_helper.rb +0 -96
@@ -0,0 +1,1728 @@
1
+ <?php
2
+
3
+ //
4
+ // $Id: sphinxapi.php 2376 2010-06-29 14:08:19Z shodan $
5
+ //
6
+
7
+ //
8
+ // Copyright (c) 2001-2010, Andrew Aksyonoff
9
+ // Copyright (c) 2008-2010, Sphinx Technologies Inc
10
+ // All rights reserved
11
+ //
12
+ // This program is free software; you can redistribute it and/or modify
13
+ // it under the terms of the GNU General Public License. You should have
14
+ // received a copy of the GPL license along with this program; if you
15
+ // did not, you can find it at http://www.gnu.org/
16
+ //
17
+
18
+ /////////////////////////////////////////////////////////////////////////////
19
+ // PHP version of Sphinx searchd client (PHP API)
20
+ /////////////////////////////////////////////////////////////////////////////
21
+
22
+ /// known searchd commands
23
+ define ( "SEARCHD_COMMAND_SEARCH", 0 );
24
+ define ( "SEARCHD_COMMAND_EXCERPT", 1 );
25
+ define ( "SEARCHD_COMMAND_UPDATE", 2 );
26
+ define ( "SEARCHD_COMMAND_KEYWORDS", 3 );
27
+ define ( "SEARCHD_COMMAND_PERSIST", 4 );
28
+ define ( "SEARCHD_COMMAND_STATUS", 5 );
29
+ define ( "SEARCHD_COMMAND_QUERY", 6 );
30
+ define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 );
31
+
32
+ /// current client-side command implementation versions
33
+ define ( "VER_COMMAND_SEARCH", 0x117 );
34
+ define ( "VER_COMMAND_EXCERPT", 0x102 );
35
+ define ( "VER_COMMAND_UPDATE", 0x102 );
36
+ define ( "VER_COMMAND_KEYWORDS", 0x100 );
37
+ define ( "VER_COMMAND_STATUS", 0x100 );
38
+ define ( "VER_COMMAND_QUERY", 0x100 );
39
+ define ( "VER_COMMAND_FLUSHATTRS", 0x100 );
40
+
41
+ /// known searchd status codes
42
+ define ( "SEARCHD_OK", 0 );
43
+ define ( "SEARCHD_ERROR", 1 );
44
+ define ( "SEARCHD_RETRY", 2 );
45
+ define ( "SEARCHD_WARNING", 3 );
46
+
47
+ /// known match modes
48
+ define ( "SPH_MATCH_ALL", 0 );
49
+ define ( "SPH_MATCH_ANY", 1 );
50
+ define ( "SPH_MATCH_PHRASE", 2 );
51
+ define ( "SPH_MATCH_BOOLEAN", 3 );
52
+ define ( "SPH_MATCH_EXTENDED", 4 );
53
+ define ( "SPH_MATCH_FULLSCAN", 5 );
54
+ define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED)
55
+
56
+ /// known ranking modes (ext2 only)
57
+ define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one
58
+ define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality)
59
+ define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1
60
+ define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts
61
+ define ( "SPH_RANK_PROXIMITY", 4 );
62
+ define ( "SPH_RANK_MATCHANY", 5 );
63
+ define ( "SPH_RANK_FIELDMASK", 6 );
64
+ define ( "SPH_RANK_SPH04", 7 );
65
+ define ( "SPH_RANK_TOTAL", 8 );
66
+
67
+ /// known sort modes
68
+ define ( "SPH_SORT_RELEVANCE", 0 );
69
+ define ( "SPH_SORT_ATTR_DESC", 1 );
70
+ define ( "SPH_SORT_ATTR_ASC", 2 );
71
+ define ( "SPH_SORT_TIME_SEGMENTS", 3 );
72
+ define ( "SPH_SORT_EXTENDED", 4 );
73
+ define ( "SPH_SORT_EXPR", 5 );
74
+
75
+ /// known filter types
76
+ define ( "SPH_FILTER_VALUES", 0 );
77
+ define ( "SPH_FILTER_RANGE", 1 );
78
+ define ( "SPH_FILTER_FLOATRANGE", 2 );
79
+
80
+ /// known attribute types
81
+ define ( "SPH_ATTR_INTEGER", 1 );
82
+ define ( "SPH_ATTR_TIMESTAMP", 2 );
83
+ define ( "SPH_ATTR_ORDINAL", 3 );
84
+ define ( "SPH_ATTR_BOOL", 4 );
85
+ define ( "SPH_ATTR_FLOAT", 5 );
86
+ define ( "SPH_ATTR_BIGINT", 6 );
87
+ define ( "SPH_ATTR_STRING", 7 );
88
+ define ( "SPH_ATTR_MULTI", 0x40000000 );
89
+
90
+ /// known grouping functions
91
+ define ( "SPH_GROUPBY_DAY", 0 );
92
+ define ( "SPH_GROUPBY_WEEK", 1 );
93
+ define ( "SPH_GROUPBY_MONTH", 2 );
94
+ define ( "SPH_GROUPBY_YEAR", 3 );
95
+ define ( "SPH_GROUPBY_ATTR", 4 );
96
+ define ( "SPH_GROUPBY_ATTRPAIR", 5 );
97
+
98
+ // important properties of PHP's integers:
99
+ // - always signed (one bit short of PHP_INT_SIZE)
100
+ // - conversion from string to int is saturated
101
+ // - float is double
102
+ // - div converts arguments to floats
103
+ // - mod converts arguments to ints
104
+
105
+ // the packing code below works as follows:
106
+ // - when we got an int, just pack it
107
+ // if performance is a problem, this is the branch users should aim for
108
+ //
109
+ // - otherwise, we got a number in string form
110
+ // this might be due to different reasons, but we assume that this is
111
+ // because it didn't fit into PHP int
112
+ //
113
+ // - factor the string into high and low ints for packing
114
+ // - if we have bcmath, then it is used
115
+ // - if we don't, we have to do it manually (this is the fun part)
116
+ //
117
+ // - x64 branch does factoring using ints
118
+ // - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int
119
+ //
120
+ // unpacking routines are pretty much the same.
121
+ // - return ints if we can
122
+ // - otherwise format number into a string
123
+
124
+ /// pack 64-bit signed
125
+ function sphPackI64 ( $v )
126
+ {
127
+ assert ( is_numeric($v) );
128
+
129
+ // x64
130
+ if ( PHP_INT_SIZE>=8 )
131
+ {
132
+ $v = (int)$v;
133
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
134
+ }
135
+
136
+ // x32, int
137
+ if ( is_int($v) )
138
+ return pack ( "NN", $v < 0 ? -1 : 0, $v );
139
+
140
+ // x32, bcmath
141
+ if ( function_exists("bcmul") )
142
+ {
143
+ if ( bccomp ( $v, 0 ) == -1 )
144
+ $v = bcadd ( "18446744073709551616", $v );
145
+ $h = bcdiv ( $v, "4294967296", 0 );
146
+ $l = bcmod ( $v, "4294967296" );
147
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
148
+ }
149
+
150
+ // x32, no-bcmath
151
+ $p = max(0, strlen($v) - 13);
152
+ $lo = abs((float)substr($v, $p));
153
+ $hi = abs((float)substr($v, 0, $p));
154
+
155
+ $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912
156
+ $q = floor($m/4294967296.0);
157
+ $l = $m - ($q*4294967296.0);
158
+ $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328
159
+
160
+ if ( $v<0 )
161
+ {
162
+ if ( $l==0 )
163
+ $h = 4294967296.0 - $h;
164
+ else
165
+ {
166
+ $h = 4294967295.0 - $h;
167
+ $l = 4294967296.0 - $l;
168
+ }
169
+ }
170
+ return pack ( "NN", $h, $l );
171
+ }
172
+
173
+ /// pack 64-bit unsigned
174
+ function sphPackU64 ( $v )
175
+ {
176
+ assert ( is_numeric($v) );
177
+
178
+ // x64
179
+ if ( PHP_INT_SIZE>=8 )
180
+ {
181
+ assert ( $v>=0 );
182
+
183
+ // x64, int
184
+ if ( is_int($v) )
185
+ return pack ( "NN", $v>>32, $v&0xFFFFFFFF );
186
+
187
+ // x64, bcmath
188
+ if ( function_exists("bcmul") )
189
+ {
190
+ $h = bcdiv ( $v, 4294967296, 0 );
191
+ $l = bcmod ( $v, 4294967296 );
192
+ return pack ( "NN", $h, $l );
193
+ }
194
+
195
+ // x64, no-bcmath
196
+ $p = max ( 0, strlen($v) - 13 );
197
+ $lo = (int)substr ( $v, $p );
198
+ $hi = (int)substr ( $v, 0, $p );
199
+
200
+ $m = $lo + $hi*1316134912;
201
+ $l = $m % 4294967296;
202
+ $h = $hi*2328 + (int)($m/4294967296);
203
+
204
+ return pack ( "NN", $h, $l );
205
+ }
206
+
207
+ // x32, int
208
+ if ( is_int($v) )
209
+ return pack ( "NN", 0, $v );
210
+
211
+ // x32, bcmath
212
+ if ( function_exists("bcmul") )
213
+ {
214
+ $h = bcdiv ( $v, "4294967296", 0 );
215
+ $l = bcmod ( $v, "4294967296" );
216
+ return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit
217
+ }
218
+
219
+ // x32, no-bcmath
220
+ $p = max(0, strlen($v) - 13);
221
+ $lo = (float)substr($v, $p);
222
+ $hi = (float)substr($v, 0, $p);
223
+
224
+ $m = $lo + $hi*1316134912.0;
225
+ $q = floor($m / 4294967296.0);
226
+ $l = $m - ($q * 4294967296.0);
227
+ $h = $hi*2328.0 + $q;
228
+
229
+ return pack ( "NN", $h, $l );
230
+ }
231
+
232
+ // unpack 64-bit unsigned
233
+ function sphUnpackU64 ( $v )
234
+ {
235
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
236
+
237
+ if ( PHP_INT_SIZE>=8 )
238
+ {
239
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
240
+ if ( $lo<0 ) $lo += (1<<32);
241
+
242
+ // x64, int
243
+ if ( $hi<=2147483647 )
244
+ return ($hi<<32) + $lo;
245
+
246
+ // x64, bcmath
247
+ if ( function_exists("bcmul") )
248
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
249
+
250
+ // x64, no-bcmath
251
+ $C = 100000;
252
+ $h = ((int)($hi / $C) << 32) + (int)($lo / $C);
253
+ $l = (($hi % $C) << 32) + ($lo % $C);
254
+ if ( $l>$C )
255
+ {
256
+ $h += (int)($l / $C);
257
+ $l = $l % $C;
258
+ }
259
+
260
+ if ( $h==0 )
261
+ return $l;
262
+ return sprintf ( "%d%05d", $h, $l );
263
+ }
264
+
265
+ // x32, int
266
+ if ( $hi==0 )
267
+ {
268
+ if ( $lo>0 )
269
+ return $lo;
270
+ return sprintf ( "%u", $lo );
271
+ }
272
+
273
+ $hi = sprintf ( "%u", $hi );
274
+ $lo = sprintf ( "%u", $lo );
275
+
276
+ // x32, bcmath
277
+ if ( function_exists("bcmul") )
278
+ return bcadd ( $lo, bcmul ( $hi, "4294967296" ) );
279
+
280
+ // x32, no-bcmath
281
+ $hi = (float)$hi;
282
+ $lo = (float)$lo;
283
+
284
+ $q = floor($hi/10000000.0);
285
+ $r = $hi - $q*10000000.0;
286
+ $m = $lo + $r*4967296.0;
287
+ $mq = floor($m/10000000.0);
288
+ $l = $m - $mq*10000000.0;
289
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
290
+
291
+ $h = sprintf ( "%.0f", $h );
292
+ $l = sprintf ( "%07.0f", $l );
293
+ if ( $h=="0" )
294
+ return sprintf( "%.0f", (float)$l );
295
+ return $h . $l;
296
+ }
297
+
298
+ // unpack 64-bit signed
299
+ function sphUnpackI64 ( $v )
300
+ {
301
+ list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) );
302
+
303
+ // x64
304
+ if ( PHP_INT_SIZE>=8 )
305
+ {
306
+ if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again
307
+ if ( $lo<0 ) $lo += (1<<32);
308
+
309
+ return ($hi<<32) + $lo;
310
+ }
311
+
312
+ // x32, int
313
+ if ( $hi==0 )
314
+ {
315
+ if ( $lo>0 )
316
+ return $lo;
317
+ return sprintf ( "%u", $lo );
318
+ }
319
+ // x32, int
320
+ elseif ( $hi==-1 )
321
+ {
322
+ if ( $lo<0 )
323
+ return $lo;
324
+ return sprintf ( "%.0f", $lo - 4294967296.0 );
325
+ }
326
+
327
+ $neg = "";
328
+ $c = 0;
329
+ if ( $hi<0 )
330
+ {
331
+ $hi = ~$hi;
332
+ $lo = ~$lo;
333
+ $c = 1;
334
+ $neg = "-";
335
+ }
336
+
337
+ $hi = sprintf ( "%u", $hi );
338
+ $lo = sprintf ( "%u", $lo );
339
+
340
+ // x32, bcmath
341
+ if ( function_exists("bcmul") )
342
+ return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c );
343
+
344
+ // x32, no-bcmath
345
+ $hi = (float)$hi;
346
+ $lo = (float)$lo;
347
+
348
+ $q = floor($hi/10000000.0);
349
+ $r = $hi - $q*10000000.0;
350
+ $m = $lo + $r*4967296.0;
351
+ $mq = floor($m/10000000.0);
352
+ $l = $m - $mq*10000000.0 + $c;
353
+ $h = $q*4294967296.0 + $r*429.0 + $mq;
354
+ if ( $l==10000000 )
355
+ {
356
+ $l = 0;
357
+ $h += 1;
358
+ }
359
+
360
+ $h = sprintf ( "%.0f", $h );
361
+ $l = sprintf ( "%07.0f", $l );
362
+ if ( $h=="0" )
363
+ return $neg . sprintf( "%.0f", (float)$l );
364
+ return $neg . $h . $l;
365
+ }
366
+
367
+
368
+ function sphFixUint ( $value )
369
+ {
370
+ if ( PHP_INT_SIZE>=8 )
371
+ {
372
+ // x64 route, workaround broken unpack() in 5.2.2+
373
+ if ( $value<0 ) $value += (1<<32);
374
+ return $value;
375
+ }
376
+ else
377
+ {
378
+ // x32 route, workaround php signed/unsigned braindamage
379
+ return sprintf ( "%u", $value );
380
+ }
381
+ }
382
+
383
+
384
+ /// sphinx searchd client class
385
+ class SphinxClient
386
+ {
387
+ var $_host; ///< searchd host (default is "localhost")
388
+ var $_port; ///< searchd port (default is 9312)
389
+ var $_offset; ///< how many records to seek from result-set start (default is 0)
390
+ var $_limit; ///< how many records to return from result-set starting at offset (default is 20)
391
+ var $_mode; ///< query matching mode (default is SPH_MATCH_ALL)
392
+ var $_weights; ///< per-field weights (default is 1 for all fields)
393
+ var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE)
394
+ var $_sortby; ///< attribute to sort by (defualt is "")
395
+ var $_min_id; ///< min ID to match (default is 0, which means no limit)
396
+ var $_max_id; ///< max ID to match (default is 0, which means no limit)
397
+ var $_filters; ///< search filters
398
+ var $_groupby; ///< group-by attribute name
399
+ var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with)
400
+ var $_groupsort; ///< group-by sorting clause (to sort groups in result set with)
401
+ var $_groupdistinct;///< group-by count-distinct attribute
402
+ var $_maxmatches; ///< max matches to retrieve
403
+ var $_cutoff; ///< cutoff to stop searching at (default is 0)
404
+ var $_retrycount; ///< distributed retries count
405
+ var $_retrydelay; ///< distributed retries delay
406
+ var $_anchor; ///< geographical anchor point
407
+ var $_indexweights; ///< per-index weights
408
+ var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25)
409
+ var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit)
410
+ var $_fieldweights; ///< per-field-name weights
411
+ var $_overrides; ///< per-query attribute values overrides
412
+ var $_select; ///< select-list (attributes or expressions, with optional aliases)
413
+
414
+ var $_error; ///< last error message
415
+ var $_warning; ///< last warning message
416
+ var $_connerror; ///< connection error vs remote error flag
417
+
418
+ var $_reqs; ///< requests array for multi-query
419
+ var $_mbenc; ///< stored mbstring encoding
420
+ var $_arrayresult; ///< whether $result["matches"] should be a hash or an array
421
+ var $_timeout; ///< connect timeout
422
+
423
+ /////////////////////////////////////////////////////////////////////////////
424
+ // common stuff
425
+ /////////////////////////////////////////////////////////////////////////////
426
+
427
+ /// create a new client object and fill defaults
428
+ function SphinxClient ()
429
+ {
430
+ // per-client-object settings
431
+ $this->_host = "localhost";
432
+ $this->_port = 9312;
433
+ $this->_path = false;
434
+ $this->_socket = false;
435
+
436
+ // per-query settings
437
+ $this->_offset = 0;
438
+ $this->_limit = 20;
439
+ $this->_mode = SPH_MATCH_ALL;
440
+ $this->_weights = array ();
441
+ $this->_sort = SPH_SORT_RELEVANCE;
442
+ $this->_sortby = "";
443
+ $this->_min_id = 0;
444
+ $this->_max_id = 0;
445
+ $this->_filters = array ();
446
+ $this->_groupby = "";
447
+ $this->_groupfunc = SPH_GROUPBY_DAY;
448
+ $this->_groupsort = "@group desc";
449
+ $this->_groupdistinct= "";
450
+ $this->_maxmatches = 1000;
451
+ $this->_cutoff = 0;
452
+ $this->_retrycount = 0;
453
+ $this->_retrydelay = 0;
454
+ $this->_anchor = array ();
455
+ $this->_indexweights= array ();
456
+ $this->_ranker = SPH_RANK_PROXIMITY_BM25;
457
+ $this->_maxquerytime= 0;
458
+ $this->_fieldweights= array();
459
+ $this->_overrides = array();
460
+ $this->_select = "*";
461
+
462
+ $this->_error = ""; // per-reply fields (for single-query case)
463
+ $this->_warning = "";
464
+ $this->_connerror = false;
465
+
466
+ $this->_reqs = array (); // requests storage (for multi-query case)
467
+ $this->_mbenc = "";
468
+ $this->_arrayresult = false;
469
+ $this->_timeout = 0;
470
+ }
471
+
472
+ function __destruct()
473
+ {
474
+ if ( $this->_socket !== false )
475
+ fclose ( $this->_socket );
476
+ }
477
+
478
+ /// get last error message (string)
479
+ function GetLastError ()
480
+ {
481
+ return $this->_error;
482
+ }
483
+
484
+ /// get last warning message (string)
485
+ function GetLastWarning ()
486
+ {
487
+ return $this->_warning;
488
+ }
489
+
490
+ /// get last error flag (to tell network connection errors from searchd errors or broken responses)
491
+ function IsConnectError()
492
+ {
493
+ return $this->_connerror;
494
+ }
495
+
496
+ /// set searchd host name (string) and port (integer)
497
+ function SetServer ( $host, $port = 0 )
498
+ {
499
+ assert ( is_string($host) );
500
+ if ( $host[0] == '/')
501
+ {
502
+ $this->_path = 'unix://' . $host;
503
+ return;
504
+ }
505
+ if ( substr ( $host, 0, 7 )=="unix://" )
506
+ {
507
+ $this->_path = $host;
508
+ return;
509
+ }
510
+
511
+ assert ( is_int($port) );
512
+ $this->_host = $host;
513
+ $this->_port = $port;
514
+ $this->_path = '';
515
+
516
+ }
517
+
518
+ /// set server connection timeout (0 to remove)
519
+ function SetConnectTimeout ( $timeout )
520
+ {
521
+ assert ( is_numeric($timeout) );
522
+ $this->_timeout = $timeout;
523
+ }
524
+
525
+
526
+ function _Send ( $handle, $data, $length )
527
+ {
528
+ if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length )
529
+ {
530
+ $this->_error = 'connection unexpectedly closed (timed out?)';
531
+ $this->_connerror = true;
532
+ return false;
533
+ }
534
+ return true;
535
+ }
536
+
537
+ /////////////////////////////////////////////////////////////////////////////
538
+
539
+ /// enter mbstring workaround mode
540
+ function _MBPush ()
541
+ {
542
+ $this->_mbenc = "";
543
+ if ( ini_get ( "mbstring.func_overload" ) & 2 )
544
+ {
545
+ $this->_mbenc = mb_internal_encoding();
546
+ mb_internal_encoding ( "latin1" );
547
+ }
548
+ }
549
+
550
+ /// leave mbstring workaround mode
551
+ function _MBPop ()
552
+ {
553
+ if ( $this->_mbenc )
554
+ mb_internal_encoding ( $this->_mbenc );
555
+ }
556
+
557
+ /// connect to searchd server
558
+ function _Connect ()
559
+ {
560
+ if ( $this->_socket!==false )
561
+ {
562
+ // we are in persistent connection mode, so we have a socket
563
+ // however, need to check whether it's still alive
564
+ if ( !@feof ( $this->_socket ) )
565
+ return $this->_socket;
566
+
567
+ // force reopen
568
+ $this->_socket = false;
569
+ }
570
+
571
+ $errno = 0;
572
+ $errstr = "";
573
+ $this->_connerror = false;
574
+
575
+ if ( $this->_path )
576
+ {
577
+ $host = $this->_path;
578
+ $port = 0;
579
+ }
580
+ else
581
+ {
582
+ $host = $this->_host;
583
+ $port = $this->_port;
584
+ }
585
+
586
+ if ( $this->_timeout<=0 )
587
+ $fp = @fsockopen ( $host, $port, $errno, $errstr );
588
+ else
589
+ $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout );
590
+
591
+ if ( !$fp )
592
+ {
593
+ if ( $this->_path )
594
+ $location = $this->_path;
595
+ else
596
+ $location = "{$this->_host}:{$this->_port}";
597
+
598
+ $errstr = trim ( $errstr );
599
+ $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)";
600
+ $this->_connerror = true;
601
+ return false;
602
+ }
603
+
604
+ // send my version
605
+ // this is a subtle part. we must do it before (!) reading back from searchd.
606
+ // because otherwise under some conditions (reported on FreeBSD for instance)
607
+ // TCP stack could throttle write-write-read pattern because of Nagle.
608
+ if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) )
609
+ {
610
+ fclose ( $fp );
611
+ $this->_error = "failed to send client protocol version";
612
+ return false;
613
+ }
614
+
615
+ // check version
616
+ list(,$v) = unpack ( "N*", fread ( $fp, 4 ) );
617
+ $v = (int)$v;
618
+ if ( $v<1 )
619
+ {
620
+ fclose ( $fp );
621
+ $this->_error = "expected searchd protocol version 1+, got version '$v'";
622
+ return false;
623
+ }
624
+
625
+ return $fp;
626
+ }
627
+
628
+ /// get and check response packet from searchd server
629
+ function _GetResponse ( $fp, $client_ver )
630
+ {
631
+ $response = "";
632
+ $len = 0;
633
+
634
+ $header = fread ( $fp, 8 );
635
+ if ( strlen($header)==8 )
636
+ {
637
+ list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) );
638
+ $left = $len;
639
+ while ( $left>0 && !feof($fp) )
640
+ {
641
+ $chunk = fread ( $fp, $left );
642
+ if ( $chunk )
643
+ {
644
+ $response .= $chunk;
645
+ $left -= strlen($chunk);
646
+ }
647
+ }
648
+ }
649
+ if ( $this->_socket === false )
650
+ fclose ( $fp );
651
+
652
+ // check response
653
+ $read = strlen ( $response );
654
+ if ( !$response || $read!=$len )
655
+ {
656
+ $this->_error = $len
657
+ ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)"
658
+ : "received zero-sized searchd response";
659
+ return false;
660
+ }
661
+
662
+ // check status
663
+ if ( $status==SEARCHD_WARNING )
664
+ {
665
+ list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) );
666
+ $this->_warning = substr ( $response, 4, $wlen );
667
+ return substr ( $response, 4+$wlen );
668
+ }
669
+ if ( $status==SEARCHD_ERROR )
670
+ {
671
+ $this->_error = "searchd error: " . substr ( $response, 4 );
672
+ return false;
673
+ }
674
+ if ( $status==SEARCHD_RETRY )
675
+ {
676
+ $this->_error = "temporary searchd error: " . substr ( $response, 4 );
677
+ return false;
678
+ }
679
+ if ( $status!=SEARCHD_OK )
680
+ {
681
+ $this->_error = "unknown status code '$status'";
682
+ return false;
683
+ }
684
+
685
+ // check version
686
+ if ( $ver<$client_ver )
687
+ {
688
+ $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
689
+ $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
690
+ }
691
+
692
+ return $response;
693
+ }
694
+
695
+ /////////////////////////////////////////////////////////////////////////////
696
+ // searching
697
+ /////////////////////////////////////////////////////////////////////////////
698
+
699
+ /// set offset and count into result set,
700
+ /// and optionally set max-matches and cutoff limits
701
+ function SetLimits ( $offset, $limit, $max=0, $cutoff=0 )
702
+ {
703
+ assert ( is_int($offset) );
704
+ assert ( is_int($limit) );
705
+ assert ( $offset>=0 );
706
+ assert ( $limit>0 );
707
+ assert ( $max>=0 );
708
+ $this->_offset = $offset;
709
+ $this->_limit = $limit;
710
+ if ( $max>0 )
711
+ $this->_maxmatches = $max;
712
+ if ( $cutoff>0 )
713
+ $this->_cutoff = $cutoff;
714
+ }
715
+
716
+ /// set maximum query time, in milliseconds, per-index
717
+ /// integer, 0 means "do not limit"
718
+ function SetMaxQueryTime ( $max )
719
+ {
720
+ assert ( is_int($max) );
721
+ assert ( $max>=0 );
722
+ $this->_maxquerytime = $max;
723
+ }
724
+
725
+ /// set matching mode
726
+ function SetMatchMode ( $mode )
727
+ {
728
+ assert ( $mode==SPH_MATCH_ALL
729
+ || $mode==SPH_MATCH_ANY
730
+ || $mode==SPH_MATCH_PHRASE
731
+ || $mode==SPH_MATCH_BOOLEAN
732
+ || $mode==SPH_MATCH_EXTENDED
733
+ || $mode==SPH_MATCH_FULLSCAN
734
+ || $mode==SPH_MATCH_EXTENDED2 );
735
+ $this->_mode = $mode;
736
+ }
737
+
738
+ /// set ranking mode
739
+ function SetRankingMode ( $ranker )
740
+ {
741
+ assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL );
742
+ $this->_ranker = $ranker;
743
+ }
744
+
745
+ /// set matches sorting mode
746
+ function SetSortMode ( $mode, $sortby="" )
747
+ {
748
+ assert (
749
+ $mode==SPH_SORT_RELEVANCE ||
750
+ $mode==SPH_SORT_ATTR_DESC ||
751
+ $mode==SPH_SORT_ATTR_ASC ||
752
+ $mode==SPH_SORT_TIME_SEGMENTS ||
753
+ $mode==SPH_SORT_EXTENDED ||
754
+ $mode==SPH_SORT_EXPR );
755
+ assert ( is_string($sortby) );
756
+ assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 );
757
+
758
+ $this->_sort = $mode;
759
+ $this->_sortby = $sortby;
760
+ }
761
+
762
+ /// bind per-field weights by order
763
+ /// DEPRECATED; use SetFieldWeights() instead
764
+ function SetWeights ( $weights )
765
+ {
766
+ assert ( is_array($weights) );
767
+ foreach ( $weights as $weight )
768
+ assert ( is_int($weight) );
769
+
770
+ $this->_weights = $weights;
771
+ }
772
+
773
+ /// bind per-field weights by name
774
+ function SetFieldWeights ( $weights )
775
+ {
776
+ assert ( is_array($weights) );
777
+ foreach ( $weights as $name=>$weight )
778
+ {
779
+ assert ( is_string($name) );
780
+ assert ( is_int($weight) );
781
+ }
782
+ $this->_fieldweights = $weights;
783
+ }
784
+
785
+ /// bind per-index weights by name
786
+ function SetIndexWeights ( $weights )
787
+ {
788
+ assert ( is_array($weights) );
789
+ foreach ( $weights as $index=>$weight )
790
+ {
791
+ assert ( is_string($index) );
792
+ assert ( is_int($weight) );
793
+ }
794
+ $this->_indexweights = $weights;
795
+ }
796
+
797
+ /// set IDs range to match
798
+ /// only match records if document ID is beetwen $min and $max (inclusive)
799
+ function SetIDRange ( $min, $max )
800
+ {
801
+ assert ( is_numeric($min) );
802
+ assert ( is_numeric($max) );
803
+ assert ( $min<=$max );
804
+ $this->_min_id = $min;
805
+ $this->_max_id = $max;
806
+ }
807
+
808
+ /// set values set filter
809
+ /// only match records where $attribute value is in given set
810
+ function SetFilter ( $attribute, $values, $exclude=false )
811
+ {
812
+ assert ( is_string($attribute) );
813
+ assert ( is_array($values) );
814
+ assert ( count($values) );
815
+
816
+ if ( is_array($values) && count($values) )
817
+ {
818
+ foreach ( $values as $value )
819
+ assert ( is_numeric($value) );
820
+
821
+ $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values );
822
+ }
823
+ }
824
+
825
+ /// set range filter
826
+ /// only match records if $attribute value is beetwen $min and $max (inclusive)
827
+ function SetFilterRange ( $attribute, $min, $max, $exclude=false )
828
+ {
829
+ assert ( is_string($attribute) );
830
+ assert ( is_numeric($min) );
831
+ assert ( is_numeric($max) );
832
+ assert ( $min<=$max );
833
+
834
+ $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
835
+ }
836
+
837
+ /// set float range filter
838
+ /// only match records if $attribute value is beetwen $min and $max (inclusive)
839
+ function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false )
840
+ {
841
+ assert ( is_string($attribute) );
842
+ assert ( is_float($min) );
843
+ assert ( is_float($max) );
844
+ assert ( $min<=$max );
845
+
846
+ $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max );
847
+ }
848
+
849
+ /// setup anchor point for geosphere distance calculations
850
+ /// required to use @geodist in filters and sorting
851
+ /// latitude and longitude must be in radians
852
+ function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long )
853
+ {
854
+ assert ( is_string($attrlat) );
855
+ assert ( is_string($attrlong) );
856
+ assert ( is_float($lat) );
857
+ assert ( is_float($long) );
858
+
859
+ $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long );
860
+ }
861
+
862
+ /// set grouping attribute and function
863
+ function SetGroupBy ( $attribute, $func, $groupsort="@group desc" )
864
+ {
865
+ assert ( is_string($attribute) );
866
+ assert ( is_string($groupsort) );
867
+ assert ( $func==SPH_GROUPBY_DAY
868
+ || $func==SPH_GROUPBY_WEEK
869
+ || $func==SPH_GROUPBY_MONTH
870
+ || $func==SPH_GROUPBY_YEAR
871
+ || $func==SPH_GROUPBY_ATTR
872
+ || $func==SPH_GROUPBY_ATTRPAIR );
873
+
874
+ $this->_groupby = $attribute;
875
+ $this->_groupfunc = $func;
876
+ $this->_groupsort = $groupsort;
877
+ }
878
+
879
+ /// set count-distinct attribute for group-by queries
880
+ function SetGroupDistinct ( $attribute )
881
+ {
882
+ assert ( is_string($attribute) );
883
+ $this->_groupdistinct = $attribute;
884
+ }
885
+
886
+ /// set distributed retries count and delay
887
+ function SetRetries ( $count, $delay=0 )
888
+ {
889
+ assert ( is_int($count) && $count>=0 );
890
+ assert ( is_int($delay) && $delay>=0 );
891
+ $this->_retrycount = $count;
892
+ $this->_retrydelay = $delay;
893
+ }
894
+
895
+ /// set result set format (hash or array; hash by default)
896
+ /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs
897
+ function SetArrayResult ( $arrayresult )
898
+ {
899
+ assert ( is_bool($arrayresult) );
900
+ $this->_arrayresult = $arrayresult;
901
+ }
902
+
903
+ /// set attribute values override
904
+ /// there can be only one override per attribute
905
+ /// $values must be a hash that maps document IDs to attribute values
906
+ function SetOverride ( $attrname, $attrtype, $values )
907
+ {
908
+ assert ( is_string ( $attrname ) );
909
+ assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) );
910
+ assert ( is_array ( $values ) );
911
+
912
+ $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values );
913
+ }
914
+
915
+ /// set select-list (attributes or expressions), SQL-like syntax
916
+ function SetSelect ( $select )
917
+ {
918
+ assert ( is_string ( $select ) );
919
+ $this->_select = $select;
920
+ }
921
+
922
+ //////////////////////////////////////////////////////////////////////////////
923
+
924
+ /// clear all filters (for multi-queries)
925
+ function ResetFilters ()
926
+ {
927
+ $this->_filters = array();
928
+ $this->_anchor = array();
929
+ }
930
+
931
+ /// clear groupby settings (for multi-queries)
932
+ function ResetGroupBy ()
933
+ {
934
+ $this->_groupby = "";
935
+ $this->_groupfunc = SPH_GROUPBY_DAY;
936
+ $this->_groupsort = "@group desc";
937
+ $this->_groupdistinct= "";
938
+ }
939
+
940
+ /// clear all attribute value overrides (for multi-queries)
941
+ function ResetOverrides ()
942
+ {
943
+ $this->_overrides = array ();
944
+ }
945
+
946
+ //////////////////////////////////////////////////////////////////////////////
947
+
948
+ /// connect to searchd server, run given search query through given indexes,
949
+ /// and return the search results
950
+ function Query ( $query, $index="*", $comment="" )
951
+ {
952
+ assert ( empty($this->_reqs) );
953
+
954
+ $this->AddQuery ( $query, $index, $comment );
955
+ $results = $this->RunQueries ();
956
+ $this->_reqs = array (); // just in case it failed too early
957
+
958
+ if ( !is_array($results) )
959
+ return false; // probably network error; error message should be already filled
960
+
961
+ $this->_error = $results[0]["error"];
962
+ $this->_warning = $results[0]["warning"];
963
+ if ( $results[0]["status"]==SEARCHD_ERROR )
964
+ return false;
965
+ else
966
+ return $results[0];
967
+ }
968
+
969
+ /// helper to pack floats in network byte order
970
+ function _PackFloat ( $f )
971
+ {
972
+ $t1 = pack ( "f", $f ); // machine order
973
+ list(,$t2) = unpack ( "L*", $t1 ); // int in machine order
974
+ return pack ( "N", $t2 );
975
+ }
976
+
977
+ /// add query to multi-query batch
978
+ /// returns index into results array from RunQueries() call
979
+ function AddQuery ( $query, $index="*", $comment="" )
980
+ {
981
+ // mbstring workaround
982
+ $this->_MBPush ();
983
+
984
+ // build request
985
+ $req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits
986
+ $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby;
987
+ $req .= pack ( "N", strlen($query) ) . $query; // query itself
988
+ $req .= pack ( "N", count($this->_weights) ); // weights
989
+ foreach ( $this->_weights as $weight )
990
+ $req .= pack ( "N", (int)$weight );
991
+ $req .= pack ( "N", strlen($index) ) . $index; // indexes
992
+ $req .= pack ( "N", 1 ); // id64 range marker
993
+ $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range
994
+
995
+ // filters
996
+ $req .= pack ( "N", count($this->_filters) );
997
+ foreach ( $this->_filters as $filter )
998
+ {
999
+ $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
1000
+ $req .= pack ( "N", $filter["type"] );
1001
+ switch ( $filter["type"] )
1002
+ {
1003
+ case SPH_FILTER_VALUES:
1004
+ $req .= pack ( "N", count($filter["values"]) );
1005
+ foreach ( $filter["values"] as $value )
1006
+ $req .= sphPackI64 ( $value );
1007
+ break;
1008
+
1009
+ case SPH_FILTER_RANGE:
1010
+ $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
1011
+ break;
1012
+
1013
+ case SPH_FILTER_FLOATRANGE:
1014
+ $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
1015
+ break;
1016
+
1017
+ default:
1018
+ assert ( 0 && "internal error: unhandled filter type" );
1019
+ }
1020
+ $req .= pack ( "N", $filter["exclude"] );
1021
+ }
1022
+
1023
+ // group-by clause, max-matches count, group-sort clause, cutoff count
1024
+ $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby;
1025
+ $req .= pack ( "N", $this->_maxmatches );
1026
+ $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort;
1027
+ $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay );
1028
+ $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct;
1029
+
1030
+ // anchor point
1031
+ if ( empty($this->_anchor) )
1032
+ {
1033
+ $req .= pack ( "N", 0 );
1034
+ } else
1035
+ {
1036
+ $a =& $this->_anchor;
1037
+ $req .= pack ( "N", 1 );
1038
+ $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"];
1039
+ $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"];
1040
+ $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] );
1041
+ }
1042
+
1043
+ // per-index weights
1044
+ $req .= pack ( "N", count($this->_indexweights) );
1045
+ foreach ( $this->_indexweights as $idx=>$weight )
1046
+ $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight );
1047
+
1048
+ // max query time
1049
+ $req .= pack ( "N", $this->_maxquerytime );
1050
+
1051
+ // per-field weights
1052
+ $req .= pack ( "N", count($this->_fieldweights) );
1053
+ foreach ( $this->_fieldweights as $field=>$weight )
1054
+ $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight );
1055
+
1056
+ // comment
1057
+ $req .= pack ( "N", strlen($comment) ) . $comment;
1058
+
1059
+ // attribute overrides
1060
+ $req .= pack ( "N", count($this->_overrides) );
1061
+ foreach ( $this->_overrides as $key => $entry )
1062
+ {
1063
+ $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"];
1064
+ $req .= pack ( "NN", $entry["type"], count($entry["values"]) );
1065
+ foreach ( $entry["values"] as $id=>$val )
1066
+ {
1067
+ assert ( is_numeric($id) );
1068
+ assert ( is_numeric($val) );
1069
+
1070
+ $req .= sphPackU64 ( $id );
1071
+ switch ( $entry["type"] )
1072
+ {
1073
+ case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break;
1074
+ case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break;
1075
+ default: $req .= pack ( "N", $val ); break;
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ // select-list
1081
+ $req .= pack ( "N", strlen($this->_select) ) . $this->_select;
1082
+
1083
+ // mbstring workaround
1084
+ $this->_MBPop ();
1085
+
1086
+ // store request to requests array
1087
+ $this->_reqs[] = $req;
1088
+ return count($this->_reqs)-1;
1089
+ }
1090
+
1091
+ /// connect to searchd, run queries batch, and return an array of result sets
1092
+ function RunQueries ()
1093
+ {
1094
+ if ( empty($this->_reqs) )
1095
+ {
1096
+ $this->_error = "no queries defined, issue AddQuery() first";
1097
+ return false;
1098
+ }
1099
+
1100
+ // mbstring workaround
1101
+ $this->_MBPush ();
1102
+
1103
+ if (!( $fp = $this->_Connect() ))
1104
+ {
1105
+ $this->_MBPop ();
1106
+ return false;
1107
+ }
1108
+
1109
+ // send query, get response
1110
+ $nreqs = count($this->_reqs);
1111
+ $req = join ( "", $this->_reqs );
1112
+ $len = 4+strlen($req);
1113
+ $req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header
1114
+
1115
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1116
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) )
1117
+ {
1118
+ $this->_MBPop ();
1119
+ return false;
1120
+ }
1121
+
1122
+ // query sent ok; we can reset reqs now
1123
+ $this->_reqs = array ();
1124
+
1125
+ // parse and return response
1126
+ return $this->_ParseSearchResponse ( $response, $nreqs );
1127
+ }
1128
+
1129
+ /// parse and return search query (or queries) response
1130
+ function _ParseSearchResponse ( $response, $nreqs )
1131
+ {
1132
+ $p = 0; // current position
1133
+ $max = strlen($response); // max position for checks, to protect against broken responses
1134
+
1135
+ $results = array ();
1136
+ for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ )
1137
+ {
1138
+ $results[] = array();
1139
+ $result =& $results[$ires];
1140
+
1141
+ $result["error"] = "";
1142
+ $result["warning"] = "";
1143
+
1144
+ // extract status
1145
+ list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1146
+ $result["status"] = $status;
1147
+ if ( $status!=SEARCHD_OK )
1148
+ {
1149
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1150
+ $message = substr ( $response, $p, $len ); $p += $len;
1151
+
1152
+ if ( $status==SEARCHD_WARNING )
1153
+ {
1154
+ $result["warning"] = $message;
1155
+ } else
1156
+ {
1157
+ $result["error"] = $message;
1158
+ continue;
1159
+ }
1160
+ }
1161
+
1162
+ // read schema
1163
+ $fields = array ();
1164
+ $attrs = array ();
1165
+
1166
+ list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1167
+ while ( $nfields-->0 && $p<$max )
1168
+ {
1169
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1170
+ $fields[] = substr ( $response, $p, $len ); $p += $len;
1171
+ }
1172
+ $result["fields"] = $fields;
1173
+
1174
+ list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1175
+ while ( $nattrs-->0 && $p<$max )
1176
+ {
1177
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1178
+ $attr = substr ( $response, $p, $len ); $p += $len;
1179
+ list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1180
+ $attrs[$attr] = $type;
1181
+ }
1182
+ $result["attrs"] = $attrs;
1183
+
1184
+ // read match count
1185
+ list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1186
+ list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1187
+
1188
+ // read matches
1189
+ $idx = -1;
1190
+ while ( $count-->0 && $p<$max )
1191
+ {
1192
+ // index into result array
1193
+ $idx++;
1194
+
1195
+ // parse document id and weight
1196
+ if ( $id64 )
1197
+ {
1198
+ $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8;
1199
+ list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1200
+ }
1201
+ else
1202
+ {
1203
+ list ( $doc, $weight ) = array_values ( unpack ( "N*N*",
1204
+ substr ( $response, $p, 8 ) ) );
1205
+ $p += 8;
1206
+ $doc = sphFixUint($doc);
1207
+ }
1208
+ $weight = sprintf ( "%u", $weight );
1209
+
1210
+ // create match entry
1211
+ if ( $this->_arrayresult )
1212
+ $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight );
1213
+ else
1214
+ $result["matches"][$doc]["weight"] = $weight;
1215
+
1216
+ // parse and create attributes
1217
+ $attrvals = array ();
1218
+ foreach ( $attrs as $attr=>$type )
1219
+ {
1220
+ // handle 64bit ints
1221
+ if ( $type==SPH_ATTR_BIGINT )
1222
+ {
1223
+ $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8;
1224
+ continue;
1225
+ }
1226
+
1227
+ // handle floats
1228
+ if ( $type==SPH_ATTR_FLOAT )
1229
+ {
1230
+ list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1231
+ list(,$fval) = unpack ( "f*", pack ( "L", $uval ) );
1232
+ $attrvals[$attr] = $fval;
1233
+ continue;
1234
+ }
1235
+
1236
+ // handle everything else as unsigned ints
1237
+ list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1238
+ if ( $type & SPH_ATTR_MULTI )
1239
+ {
1240
+ $attrvals[$attr] = array ();
1241
+ $nvalues = $val;
1242
+ while ( $nvalues-->0 && $p<$max )
1243
+ {
1244
+ list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1245
+ $attrvals[$attr][] = sphFixUint($val);
1246
+ }
1247
+ } else if ( $type==SPH_ATTR_STRING )
1248
+ {
1249
+ $attrvals[$attr] = substr ( $response, $p, $val );
1250
+ $p += $val;
1251
+ } else
1252
+ {
1253
+ $attrvals[$attr] = sphFixUint($val);
1254
+ }
1255
+ }
1256
+
1257
+ if ( $this->_arrayresult )
1258
+ $result["matches"][$idx]["attrs"] = $attrvals;
1259
+ else
1260
+ $result["matches"][$doc]["attrs"] = $attrvals;
1261
+ }
1262
+
1263
+ list ( $total, $total_found, $msecs, $words ) =
1264
+ array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) );
1265
+ $result["total"] = sprintf ( "%u", $total );
1266
+ $result["total_found"] = sprintf ( "%u", $total_found );
1267
+ $result["time"] = sprintf ( "%.3f", $msecs/1000 );
1268
+ $p += 16;
1269
+
1270
+ while ( $words-->0 && $p<$max )
1271
+ {
1272
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1273
+ $word = substr ( $response, $p, $len ); $p += $len;
1274
+ list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1275
+ $result["words"][$word] = array (
1276
+ "docs"=>sprintf ( "%u", $docs ),
1277
+ "hits"=>sprintf ( "%u", $hits ) );
1278
+ }
1279
+ }
1280
+
1281
+ $this->_MBPop ();
1282
+ return $results;
1283
+ }
1284
+
1285
+ /////////////////////////////////////////////////////////////////////////////
1286
+ // excerpts generation
1287
+ /////////////////////////////////////////////////////////////////////////////
1288
+
1289
+ /// connect to searchd server, and generate exceprts (snippets)
1290
+ /// of given documents for given query. returns false on failure,
1291
+ /// an array of snippets on success
1292
+ function BuildExcerpts ( $docs, $index, $words, $opts=array() )
1293
+ {
1294
+ assert ( is_array($docs) );
1295
+ assert ( is_string($index) );
1296
+ assert ( is_string($words) );
1297
+ assert ( is_array($opts) );
1298
+
1299
+ $this->_MBPush ();
1300
+
1301
+ if (!( $fp = $this->_Connect() ))
1302
+ {
1303
+ $this->_MBPop();
1304
+ return false;
1305
+ }
1306
+
1307
+ /////////////////
1308
+ // fixup options
1309
+ /////////////////
1310
+
1311
+ if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>";
1312
+ if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>";
1313
+ if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... ";
1314
+ if ( !isset($opts["limit"]) ) $opts["limit"] = 256;
1315
+ if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0;
1316
+ if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0;
1317
+ if ( !isset($opts["around"]) ) $opts["around"] = 5;
1318
+ if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false;
1319
+ if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false;
1320
+ if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false;
1321
+ if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false;
1322
+ if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false;
1323
+ if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false;
1324
+ if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1;
1325
+ if ( !isset($opts["load_files"]) ) $opts["load_files"] = false;
1326
+ if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index";
1327
+ if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false;
1328
+
1329
+ /////////////////
1330
+ // build request
1331
+ /////////////////
1332
+
1333
+ // v.1.2 req
1334
+ $flags = 1; // remove spaces
1335
+ if ( $opts["exact_phrase"] ) $flags |= 2;
1336
+ if ( $opts["single_passage"] ) $flags |= 4;
1337
+ if ( $opts["use_boundaries"] ) $flags |= 8;
1338
+ if ( $opts["weight_order"] ) $flags |= 16;
1339
+ if ( $opts["query_mode"] ) $flags |= 32;
1340
+ if ( $opts["force_all_words"] ) $flags |= 64;
1341
+ if ( $opts["load_files"] ) $flags |= 128;
1342
+ if ( $opts["allow_empty"] ) $flags |= 256;
1343
+ $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags
1344
+ $req .= pack ( "N", strlen($index) ) . $index; // req index
1345
+ $req .= pack ( "N", strlen($words) ) . $words; // req words
1346
+
1347
+ // options
1348
+ $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"];
1349
+ $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"];
1350
+ $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"];
1351
+ $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] );
1352
+ $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2
1353
+ $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"];
1354
+
1355
+ // documents
1356
+ $req .= pack ( "N", count($docs) );
1357
+ foreach ( $docs as $doc )
1358
+ {
1359
+ assert ( is_string($doc) );
1360
+ $req .= pack ( "N", strlen($doc) ) . $doc;
1361
+ }
1362
+
1363
+ ////////////////////////////
1364
+ // send query, get response
1365
+ ////////////////////////////
1366
+
1367
+ $len = strlen($req);
1368
+ $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header
1369
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1370
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) )
1371
+ {
1372
+ $this->_MBPop ();
1373
+ return false;
1374
+ }
1375
+
1376
+ //////////////////
1377
+ // parse response
1378
+ //////////////////
1379
+
1380
+ $pos = 0;
1381
+ $res = array ();
1382
+ $rlen = strlen($response);
1383
+ for ( $i=0; $i<count($docs); $i++ )
1384
+ {
1385
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1386
+ $pos += 4;
1387
+
1388
+ if ( $pos+$len > $rlen )
1389
+ {
1390
+ $this->_error = "incomplete reply";
1391
+ $this->_MBPop ();
1392
+ return false;
1393
+ }
1394
+ $res[] = $len ? substr ( $response, $pos, $len ) : "";
1395
+ $pos += $len;
1396
+ }
1397
+
1398
+ $this->_MBPop ();
1399
+ return $res;
1400
+ }
1401
+
1402
+
1403
+ /////////////////////////////////////////////////////////////////////////////
1404
+ // keyword generation
1405
+ /////////////////////////////////////////////////////////////////////////////
1406
+
1407
+ /// connect to searchd server, and generate keyword list for a given query
1408
+ /// returns false on failure,
1409
+ /// an array of words on success
1410
+ function BuildKeywords ( $query, $index, $hits )
1411
+ {
1412
+ assert ( is_string($query) );
1413
+ assert ( is_string($index) );
1414
+ assert ( is_bool($hits) );
1415
+
1416
+ // Commented out for testing Riddle
1417
+ // $this->_MBPush ();
1418
+ //
1419
+ // if (!( $fp = $this->_Connect() ))
1420
+ // {
1421
+ // $this->_MBPop();
1422
+ // return false;
1423
+ // }
1424
+
1425
+ /////////////////
1426
+ // build request
1427
+ /////////////////
1428
+
1429
+ // v.1.0 req
1430
+ $req = pack ( "N", strlen($query) ) . $query; // req query
1431
+ $req .= pack ( "N", strlen($index) ) . $index; // req index
1432
+ $req .= pack ( "N", (int)$hits );
1433
+
1434
+ // Line for testing Riddle:
1435
+ return $req;
1436
+
1437
+ ////////////////////////////
1438
+ // send query, get response
1439
+ ////////////////////////////
1440
+
1441
+ $len = strlen($req);
1442
+ $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header
1443
+ if ( !( $this->_Send ( $fp, $req, $len+8 ) ) ||
1444
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) )
1445
+ {
1446
+ $this->_MBPop ();
1447
+ return false;
1448
+ }
1449
+
1450
+ //////////////////
1451
+ // parse response
1452
+ //////////////////
1453
+
1454
+ $pos = 0;
1455
+ $res = array ();
1456
+ $rlen = strlen($response);
1457
+ list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) );
1458
+ $pos += 4;
1459
+ for ( $i=0; $i<$nwords; $i++ )
1460
+ {
1461
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
1462
+ $tokenized = $len ? substr ( $response, $pos, $len ) : "";
1463
+ $pos += $len;
1464
+
1465
+ list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4;
1466
+ $normalized = $len ? substr ( $response, $pos, $len ) : "";
1467
+ $pos += $len;
1468
+
1469
+ $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized );
1470
+
1471
+ if ( $hits )
1472
+ {
1473
+ list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) );
1474
+ $pos += 8;
1475
+ $res [$i]["docs"] = $ndocs;
1476
+ $res [$i]["hits"] = $nhits;
1477
+ }
1478
+
1479
+ if ( $pos > $rlen )
1480
+ {
1481
+ $this->_error = "incomplete reply";
1482
+ $this->_MBPop ();
1483
+ return false;
1484
+ }
1485
+ }
1486
+
1487
+ $this->_MBPop ();
1488
+ return $res;
1489
+ }
1490
+
1491
+ function EscapeString ( $string )
1492
+ {
1493
+ $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' );
1494
+ $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' );
1495
+
1496
+ return str_replace ( $from, $to, $string );
1497
+ }
1498
+
1499
+ /////////////////////////////////////////////////////////////////////////////
1500
+ // attribute updates
1501
+ /////////////////////////////////////////////////////////////////////////////
1502
+
1503
+ /// batch update given attributes in given rows in given indexes
1504
+ /// returns amount of updated documents (0 or more) on success, or -1 on failure
1505
+ function UpdateAttributes ( $index, $attrs, $values, $mva=false )
1506
+ {
1507
+ // verify everything
1508
+ assert ( is_string($index) );
1509
+ assert ( is_bool($mva) );
1510
+
1511
+ assert ( is_array($attrs) );
1512
+ foreach ( $attrs as $attr )
1513
+ assert ( is_string($attr) );
1514
+
1515
+ assert ( is_array($values) );
1516
+ foreach ( $values as $id=>$entry )
1517
+ {
1518
+ assert ( is_numeric($id) );
1519
+ assert ( is_array($entry) );
1520
+ assert ( count($entry)==count($attrs) );
1521
+ foreach ( $entry as $v )
1522
+ {
1523
+ if ( $mva )
1524
+ {
1525
+ assert ( is_array($v) );
1526
+ foreach ( $v as $vv )
1527
+ assert ( is_int($vv) );
1528
+ } else
1529
+ assert ( is_int($v) );
1530
+ }
1531
+ }
1532
+
1533
+ // build request
1534
+ $this->_MBPush ();
1535
+ $req = pack ( "N", strlen($index) ) . $index;
1536
+
1537
+ $req .= pack ( "N", count($attrs) );
1538
+ foreach ( $attrs as $attr )
1539
+ {
1540
+ $req .= pack ( "N", strlen($attr) ) . $attr;
1541
+ $req .= pack ( "N", $mva ? 1 : 0 );
1542
+ }
1543
+
1544
+ $req .= pack ( "N", count($values) );
1545
+ foreach ( $values as $id=>$entry )
1546
+ {
1547
+ $req .= sphPackU64 ( $id );
1548
+ foreach ( $entry as $v )
1549
+ {
1550
+ $req .= pack ( "N", $mva ? count($v) : $v );
1551
+ if ( $mva )
1552
+ foreach ( $v as $vv )
1553
+ $req .= pack ( "N", $vv );
1554
+ }
1555
+ }
1556
+
1557
+ // Line for testing Riddle:
1558
+ return $req;
1559
+
1560
+ // connect, send query, get response
1561
+ if (!( $fp = $this->_Connect() ))
1562
+ {
1563
+ $this->_MBPop ();
1564
+ return -1;
1565
+ }
1566
+
1567
+ $len = strlen($req);
1568
+ $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header
1569
+ if ( !$this->_Send ( $fp, $req, $len+8 ) )
1570
+ {
1571
+ $this->_MBPop ();
1572
+ return -1;
1573
+ }
1574
+
1575
+ if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) ))
1576
+ {
1577
+ $this->_MBPop ();
1578
+ return -1;
1579
+ }
1580
+
1581
+ // parse response
1582
+ list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) );
1583
+ $this->_MBPop ();
1584
+ return $updated;
1585
+ }
1586
+
1587
+ /////////////////////////////////////////////////////////////////////////////
1588
+ // persistent connections
1589
+ /////////////////////////////////////////////////////////////////////////////
1590
+
1591
+ function Open()
1592
+ {
1593
+ if ( $this->_socket !== false )
1594
+ {
1595
+ $this->_error = 'already connected';
1596
+ return false;
1597
+ }
1598
+ if ( !$fp = $this->_Connect() )
1599
+ return false;
1600
+
1601
+ // command, command version = 0, body length = 4, body = 1
1602
+ $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 );
1603
+ if ( !$this->_Send ( $fp, $req, 12 ) )
1604
+ return false;
1605
+
1606
+ $this->_socket = $fp;
1607
+ return true;
1608
+ }
1609
+
1610
+ function Close()
1611
+ {
1612
+ if ( $this->_socket === false )
1613
+ {
1614
+ $this->_error = 'not connected';
1615
+ return false;
1616
+ }
1617
+
1618
+ fclose ( $this->_socket );
1619
+ $this->_socket = false;
1620
+
1621
+ return true;
1622
+ }
1623
+
1624
+ //////////////////////////////////////////////////////////////////////////
1625
+ // status
1626
+ //////////////////////////////////////////////////////////////////////////
1627
+
1628
+ function Status ()
1629
+ {
1630
+ $this->_MBPush ();
1631
+ if (!( $fp = $this->_Connect() ))
1632
+ {
1633
+ $this->_MBPop();
1634
+ return false;
1635
+ }
1636
+
1637
+ $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1
1638
+ if ( !( $this->_Send ( $fp, $req, 12 ) ) ||
1639
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) )
1640
+ {
1641
+ $this->_MBPop ();
1642
+ return false;
1643
+ }
1644
+
1645
+ $res = substr ( $response, 4 ); // just ignore length, error handling, etc
1646
+ $p = 0;
1647
+ list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8;
1648
+
1649
+ $res = array();
1650
+ for ( $i=0; $i<$rows; $i++ )
1651
+ for ( $j=0; $j<$cols; $j++ )
1652
+ {
1653
+ list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
1654
+ $res[$i][] = substr ( $response, $p, $len ); $p += $len;
1655
+ }
1656
+
1657
+ $this->_MBPop ();
1658
+ return $res;
1659
+ }
1660
+
1661
+ //////////////////////////////////////////////////////////////////////////
1662
+ // flush
1663
+ //////////////////////////////////////////////////////////////////////////
1664
+
1665
+ function FlushAttributes ()
1666
+ {
1667
+ $this->_MBPush ();
1668
+ if (!( $fp = $this->_Connect() ))
1669
+ {
1670
+ $this->_MBPop();
1671
+ return -1;
1672
+ }
1673
+
1674
+ $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0
1675
+ if ( !( $this->_Send ( $fp, $req, 8 ) ) ||
1676
+ !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) )
1677
+ {
1678
+ $this->_MBPop ();
1679
+ return -1;
1680
+ }
1681
+
1682
+ $tag = -1;
1683
+ if ( strlen($response)==4 )
1684
+ list(,$tag) = unpack ( "N*", $response );
1685
+ else
1686
+ $this->_error = "unexpected response length";
1687
+
1688
+ $this->_MBPop ();
1689
+ return $tag;
1690
+ }
1691
+
1692
+ // Added for Riddle - code is taken from AddQuery
1693
+ function FilterOutput()
1694
+ {
1695
+ $req = "";
1696
+ foreach ( $this->_filters as $filter )
1697
+ {
1698
+ $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"];
1699
+ $req .= pack ( "N", $filter["type"] );
1700
+ switch ( $filter["type"] )
1701
+ {
1702
+ case SPH_FILTER_VALUES:
1703
+ $req .= pack ( "N", count($filter["values"]) );
1704
+ foreach ( $filter["values"] as $value )
1705
+ $req .= sphPackI64 ( $value );
1706
+ break;
1707
+
1708
+ case SPH_FILTER_RANGE:
1709
+ $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] );
1710
+ break;
1711
+
1712
+ case SPH_FILTER_FLOATRANGE:
1713
+ $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] );
1714
+ break;
1715
+
1716
+ default:
1717
+ assert ( 0 && "internal error: unhandled filter type" );
1718
+ }
1719
+ $req .= pack ( "N", $filter["exclude"] );
1720
+ }
1721
+
1722
+ return $req;
1723
+ }
1724
+ }
1725
+
1726
+ //
1727
+ // $Id: sphinxapi.php 2376 2010-06-29 14:08:19Z shodan $
1728
+ //