1 /* -*- c-basic-offset: 2; indent-tabs-mode: nil -*- */
2 /* Copyright(C) 2010-2014 Brazil
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License version 2.1 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Lesser General Public License for more details.
12
13 You should have received a copy of the GNU Lesser General Public
14 License along with this library; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
16 */
17
18 #ifdef GRN_EMBEDDED
19 # define GRN_PLUGIN_FUNCTION_TAG suggest_suggest
20 #endif
21
22 #include <string.h>
23
24 #include "grn_ctx.h"
25 #include "grn_db.h"
26 #include "grn_ii.h"
27 #include "grn_token_cursor.h"
28 #include "grn_output.h"
29 #include <groonga/plugin.h>
30
31 #define VAR GRN_PROC_GET_VAR_BY_OFFSET
32 #define CONST_STR_LEN(x) x, x ? sizeof(x) - 1 : 0
33 #define TEXT_VALUE_LEN(x) GRN_TEXT_VALUE(x), GRN_TEXT_LEN(x)
34
35 #define MIN_LEARN_DISTANCE (60 * GRN_TIME_USEC_PER_SEC)
36
37 #define COMPLETE 1
38 #define CORRECT 2
39 #define SUGGEST 4
40
41 typedef enum {
42 GRN_SUGGEST_SEARCH_YES,
43 GRN_SUGGEST_SEARCH_NO,
44 GRN_SUGGEST_SEARCH_AUTO
45 } grn_suggest_search_mode;
46
47 typedef struct {
48 grn_obj *post_event;
49 grn_obj *post_type;
50 grn_obj *post_item;
51 grn_obj *seq;
52 grn_obj *post_time;
53 grn_obj *pairs;
54
55 int learn_distance_in_seconds;
56
57 grn_id post_event_id;
58 grn_id post_type_id;
59 grn_id post_item_id;
60 grn_id seq_id;
61 int64_t post_time_value;
62
63 grn_obj *seqs;
64 grn_obj *seqs_events;
65 grn_obj *events;
66 grn_obj *events_item;
67 grn_obj *events_type;
68 grn_obj *events_time;
69 grn_obj *event_types;
70 grn_obj *items;
71 grn_obj *items_freq;
72 grn_obj *items_freq2;
73 grn_obj *items_last;
74 grn_obj *pairs_pre;
75 grn_obj *pairs_post;
76 grn_obj *pairs_freq0;
77 grn_obj *pairs_freq1;
78 grn_obj *pairs_freq2;
79
80 grn_obj dataset_name;
81
82 grn_obj *configuration;
83
84 grn_obj weight;
85 grn_obj pre_events;
86
87 uint64_t key_prefix;
88 grn_obj pre_item;
89 } grn_suggest_learner;
90
91 static int
grn_parse_suggest_types(grn_obj * text)92 grn_parse_suggest_types(grn_obj *text)
93 {
94 const char *nptr = GRN_TEXT_VALUE(text);
95 const char *end = GRN_BULK_CURR(text);
96 int types = 0;
97 while (nptr < end) {
98 if (*nptr == '|') {
99 nptr += 1;
100 continue;
101 }
102 {
103 const char string[] = "complete";
104 size_t length = sizeof(string) - 1;
105 if (nptr + length <= end && memcmp(nptr, string, length) == 0) {
106 types |= COMPLETE;
107 nptr += length;
108 continue;
109 }
110 }
111 {
112 const char string[] = "correct";
113 size_t length = sizeof(string) - 1;
114 if (nptr + length <= end && memcmp(nptr, string, length) == 0) {
115 types |= CORRECT;
116 nptr += length;
117 continue;
118 }
119 }
120 {
121 const char string[] = "suggest";
122 size_t length = sizeof(string) - 1;
123 if (nptr + length <= end && memcmp(nptr, string, length) == 0) {
124 types |= SUGGEST;
125 nptr += length;
126 continue;
127 }
128 }
129 break;
130 }
131 return types;
132 }
133
134 static double
cooccurrence_search(grn_ctx * ctx,grn_obj * items,grn_obj * items_boost,grn_id id,grn_obj * res,int query_type,int frequency_threshold,double conditional_probability_threshold)135 cooccurrence_search(grn_ctx *ctx, grn_obj *items, grn_obj *items_boost, grn_id id,
136 grn_obj *res, int query_type, int frequency_threshold,
137 double conditional_probability_threshold)
138 {
139 double max_score = 0.0;
140 if (id) {
141 grn_ii_cursor *c;
142 grn_obj *co = grn_obj_column(ctx, items, CONST_STR_LEN("co"));
143 grn_obj *pairs = grn_ctx_at(ctx, grn_obj_get_range(ctx, co));
144 grn_obj *items_freq = grn_obj_column(ctx, items, CONST_STR_LEN("freq"));
145 grn_obj *items_freq2 = grn_obj_column(ctx, items, CONST_STR_LEN("freq2"));
146 grn_obj *pairs_freq, *pairs_post = grn_obj_column(ctx, pairs, CONST_STR_LEN("post"));
147 switch (query_type) {
148 case COMPLETE :
149 pairs_freq = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq0"));
150 break;
151 case CORRECT :
152 pairs_freq = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq1"));
153 break;
154 case SUGGEST :
155 pairs_freq = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq2"));
156 break;
157 default :
158 return max_score;
159 }
160 if ((c = grn_ii_cursor_open(ctx, (grn_ii *)co, id, GRN_ID_NIL, GRN_ID_MAX,
161 ((grn_ii *)co)->n_elements - 1, 0))) {
162 grn_posting *p;
163 grn_obj post, pair_freq, item_freq, item_freq2, item_boost;
164 GRN_RECORD_INIT(&post, 0, grn_obj_id(ctx, items));
165 GRN_INT32_INIT(&pair_freq, 0);
166 GRN_INT32_INIT(&item_freq, 0);
167 GRN_INT32_INIT(&item_freq2, 0);
168 GRN_INT32_INIT(&item_boost, 0);
169 while ((p = grn_ii_cursor_next(ctx, c))) {
170 grn_id post_id;
171 int pfreq, ifreq, ifreq2, boost;
172 double conditional_probability;
173 GRN_BULK_REWIND(&post);
174 GRN_BULK_REWIND(&pair_freq);
175 GRN_BULK_REWIND(&item_freq);
176 GRN_BULK_REWIND(&item_freq2);
177 GRN_BULK_REWIND(&item_boost);
178 grn_obj_get_value(ctx, pairs_post, p->rid, &post);
179 grn_obj_get_value(ctx, pairs_freq, p->rid, &pair_freq);
180 post_id = GRN_RECORD_VALUE(&post);
181 grn_obj_get_value(ctx, items_freq, post_id, &item_freq);
182 grn_obj_get_value(ctx, items_freq2, post_id, &item_freq2);
183 grn_obj_get_value(ctx, items_boost, post_id, &item_boost);
184 pfreq = GRN_INT32_VALUE(&pair_freq);
185 ifreq = GRN_INT32_VALUE(&item_freq);
186 ifreq2 = GRN_INT32_VALUE(&item_freq2);
187 if (ifreq2 > 0) {
188 conditional_probability = (double)pfreq / (double)ifreq2;
189 } else {
190 conditional_probability = 0.0;
191 }
192 boost = GRN_INT32_VALUE(&item_boost);
193 if (pfreq >= frequency_threshold && ifreq >= frequency_threshold &&
194 conditional_probability >= conditional_probability_threshold &&
195 boost >= 0) {
196 grn_rset_recinfo *ri;
197 void *value;
198 double score = pfreq;
199 int added;
200 if (max_score < score + boost) { max_score = score + boost; }
201 /* put any formula if desired */
202 if (grn_hash_add(ctx, (grn_hash *)res,
203 &post_id, sizeof(grn_id), &value, &added)) {
204 ri = value;
205 ri->score += score;
206 if (added) {
207 ri->score += boost;
208 }
209 }
210 }
211 }
212 GRN_OBJ_FIN(ctx, &post);
213 GRN_OBJ_FIN(ctx, &pair_freq);
214 GRN_OBJ_FIN(ctx, &item_freq);
215 GRN_OBJ_FIN(ctx, &item_freq2);
216 GRN_OBJ_FIN(ctx, &item_boost);
217 grn_ii_cursor_close(ctx, c);
218 }
219 }
220 return max_score;
221 }
222
223 #define DEFAULT_LIMIT 10
224 #define DEFAULT_SORTBY "-_score"
225 #define DEFAULT_OUTPUT_COLUMNS "_key,_score"
226 #define DEFAULT_FREQUENCY_THRESHOLD 100
227 #define DEFAULT_CONDITIONAL_PROBABILITY_THRESHOLD 0.2
228
229 static void
output(grn_ctx * ctx,grn_obj * table,grn_obj * res,grn_id tid,grn_obj * sortby,grn_obj * output_columns,int offset,int limit)230 output(grn_ctx *ctx, grn_obj *table, grn_obj *res, grn_id tid,
231 grn_obj *sortby, grn_obj *output_columns, int offset, int limit)
232 {
233 grn_obj *sorted;
234 if ((sorted = grn_table_create(ctx, NULL, 0, NULL, GRN_OBJ_TABLE_NO_KEY, NULL, res))) {
235 uint32_t nkeys;
236 grn_obj_format format;
237 grn_table_sort_key *keys;
238 const char *sortby_val = GRN_TEXT_VALUE(sortby);
239 unsigned int sortby_len = GRN_TEXT_LEN(sortby);
240 const char *oc_val = GRN_TEXT_VALUE(output_columns);
241 unsigned int oc_len = GRN_TEXT_LEN(output_columns);
242 if (!sortby_val || !sortby_len) {
243 sortby_val = DEFAULT_SORTBY;
244 sortby_len = sizeof(DEFAULT_SORTBY) - 1;
245 }
246 if (!oc_val || !oc_len) {
247 oc_val = DEFAULT_OUTPUT_COLUMNS;
248 oc_len = sizeof(DEFAULT_OUTPUT_COLUMNS) - 1;
249 }
250 if ((keys = grn_table_sort_key_from_str(ctx, sortby_val, sortby_len, res, &nkeys))) {
251 grn_table_sort(ctx, res, offset, limit, sorted, keys, nkeys);
252 GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_SIZE,
253 ":", "sort(%d)", limit);
254 GRN_OBJ_FORMAT_INIT(&format, grn_table_size(ctx, res), 0, limit, offset);
255 format.flags =
256 GRN_OBJ_FORMAT_WITH_COLUMN_NAMES|
257 GRN_OBJ_FORMAT_XML_ELEMENT_RESULTSET;
258 grn_obj_columns(ctx, sorted, oc_val, oc_len, &format.columns);
259 GRN_OUTPUT_OBJ(sorted, &format);
260 GRN_OBJ_FORMAT_FIN(ctx, &format);
261 grn_table_sort_key_close(ctx, keys, nkeys);
262 }
263 grn_obj_unlink(ctx, sorted);
264 } else {
265 ERR(GRN_UNKNOWN_ERROR, "cannot create temporary sort table.");
266 }
267 }
268
269 static inline void
complete_add_item(grn_ctx * ctx,grn_id id,grn_obj * res,int frequency_threshold,grn_obj * items_freq,grn_obj * items_boost,grn_obj * item_freq,grn_obj * item_boost)270 complete_add_item(grn_ctx *ctx, grn_id id, grn_obj *res, int frequency_threshold,
271 grn_obj *items_freq, grn_obj *items_boost,
272 grn_obj *item_freq, grn_obj *item_boost)
273 {
274 GRN_BULK_REWIND(item_freq);
275 GRN_BULK_REWIND(item_boost);
276 grn_obj_get_value(ctx, items_freq, id, item_freq);
277 grn_obj_get_value(ctx, items_boost, id, item_boost);
278 if (GRN_INT32_VALUE(item_boost) >= 0) {
279 double score;
280 score = 1 +
281 GRN_INT32_VALUE(item_freq) +
282 GRN_INT32_VALUE(item_boost);
283 if (score >= frequency_threshold) {
284 void *value;
285 if (grn_hash_add(ctx, (grn_hash *)res, &id, sizeof(grn_id),
286 &value, NULL)) {
287 grn_rset_recinfo *ri;
288 ri = value;
289 ri->score += score;
290 }
291 }
292 }
293 }
294
295 static void
complete(grn_ctx * ctx,grn_obj * items,grn_obj * items_boost,grn_obj * col,grn_obj * query,grn_obj * sortby,grn_obj * output_columns,int offset,int limit,int frequency_threshold,double conditional_probability_threshold,grn_suggest_search_mode prefix_search_mode)296 complete(grn_ctx *ctx, grn_obj *items, grn_obj *items_boost, grn_obj *col,
297 grn_obj *query, grn_obj *sortby,
298 grn_obj *output_columns, int offset, int limit,
299 int frequency_threshold, double conditional_probability_threshold,
300 grn_suggest_search_mode prefix_search_mode)
301 {
302 grn_obj *res;
303 grn_obj *items_freq = grn_obj_column(ctx, items, CONST_STR_LEN("freq"));
304 grn_obj item_freq, item_boost;
305 GRN_INT32_INIT(&item_freq, 0);
306 GRN_INT32_INIT(&item_boost, 0);
307 if ((res = grn_table_create(ctx, NULL, 0, NULL,
308 GRN_TABLE_HASH_KEY|GRN_OBJ_WITH_SUBREC, items, NULL))) {
309 grn_id tid = grn_table_get(ctx, items, TEXT_VALUE_LEN(query));
310 if (GRN_TEXT_LEN(query)) {
311 grn_table_cursor *cur;
312 /* RK search + prefix search */
313 grn_obj *index;
314 /* FIXME: support index selection */
315 if (grn_column_index(ctx, col, GRN_OP_PREFIX, &index, 1, NULL)) {
316 if ((cur = grn_table_cursor_open(ctx, grn_ctx_at(ctx, index->header.domain),
317 GRN_TEXT_VALUE(query),
318 GRN_TEXT_LEN(query),
319 NULL, 0, 0, -1,
320 GRN_CURSOR_PREFIX|GRN_CURSOR_RK))) {
321 grn_id id;
322 while ((id = grn_table_cursor_next(ctx, cur))) {
323 grn_ii_cursor *icur;
324 if ((icur = grn_ii_cursor_open(ctx, (grn_ii *)index, id,
325 GRN_ID_NIL, GRN_ID_MAX, 1, 0))) {
326 grn_posting *p;
327 while ((p = grn_ii_cursor_next(ctx, icur))) {
328 complete_add_item(ctx, p->rid, res, frequency_threshold,
329 items_freq, items_boost,
330 &item_freq, &item_boost);
331 }
332 grn_ii_cursor_close(ctx, icur);
333 }
334 }
335 grn_table_cursor_close(ctx, cur);
336 } else {
337 ERR(GRN_UNKNOWN_ERROR, "cannot open cursor for prefix RK search.");
338 }
339 } else {
340 ERR(GRN_UNKNOWN_ERROR, "cannot find index for prefix RK search.");
341 }
342 cooccurrence_search(ctx, items, items_boost, tid, res, COMPLETE,
343 frequency_threshold,
344 conditional_probability_threshold);
345 if (((prefix_search_mode == GRN_SUGGEST_SEARCH_YES) ||
346 (prefix_search_mode == GRN_SUGGEST_SEARCH_AUTO &&
347 !grn_table_size(ctx, res))) &&
348 (cur = grn_table_cursor_open(ctx, items,
349 GRN_TEXT_VALUE(query),
350 GRN_TEXT_LEN(query),
351 NULL, 0, 0, -1, GRN_CURSOR_PREFIX))) {
352 grn_id id;
353 while ((id = grn_table_cursor_next(ctx, cur))) {
354 complete_add_item(ctx, id, res, frequency_threshold,
355 items_freq, items_boost, &item_freq, &item_boost);
356 }
357 grn_table_cursor_close(ctx, cur);
358 }
359 }
360 output(ctx, items, res, tid, sortby, output_columns, offset, limit);
361 grn_obj_close(ctx, res);
362 } else {
363 ERR(GRN_UNKNOWN_ERROR, "cannot create temporary table.");
364 }
365 GRN_OBJ_FIN(ctx, &item_boost);
366 GRN_OBJ_FIN(ctx, &item_freq);
367 }
368
369 static void
correct(grn_ctx * ctx,grn_obj * items,grn_obj * items_boost,grn_obj * query,grn_obj * sortby,grn_obj * output_columns,int offset,int limit,int frequency_threshold,double conditional_probability_threshold,grn_suggest_search_mode similar_search_mode)370 correct(grn_ctx *ctx, grn_obj *items, grn_obj *items_boost,
371 grn_obj *query, grn_obj *sortby,
372 grn_obj *output_columns, int offset, int limit,
373 int frequency_threshold, double conditional_probability_threshold,
374 grn_suggest_search_mode similar_search_mode)
375 {
376 grn_obj *res;
377 grn_obj *items_freq2 = grn_obj_column(ctx, items, CONST_STR_LEN("freq2"));
378 grn_obj item_freq2, item_boost;
379 GRN_INT32_INIT(&item_freq2, 0);
380 GRN_INT32_INIT(&item_boost, 0);
381 if ((res = grn_table_create(ctx, NULL, 0, NULL,
382 GRN_TABLE_HASH_KEY|GRN_OBJ_WITH_SUBREC, items, NULL))) {
383 grn_id tid = grn_table_get(ctx, items, TEXT_VALUE_LEN(query));
384 double max_score;
385 max_score = cooccurrence_search(ctx, items, items_boost, tid, res, CORRECT,
386 frequency_threshold,
387 conditional_probability_threshold);
388 GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_SCORE,
389 ":", "cooccur(%f)", max_score);
390 if (GRN_TEXT_LEN(query) &&
391 ((similar_search_mode == GRN_SUGGEST_SEARCH_YES) ||
392 (similar_search_mode == GRN_SUGGEST_SEARCH_AUTO &&
393 max_score < frequency_threshold))) {
394 grn_obj *key, *index;
395 if ((key = grn_obj_column(ctx, items,
396 GRN_COLUMN_NAME_KEY,
397 GRN_COLUMN_NAME_KEY_LEN))) {
398 if (grn_column_index(ctx, key, GRN_OP_MATCH, &index, 1, NULL)) {
399 grn_select_optarg optarg;
400 memset(&optarg, 0, sizeof(grn_select_optarg));
401 optarg.mode = GRN_OP_SIMILAR;
402 optarg.similarity_threshold = 0;
403 optarg.max_size = 2;
404 grn_ii_select(ctx, (grn_ii *)index, TEXT_VALUE_LEN(query),
405 (grn_hash *)res, GRN_OP_OR, &optarg);
406 grn_obj_unlink(ctx, index);
407 GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_SIZE,
408 ":", "similar(%d)", grn_table_size(ctx, res));
409 {
410 grn_hash_cursor *hc = grn_hash_cursor_open(ctx, (grn_hash *)res, NULL,
411 0, NULL, 0, 0, -1, 0);
412 if (hc) {
413 while (grn_hash_cursor_next(ctx, hc)) {
414 void *key, *value;
415 if (grn_hash_cursor_get_key_value(ctx, hc, &key, NULL, &value)) {
416 grn_id *rp;
417 rp = key;
418 GRN_BULK_REWIND(&item_freq2);
419 GRN_BULK_REWIND(&item_boost);
420 grn_obj_get_value(ctx, items_freq2, *rp, &item_freq2);
421 grn_obj_get_value(ctx, items_boost, *rp, &item_boost);
422 if (GRN_INT32_VALUE(&item_boost) >= 0) {
423 double score;
424 grn_rset_recinfo *ri;
425 score = 1 +
426 (GRN_INT32_VALUE(&item_freq2) >> 4) +
427 GRN_INT32_VALUE(&item_boost);
428 ri = value;
429 ri->score += score;
430 if (score >= frequency_threshold) { continue; }
431 }
432 /* score < frequency_threshold || item_boost < 0 */
433 grn_hash_cursor_delete(ctx, hc, NULL);
434 }
435 }
436 grn_hash_cursor_close(ctx, hc);
437 }
438 }
439 GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_SIZE,
440 ":", "filter(%d)", grn_table_size(ctx, res));
441 {
442 /* exec _score -= edit_distance(_key, "query string") for all records */
443 grn_obj *var;
444 grn_obj *expr;
445
446 GRN_EXPR_CREATE_FOR_QUERY(ctx, res, expr, var);
447 if (expr) {
448 grn_table_cursor *tc;
449 grn_obj *score = grn_obj_column(ctx, res,
450 GRN_COLUMN_NAME_SCORE,
451 GRN_COLUMN_NAME_SCORE_LEN);
452 grn_obj *key = grn_obj_column(ctx, res,
453 GRN_COLUMN_NAME_KEY,
454 GRN_COLUMN_NAME_KEY_LEN);
455 grn_expr_append_obj(ctx, expr,
456 score,
457 GRN_OP_GET_VALUE, 1);
458 grn_expr_append_obj(ctx, expr,
459 grn_ctx_get(ctx, CONST_STR_LEN("edit_distance")),
460 GRN_OP_PUSH, 1);
461 grn_expr_append_obj(ctx, expr,
462 key,
463 GRN_OP_GET_VALUE, 1);
464 grn_expr_append_const(ctx, expr, query, GRN_OP_PUSH, 1);
465 grn_expr_append_op(ctx, expr, GRN_OP_CALL, 2);
466 grn_expr_append_op(ctx, expr, GRN_OP_MINUS_ASSIGN, 2);
467
468 if ((tc = grn_table_cursor_open(ctx, res, NULL, 0, NULL, 0, 0, -1, 0))) {
469 grn_id id;
470 grn_obj score_value;
471 GRN_FLOAT_INIT(&score_value, 0);
472 while ((id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL) {
473 GRN_RECORD_SET(ctx, var, id);
474 grn_expr_exec(ctx, expr, 0);
475 GRN_BULK_REWIND(&score_value);
476 grn_obj_get_value(ctx, score, id, &score_value);
477 if (GRN_FLOAT_VALUE(&score_value) < frequency_threshold) {
478 grn_table_cursor_delete(ctx, tc);
479 }
480 }
481 grn_obj_unlink(ctx, &score_value);
482 grn_table_cursor_close(ctx, tc);
483 }
484 grn_obj_unlink(ctx, score);
485 grn_obj_unlink(ctx, key);
486 grn_obj_unlink(ctx, expr);
487 } else {
488 ERR(GRN_UNKNOWN_ERROR,
489 "error on building expr. for calicurating edit distance");
490 }
491 }
492 }
493 grn_obj_unlink(ctx, key);
494 }
495 }
496 output(ctx, items, res, tid, sortby, output_columns, offset, limit);
497 grn_obj_close(ctx, res);
498 } else {
499 ERR(GRN_UNKNOWN_ERROR, "cannot create temporary table.");
500 }
501 GRN_OBJ_FIN(ctx, &item_boost);
502 GRN_OBJ_FIN(ctx, &item_freq2);
503 }
504
505 static void
suggest(grn_ctx * ctx,grn_obj * items,grn_obj * items_boost,grn_obj * query,grn_obj * sortby,grn_obj * output_columns,int offset,int limit,int frequency_threshold,double conditional_probability_threshold)506 suggest(grn_ctx *ctx, grn_obj *items, grn_obj *items_boost,
507 grn_obj *query, grn_obj *sortby,
508 grn_obj *output_columns, int offset, int limit,
509 int frequency_threshold, double conditional_probability_threshold)
510 {
511 grn_obj *res;
512 if ((res = grn_table_create(ctx, NULL, 0, NULL,
513 GRN_TABLE_HASH_KEY|GRN_OBJ_WITH_SUBREC, items, NULL))) {
514 grn_id tid = grn_table_get(ctx, items, TEXT_VALUE_LEN(query));
515 cooccurrence_search(ctx, items, items_boost, tid, res, SUGGEST,
516 frequency_threshold, conditional_probability_threshold);
517 output(ctx, items, res, tid, sortby, output_columns, offset, limit);
518 grn_obj_close(ctx, res);
519 } else {
520 ERR(GRN_UNKNOWN_ERROR, "cannot create temporary table.");
521 }
522 }
523
524 static grn_suggest_search_mode
parse_search_mode(grn_ctx * ctx,grn_obj * mode_text)525 parse_search_mode(grn_ctx *ctx, grn_obj *mode_text)
526 {
527 grn_suggest_search_mode mode;
528 int mode_length;
529
530 mode_length = GRN_TEXT_LEN(mode_text);
531 if (mode_length == 3 &&
532 grn_strncasecmp("yes", GRN_TEXT_VALUE(mode_text), 3) == 0) {
533 mode = GRN_SUGGEST_SEARCH_YES;
534 } else if (mode_length == 2 &&
535 grn_strncasecmp("no", GRN_TEXT_VALUE(mode_text), 2) == 0) {
536 mode = GRN_SUGGEST_SEARCH_NO;
537 } else {
538 mode = GRN_SUGGEST_SEARCH_AUTO;
539 }
540
541 return mode;
542 }
543
544 static grn_obj *
command_suggest(grn_ctx * ctx,int nargs,grn_obj ** args,grn_user_data * user_data)545 command_suggest(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
546 {
547 grn_obj *items, *col, *items_boost;
548 int types;
549 int offset = 0;
550 int limit = DEFAULT_LIMIT;
551 int frequency_threshold = DEFAULT_FREQUENCY_THRESHOLD;
552 double conditional_probability_threshold =
553 DEFAULT_CONDITIONAL_PROBABILITY_THRESHOLD;
554 grn_suggest_search_mode prefix_search_mode;
555 grn_suggest_search_mode similar_search_mode;
556
557 types = grn_parse_suggest_types(VAR(0));
558 if (GRN_TEXT_LEN(VAR(6)) > 0) {
559 offset = grn_atoi(GRN_TEXT_VALUE(VAR(6)), GRN_BULK_CURR(VAR(6)), NULL);
560 }
561 if (GRN_TEXT_LEN(VAR(7)) > 0) {
562 limit = grn_atoi(GRN_TEXT_VALUE(VAR(7)), GRN_BULK_CURR(VAR(7)), NULL);
563 }
564 if (GRN_TEXT_LEN(VAR(8)) > 0) {
565 frequency_threshold = grn_atoi(GRN_TEXT_VALUE(VAR(8)), GRN_BULK_CURR(VAR(8)), NULL);
566 }
567 if (GRN_TEXT_LEN(VAR(9)) > 0) {
568 GRN_TEXT_PUTC(ctx, VAR(9), '\0');
569 conditional_probability_threshold = strtod(GRN_TEXT_VALUE(VAR(9)), NULL);
570 }
571
572 prefix_search_mode = parse_search_mode(ctx, VAR(10));
573 similar_search_mode = parse_search_mode(ctx, VAR(11));
574
575 if ((items = grn_ctx_get(ctx, TEXT_VALUE_LEN(VAR(1))))) {
576 if ((items_boost = grn_obj_column(ctx, items, CONST_STR_LEN("boost")))) {
577 int n_outputs = 0;
578 if (types & COMPLETE) {
579 n_outputs++;
580 }
581 if (types & CORRECT) {
582 n_outputs++;
583 }
584 if (types & SUGGEST) {
585 n_outputs++;
586 }
587 GRN_OUTPUT_MAP_OPEN("RESULT_SET", n_outputs);
588
589 if (types & COMPLETE) {
590 if ((col = grn_obj_column(ctx, items, TEXT_VALUE_LEN(VAR(2))))) {
591 GRN_OUTPUT_CSTR("complete");
592 complete(ctx, items, items_boost, col, VAR(3), VAR(4),
593 VAR(5), offset, limit,
594 frequency_threshold, conditional_probability_threshold,
595 prefix_search_mode);
596 } else {
597 ERR(GRN_INVALID_ARGUMENT, "invalid column.");
598 }
599 }
600 if (types & CORRECT) {
601 GRN_OUTPUT_CSTR("correct");
602 correct(ctx, items, items_boost, VAR(3), VAR(4),
603 VAR(5), offset, limit,
604 frequency_threshold, conditional_probability_threshold,
605 similar_search_mode);
606 }
607 if (types & SUGGEST) {
608 GRN_OUTPUT_CSTR("suggest");
609 suggest(ctx, items, items_boost, VAR(3), VAR(4),
610 VAR(5), offset, limit,
611 frequency_threshold, conditional_probability_threshold);
612 }
613 GRN_OUTPUT_MAP_CLOSE();
614 } else {
615 ERR(GRN_INVALID_ARGUMENT, "nonexistent column: <%.*s.boost>",
616 (int)GRN_TEXT_LEN(VAR(1)), GRN_TEXT_VALUE(VAR(1)));
617 }
618 grn_obj_unlink(ctx, items);
619 } else {
620 ERR(GRN_INVALID_ARGUMENT, "nonexistent table: <%.*s>",
621 (int)GRN_TEXT_LEN(VAR(1)), GRN_TEXT_VALUE(VAR(1)));
622 }
623 return NULL;
624 }
625
626 static void
learner_init_values(grn_ctx * ctx,grn_suggest_learner * learner)627 learner_init_values(grn_ctx *ctx, grn_suggest_learner *learner)
628 {
629 learner->post_event_id = GRN_RECORD_VALUE(learner->post_event);
630 learner->post_type_id = GRN_RECORD_VALUE(learner->post_type);
631 learner->post_item_id = GRN_RECORD_VALUE(learner->post_item);
632 learner->seq_id = GRN_RECORD_VALUE(learner->seq);
633 learner->post_time_value = GRN_TIME_VALUE(learner->post_time);
634 }
635
636 static void
learner_init(grn_ctx * ctx,grn_suggest_learner * learner,grn_obj * post_event,grn_obj * post_type,grn_obj * post_item,grn_obj * seq,grn_obj * post_time,grn_obj * pairs)637 learner_init(grn_ctx *ctx, grn_suggest_learner *learner,
638 grn_obj *post_event, grn_obj *post_type, grn_obj *post_item,
639 grn_obj *seq, grn_obj *post_time, grn_obj *pairs)
640 {
641 learner->post_event = post_event;
642 learner->post_type = post_type;
643 learner->post_item = post_item;
644 learner->seq = seq;
645 learner->post_time = post_time;
646 learner->pairs = pairs;
647
648 learner->learn_distance_in_seconds = 0;
649
650 learner_init_values(ctx, learner);
651 }
652
653 static void
learner_init_columns(grn_ctx * ctx,grn_suggest_learner * learner)654 learner_init_columns(grn_ctx *ctx, grn_suggest_learner *learner)
655 {
656 grn_id events_id, event_types_id;
657 grn_obj *seqs, *events, *post_item, *items, *pairs;
658
659 learner->seqs = seqs = grn_ctx_at(ctx, GRN_OBJ_GET_DOMAIN(learner->seq));
660 learner->seqs_events = grn_obj_column(ctx, seqs, CONST_STR_LEN("events"));
661
662 events_id = grn_obj_get_range(ctx, learner->seqs_events);
663 learner->events = events = grn_ctx_at(ctx, events_id);
664 learner->events_item = grn_obj_column(ctx, events, CONST_STR_LEN("item"));
665 learner->events_type = grn_obj_column(ctx, events, CONST_STR_LEN("type"));
666 learner->events_time = grn_obj_column(ctx, events, CONST_STR_LEN("time"));
667
668 event_types_id = grn_obj_get_range(ctx, learner->events_type);
669 learner->event_types = grn_obj_column(ctx, events, CONST_STR_LEN("time"));
670
671 post_item = learner->post_item;
672 learner->items = items = grn_ctx_at(ctx, GRN_OBJ_GET_DOMAIN(post_item));
673 learner->items_freq = grn_obj_column(ctx, items, CONST_STR_LEN("freq"));
674 learner->items_freq2 = grn_obj_column(ctx, items, CONST_STR_LEN("freq2"));
675 learner->items_last = grn_obj_column(ctx, items, CONST_STR_LEN("last"));
676
677 pairs = learner->pairs;
678 learner->pairs_pre = grn_obj_column(ctx, pairs, CONST_STR_LEN("pre"));
679 learner->pairs_post = grn_obj_column(ctx, pairs, CONST_STR_LEN("post"));
680 learner->pairs_freq0 = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq0"));
681 learner->pairs_freq1 = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq1"));
682 learner->pairs_freq2 = grn_obj_column(ctx, pairs, CONST_STR_LEN("freq2"));
683 }
684
685 static void
learner_fin_columns(grn_ctx * ctx,grn_suggest_learner * learner)686 learner_fin_columns(grn_ctx *ctx, grn_suggest_learner *learner)
687 {
688 grn_obj_unlink(ctx, learner->seqs);
689 grn_obj_unlink(ctx, learner->seqs_events);
690
691 grn_obj_unlink(ctx, learner->events);
692 grn_obj_unlink(ctx, learner->events_item);
693 grn_obj_unlink(ctx, learner->events_type);
694 grn_obj_unlink(ctx, learner->events_time);
695
696 grn_obj_unlink(ctx, learner->event_types);
697
698 grn_obj_unlink(ctx, learner->items);
699 grn_obj_unlink(ctx, learner->items_freq);
700 grn_obj_unlink(ctx, learner->items_freq2);
701 grn_obj_unlink(ctx, learner->items_last);
702
703 grn_obj_unlink(ctx, learner->pairs_pre);
704 grn_obj_unlink(ctx, learner->pairs_post);
705 grn_obj_unlink(ctx, learner->pairs_freq0);
706 grn_obj_unlink(ctx, learner->pairs_freq1);
707 grn_obj_unlink(ctx, learner->pairs_freq2);
708 }
709
710 static void
learner_init_weight(grn_ctx * ctx,grn_suggest_learner * learner)711 learner_init_weight(grn_ctx *ctx, grn_suggest_learner *learner)
712 {
713 grn_obj *weight_column = NULL;
714 unsigned int weight = 1;
715
716 if (learner->configuration) {
717 weight_column = grn_obj_column(ctx,
718 learner->configuration,
719 CONST_STR_LEN("weight"));
720 }
721 if (weight_column) {
722 grn_id id;
723 id = grn_table_get(ctx, learner->configuration,
724 GRN_TEXT_VALUE(&(learner->dataset_name)),
725 GRN_TEXT_LEN(&(learner->dataset_name)));
726 if (id != GRN_ID_NIL) {
727 grn_obj weight_value;
728 GRN_UINT32_INIT(&weight_value, 0);
729 grn_obj_get_value(ctx, weight_column, id, &weight_value);
730 weight = GRN_UINT32_VALUE(&weight_value);
731 GRN_OBJ_FIN(ctx, &weight_value);
732 }
733 grn_obj_unlink(ctx, weight_column);
734 }
735
736 GRN_UINT32_INIT(&(learner->weight), 0);
737 GRN_UINT32_SET(ctx, &(learner->weight), weight);
738 }
739
740 static void
learner_init_dataset_name(grn_ctx * ctx,grn_suggest_learner * learner)741 learner_init_dataset_name(grn_ctx *ctx, grn_suggest_learner *learner)
742 {
743 char events_name[GRN_TABLE_MAX_KEY_SIZE];
744 unsigned int events_name_size;
745 unsigned int events_name_prefix_size;
746
747 events_name_size = grn_obj_name(ctx, learner->events,
748 events_name, GRN_TABLE_MAX_KEY_SIZE);
749 GRN_TEXT_INIT(&(learner->dataset_name), 0);
750 events_name_prefix_size = strlen("event_");
751 if (events_name_size > events_name_prefix_size) {
752 GRN_TEXT_PUT(ctx,
753 &(learner->dataset_name),
754 events_name + events_name_prefix_size,
755 events_name_size - events_name_prefix_size);
756 }
757 }
758
759 static void
learner_fin_dataset_name(grn_ctx * ctx,grn_suggest_learner * learner)760 learner_fin_dataset_name(grn_ctx *ctx, grn_suggest_learner *learner)
761 {
762 GRN_OBJ_FIN(ctx, &(learner->dataset_name));
763 }
764
765 static void
learner_init_configuration(grn_ctx * ctx,grn_suggest_learner * learner)766 learner_init_configuration(grn_ctx *ctx, grn_suggest_learner *learner)
767 {
768 learner->configuration = grn_ctx_get(ctx, "configuration", -1);
769 }
770
771 static void
learner_fin_configuration(grn_ctx * ctx,grn_suggest_learner * learner)772 learner_fin_configuration(grn_ctx *ctx, grn_suggest_learner *learner)
773 {
774 if (learner->configuration) {
775 grn_obj_unlink(ctx, learner->configuration);
776 }
777 }
778
779 static void
learner_init_buffers(grn_ctx * ctx,grn_suggest_learner * learner)780 learner_init_buffers(grn_ctx *ctx, grn_suggest_learner *learner)
781 {
782 learner_init_weight(ctx, learner);
783 GRN_RECORD_INIT(&(learner->pre_events), 0, grn_obj_id(ctx, learner->events));
784 }
785
786 static void
learner_fin_buffers(grn_ctx * ctx,grn_suggest_learner * learner)787 learner_fin_buffers(grn_ctx *ctx, grn_suggest_learner *learner)
788 {
789 grn_obj_unlink(ctx, &(learner->weight));
790 grn_obj_unlink(ctx, &(learner->pre_events));
791 }
792
793 static void
learner_init_submit_learn(grn_ctx * ctx,grn_suggest_learner * learner)794 learner_init_submit_learn(grn_ctx *ctx, grn_suggest_learner *learner)
795 {
796 grn_id items_id;
797
798 learner->key_prefix = ((uint64_t)learner->post_item_id) << 32;
799
800 items_id = grn_obj_get_range(ctx, learner->events_item);
801 GRN_RECORD_INIT(&(learner->pre_item), 0, items_id);
802
803 grn_obj_get_value(ctx, learner->seqs_events, learner->seq_id,
804 &(learner->pre_events));
805 }
806
807 static void
learner_fin_submit_learn(grn_ctx * ctx,grn_suggest_learner * learner)808 learner_fin_submit_learn(grn_ctx *ctx, grn_suggest_learner *learner)
809 {
810 grn_obj_unlink(ctx, &(learner->pre_item));
811 GRN_BULK_REWIND(&(learner->pre_events));
812 }
813
814 static grn_bool
learner_is_valid_input(grn_ctx * ctx,grn_suggest_learner * learner)815 learner_is_valid_input(grn_ctx *ctx, grn_suggest_learner *learner)
816 {
817 return learner->post_event_id && learner->post_item_id && learner->seq_id;
818 }
819
820 static void
learner_increment(grn_ctx * ctx,grn_suggest_learner * learner,grn_obj * column,grn_id record_id)821 learner_increment(grn_ctx *ctx, grn_suggest_learner *learner,
822 grn_obj *column, grn_id record_id)
823 {
824 grn_obj_set_value(ctx, column, record_id, &(learner->weight), GRN_OBJ_INCR);
825 }
826
827 static void
learner_increment_item_freq(grn_ctx * ctx,grn_suggest_learner * learner,grn_obj * column)828 learner_increment_item_freq(grn_ctx *ctx, grn_suggest_learner *learner,
829 grn_obj *column)
830 {
831 learner_increment(ctx, learner, column, learner->post_item_id);
832 }
833
834 static void
learner_set_last_post_time(grn_ctx * ctx,grn_suggest_learner * learner)835 learner_set_last_post_time(grn_ctx *ctx, grn_suggest_learner *learner)
836 {
837 grn_obj_set_value(ctx, learner->items_last, learner->post_item_id,
838 learner->post_time, GRN_OBJ_SET);
839 }
840
841 static void
learner_learn_for_complete_and_correcnt(grn_ctx * ctx,grn_suggest_learner * learner)842 learner_learn_for_complete_and_correcnt(grn_ctx *ctx,
843 grn_suggest_learner *learner)
844 {
845 grn_obj *pre_item, *post_item, *pre_events;
846 grn_obj pre_type, pre_time;
847 grn_id *ep, *es;
848 uint64_t key;
849 int64_t post_time_value;
850
851 pre_item = &(learner->pre_item);
852 post_item = learner->post_item;
853 pre_events = &(learner->pre_events);
854 post_time_value = learner->post_time_value;
855 GRN_RECORD_INIT(&pre_type, 0, grn_obj_get_range(ctx, learner->events_type));
856 GRN_TIME_INIT(&pre_time, 0);
857 ep = (grn_id *)GRN_BULK_CURR(pre_events);
858 es = (grn_id *)GRN_BULK_HEAD(pre_events);
859 while (es < ep--) {
860 grn_id pair_id;
861 int added;
862 int64_t learn_distance;
863
864 GRN_BULK_REWIND(&pre_type);
865 GRN_BULK_REWIND(&pre_time);
866 GRN_BULK_REWIND(pre_item);
867 grn_obj_get_value(ctx, learner->events_type, *ep, &pre_type);
868 grn_obj_get_value(ctx, learner->events_time, *ep, &pre_time);
869 grn_obj_get_value(ctx, learner->events_item, *ep, pre_item);
870 learn_distance = post_time_value - GRN_TIME_VALUE(&pre_time);
871 if (learn_distance >= MIN_LEARN_DISTANCE) {
872 learner->learn_distance_in_seconds =
873 (int)(learn_distance / GRN_TIME_USEC_PER_SEC);
874 break;
875 }
876 key = learner->key_prefix + GRN_RECORD_VALUE(pre_item);
877 pair_id = grn_table_add(ctx, learner->pairs, &key, sizeof(uint64_t),
878 &added);
879 if (added) {
880 grn_obj_set_value(ctx, learner->pairs_pre, pair_id, pre_item,
881 GRN_OBJ_SET);
882 grn_obj_set_value(ctx, learner->pairs_post, pair_id, post_item,
883 GRN_OBJ_SET);
884 }
885 if (GRN_RECORD_VALUE(&pre_type)) {
886 learner_increment(ctx, learner, learner->pairs_freq1, pair_id);
887 break;
888 } else {
889 learner_increment(ctx, learner, learner->pairs_freq0, pair_id);
890 }
891 }
892 GRN_OBJ_FIN(ctx, &pre_type);
893 GRN_OBJ_FIN(ctx, &pre_time);
894 }
895
896 static void
learner_learn_for_suggest(grn_ctx * ctx,grn_suggest_learner * learner)897 learner_learn_for_suggest(grn_ctx *ctx, grn_suggest_learner *learner)
898 {
899 char keybuf[GRN_TABLE_MAX_KEY_SIZE];
900 int keylen = grn_table_get_key(ctx, learner->items, learner->post_item_id,
901 keybuf, GRN_TABLE_MAX_KEY_SIZE);
902 unsigned int token_flags = 0;
903 grn_token_cursor *token_cursor =
904 grn_token_cursor_open(ctx, learner->items, keybuf, keylen,
905 GRN_TOKEN_ADD, token_flags);
906 if (token_cursor) {
907 grn_id tid;
908 grn_obj *pre_item = &(learner->pre_item);
909 grn_obj *post_item = learner->post_item;
910 grn_hash *token_ids = NULL;
911 while ((tid = grn_token_cursor_next(ctx, token_cursor)) && tid != learner->post_item_id) {
912 uint64_t key;
913 int added;
914 grn_id pair_id;
915 key = learner->key_prefix + tid;
916 pair_id = grn_table_add(ctx, learner->pairs, &key, sizeof(uint64_t),
917 &added);
918 if (added) {
919 GRN_RECORD_SET(ctx, pre_item, tid);
920 grn_obj_set_value(ctx, learner->pairs_pre, pair_id,
921 pre_item, GRN_OBJ_SET);
922 grn_obj_set_value(ctx, learner->pairs_post, pair_id,
923 post_item, GRN_OBJ_SET);
924 }
925 if (!token_ids) {
926 token_ids = grn_hash_create(ctx, NULL, sizeof(grn_id), 0,
927 GRN_OBJ_TABLE_HASH_KEY|GRN_HASH_TINY);
928 }
929 if (token_ids) {
930 int token_added;
931 grn_hash_add(ctx, token_ids, &tid, sizeof(grn_id), NULL, &token_added);
932 if (token_added) {
933 learner_increment(ctx, learner, learner->pairs_freq2, pair_id);
934 }
935 }
936 }
937 if (token_ids) {
938 grn_hash_close(ctx, token_ids);
939 }
940 grn_token_cursor_close(ctx, token_cursor);
941 }
942 }
943
944 static void
learner_append_post_event(grn_ctx * ctx,grn_suggest_learner * learner)945 learner_append_post_event(grn_ctx *ctx, grn_suggest_learner *learner)
946 {
947 GRN_RECORD_SET(ctx, &(learner->pre_events), learner->post_event_id);
948 grn_obj_set_value(ctx, learner->seqs_events, learner->seq_id,
949 &(learner->pre_events), GRN_OBJ_APPEND);
950 }
951
952 static void
learner_learn(grn_ctx * ctx,grn_suggest_learner * learner)953 learner_learn(grn_ctx *ctx, grn_suggest_learner *learner)
954 {
955 if (learner_is_valid_input(ctx, learner)) {
956 learner_init_columns(ctx, learner);
957 learner_init_dataset_name(ctx, learner);
958 learner_init_configuration(ctx, learner);
959 learner_init_buffers(ctx, learner);
960 learner_increment_item_freq(ctx, learner, learner->items_freq);
961 learner_set_last_post_time(ctx, learner);
962 if (learner->post_type_id) {
963 learner_init_submit_learn(ctx, learner);
964 learner_increment_item_freq(ctx, learner, learner->items_freq2);
965 learner_learn_for_complete_and_correcnt(ctx, learner);
966 learner_learn_for_suggest(ctx, learner);
967 learner_fin_submit_learn(ctx, learner);
968 }
969 learner_append_post_event(ctx, learner);
970 learner_fin_buffers(ctx, learner);
971 learner_fin_configuration(ctx, learner);
972 learner_fin_dataset_name(ctx, learner);
973 learner_fin_columns(ctx, learner);
974 }
975 }
976
977 static grn_obj *
func_suggest_preparer(grn_ctx * ctx,int nargs,grn_obj ** args,grn_user_data * user_data)978 func_suggest_preparer(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
979 {
980 int learn_distance_in_seconds = 0;
981 grn_obj *obj;
982 if (nargs == 6) {
983 grn_obj *post_event = args[0];
984 grn_obj *post_type = args[1];
985 grn_obj *post_item = args[2];
986 grn_obj *seq = args[3];
987 grn_obj *post_time = args[4];
988 grn_obj *pairs = args[5];
989 grn_suggest_learner learner;
990 learner_init(ctx, &learner,
991 post_event, post_type, post_item, seq, post_time, pairs);
992 learner_learn(ctx, &learner);
993 learn_distance_in_seconds = learner.learn_distance_in_seconds;
994 }
995 if ((obj = GRN_PROC_ALLOC(GRN_DB_UINT32, 0))) {
996 GRN_UINT32_SET(ctx, obj, learn_distance_in_seconds);
997 }
998 return obj;
999 }
1000
1001 grn_rc
GRN_PLUGIN_INIT(grn_ctx * ctx)1002 GRN_PLUGIN_INIT(grn_ctx *ctx)
1003 {
1004 return GRN_SUCCESS;
1005 }
1006
1007 grn_rc
GRN_PLUGIN_REGISTER(grn_ctx * ctx)1008 GRN_PLUGIN_REGISTER(grn_ctx *ctx)
1009 {
1010 grn_expr_var vars[12];
1011
1012 grn_plugin_expr_var_init(ctx, &vars[0], "types", -1);
1013 grn_plugin_expr_var_init(ctx, &vars[1], "table", -1);
1014 grn_plugin_expr_var_init(ctx, &vars[2], "column", -1);
1015 grn_plugin_expr_var_init(ctx, &vars[3], "query", -1);
1016 grn_plugin_expr_var_init(ctx, &vars[4], "sortby", -1);
1017 grn_plugin_expr_var_init(ctx, &vars[5], "output_columns", -1);
1018 grn_plugin_expr_var_init(ctx, &vars[6], "offset", -1);
1019 grn_plugin_expr_var_init(ctx, &vars[7], "limit", -1);
1020 grn_plugin_expr_var_init(ctx, &vars[8], "frequency_threshold", -1);
1021 grn_plugin_expr_var_init(ctx, &vars[9], "conditional_probability_threshold", -1);
1022 grn_plugin_expr_var_init(ctx, &vars[10], "prefix_search", -1);
1023 grn_plugin_expr_var_init(ctx, &vars[11], "similar_search", -1);
1024 grn_plugin_command_create(ctx, "suggest", -1, command_suggest, 12, vars);
1025
1026 grn_proc_create(ctx, CONST_STR_LEN("suggest_preparer"), GRN_PROC_FUNCTION,
1027 func_suggest_preparer, NULL, NULL, 0, NULL);
1028 return ctx->rc;
1029 }
1030
1031 grn_rc
GRN_PLUGIN_FIN(grn_ctx * ctx)1032 GRN_PLUGIN_FIN(grn_ctx *ctx)
1033 {
1034 return GRN_SUCCESS;
1035 }
1036