1/* -*- c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 Copyright(C) 2015-2017 Kouhei Sutou <kou@clear-code.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#include <mrn_mysql.h>
21#include <mrn_mysql_compat.h>
22#include <mrn_err.h>
23#include <mrn_encoding.hpp>
24#include <mrn_windows.hpp>
25#include <mrn_table.hpp>
26#include <mrn_macro.hpp>
27#include <mrn_database_manager.hpp>
28#include <mrn_context_pool.hpp>
29#include <mrn_variables.hpp>
30#include <mrn_query_parser.hpp>
31#include <mrn_current_thread.hpp>
32
33MRN_BEGIN_DECLS
34
35extern mrn::DatabaseManager *mrn_db_manager;
36extern mrn::ContextPool *mrn_context_pool;
37
38typedef struct st_mrn_snippet_html_info
39{
40 grn_ctx *ctx;
41 grn_obj *db;
42 bool use_shared_db;
43 grn_obj *snippet;
44 String result_str;
45 struct {
46 bool used;
47 grn_obj *table;
48 grn_obj *default_column;
49 } query_mode;
50} mrn_snippet_html_info;
51
52static my_bool mrn_snippet_html_prepare(mrn_snippet_html_info *info,
53 UDF_ARGS *args,
54 char *message,
55 grn_obj **snippet)
56{
57 MRN_DBUG_ENTER_FUNCTION();
58
59 grn_ctx *ctx = info->ctx;
60 int flags = GRN_SNIP_SKIP_LEADING_SPACES;
61 unsigned int width = 200;
62 unsigned int max_n_results = 3;
63 const char *open_tag = "<span class=\"keyword\">";
64 const char *close_tag = "</span>";
65 grn_snip_mapping *mapping = GRN_SNIP_MAPPING_HTML_ESCAPE;
66 grn_obj *expr = NULL;
67 String *result_str = &(info->result_str);
68
69 *snippet = NULL;
70
71 mrn::encoding::set_raw(ctx, system_charset_info);
72 if (!(system_charset_info->state & (MY_CS_BINSORT | MY_CS_CSSORT))) {
73 flags |= GRN_SNIP_NORMALIZE;
74 }
75
76 *snippet = grn_snip_open(ctx, flags,
77 width, max_n_results,
78 open_tag, strlen(open_tag),
79 close_tag, strlen(close_tag),
80 mapping);
81 if (ctx->rc != GRN_SUCCESS) {
82 if (message) {
83 snprintf(message, MYSQL_ERRMSG_SIZE,
84 "mroonga_snippet_html(): failed to open grn_snip: <%s>",
85 ctx->errbuf);
86 }
87 goto error;
88 }
89
90 if (info->query_mode.used) {
91 if (!info->query_mode.table) {
92 grn_obj *short_text;
93 short_text = grn_ctx_at(info->ctx, GRN_DB_SHORT_TEXT);
94 info->query_mode.table = grn_table_create(info->ctx,
95 NULL, 0, NULL,
96 GRN_TABLE_HASH_KEY,
97 short_text,
98 NULL);
99 }
100 if (!info->query_mode.default_column) {
101 info->query_mode.default_column =
102 grn_obj_column(info->ctx,
103 info->query_mode.table,
104 GRN_COLUMN_NAME_KEY,
105 GRN_COLUMN_NAME_KEY_LEN);
106 }
107
108 grn_obj *record = NULL;
109 GRN_EXPR_CREATE_FOR_QUERY(info->ctx, info->query_mode.table, expr, record);
110 if (!expr) {
111 if (message) {
112 snprintf(message, MYSQL_ERRMSG_SIZE,
113 "mroonga_snippet_html(): "
114 "failed to create expression: <%s>",
115 ctx->errbuf);
116 }
117 goto error;
118 }
119
120 mrn::QueryParser query_parser(info->ctx,
121 current_thd,
122 expr,
123 info->query_mode.default_column,
124 0,
125 NULL);
126 grn_rc rc = query_parser.parse(args->args[1], args->lengths[1]);
127 if (rc != GRN_SUCCESS) {
128 if (message) {
129 snprintf(message, MYSQL_ERRMSG_SIZE,
130 "mroonga_snippet_html(): "
131 "failed to parse query: <%s>",
132 ctx->errbuf);
133 }
134 goto error;
135 }
136
137 rc = grn_expr_snip_add_conditions(info->ctx,
138 expr,
139 *snippet,
140 0,
141 NULL, NULL,
142 NULL, NULL);
143 if (rc != GRN_SUCCESS) {
144 if (message) {
145 snprintf(message, MYSQL_ERRMSG_SIZE,
146 "mroonga_snippet_html(): "
147 "failed to add conditions: <%s>",
148 ctx->errbuf);
149 }
150 goto error;
151 }
152 } else {
153 unsigned int i;
154 for (i = 1; i < args->arg_count; ++i) {
155 if (!args->args[i]) {
156 continue;
157 }
158 grn_rc rc = grn_snip_add_cond(ctx, *snippet,
159 args->args[i], args->lengths[i],
160 NULL, 0,
161 NULL, 0);
162 if (rc != GRN_SUCCESS) {
163 if (message) {
164 snprintf(message, MYSQL_ERRMSG_SIZE,
165 "mroonga_snippet_html(): "
166 "failed to add a condition to grn_snip: <%s>",
167 ctx->errbuf);
168 }
169 goto error;
170 }
171 }
172 }
173
174 result_str->set_charset(system_charset_info);
175 DBUG_RETURN(FALSE);
176
177error:
178 if (expr) {
179 grn_obj_close(ctx, expr);
180 }
181 if (*snippet) {
182 grn_obj_close(ctx, *snippet);
183 }
184 DBUG_RETURN(TRUE);
185}
186
187MRN_API my_bool mroonga_snippet_html_init(UDF_INIT *init,
188 UDF_ARGS *args,
189 char *message)
190{
191 MRN_DBUG_ENTER_FUNCTION();
192
193 mrn_snippet_html_info *info = NULL;
194
195 init->ptr = NULL;
196
197 if (args->arg_count < 1) {
198 snprintf(message, MYSQL_ERRMSG_SIZE,
199 "mroonga_snippet_html(): wrong number of arguments: %u for 1+",
200 args->arg_count);
201 goto error;
202 }
203
204
205 for (unsigned int i = 0; i < args->arg_count; ++i) {
206 switch (args->arg_type[i]) {
207 case STRING_RESULT:
208 /* OK */
209 break;
210 case REAL_RESULT:
211 snprintf(message, MYSQL_ERRMSG_SIZE,
212 "mroonga_snippet_html(): all arguments must be string: "
213 "<%u>=<%g>",
214 i, *((double *)(args->args[i])));
215 goto error;
216 break;
217 case INT_RESULT:
218 snprintf(message, MYSQL_ERRMSG_SIZE,
219 "mroonga_snippet_html(): all arguments must be string: "
220 "<%u>=<%lld>",
221 i, *((longlong *)(args->args[i])));
222 goto error;
223 break;
224 default:
225 snprintf(message, MYSQL_ERRMSG_SIZE,
226 "mroonga_snippet_html(): all arguments must be string: <%u>",
227 i);
228 goto error;
229 break;
230 }
231 }
232
233 init->maybe_null = 1;
234
235 info = (mrn_snippet_html_info *)mrn_my_malloc(sizeof(mrn_snippet_html_info),
236 MYF(MY_WME | MY_ZEROFILL));
237 if (!info) {
238 snprintf(message, MYSQL_ERRMSG_SIZE,
239 "mroonga_snippet_html(): failed to allocate memory");
240 goto error;
241 }
242
243 info->ctx = mrn_context_pool->pull();
244 {
245 const char *current_db_path = MRN_THD_DB_PATH(current_thd);
246 const char *action;
247 if (current_db_path) {
248 action = "open database";
249 mrn::Database *db;
250 int error = mrn_db_manager->open(current_db_path, &db);
251 if (error == 0) {
252 info->db = db->get();
253 grn_ctx_use(info->ctx, info->db);
254 info->use_shared_db = true;
255 }
256 } else {
257 action = "create anonymous database";
258 info->db = grn_db_create(info->ctx, NULL, NULL);
259 info->use_shared_db = false;
260 }
261 if (!info->db) {
262 sprintf(message,
263 "mroonga_snippet_html(): failed to %s: %s",
264 action,
265 info->ctx->errbuf);
266 goto error;
267 }
268 }
269
270 info->query_mode.used = FALSE;
271
272 if (args->arg_count == 2 &&
273 args->attribute_lengths[1] == strlen("query") &&
274 strncmp(args->attributes[1], "query", strlen("query")) == 0) {
275 info->query_mode.used = TRUE;
276 info->query_mode.table = NULL;
277 info->query_mode.default_column = NULL;
278 }
279
280 {
281 bool all_keywords_are_constant = TRUE;
282 for (unsigned int i = 1; i < args->arg_count; ++i) {
283 if (!args->args[i]) {
284 all_keywords_are_constant = FALSE;
285 break;
286 }
287 }
288
289 if (all_keywords_are_constant) {
290 if (mrn_snippet_html_prepare(info, args, message, &(info->snippet))) {
291 goto error;
292 }
293 } else {
294 info->snippet = NULL;
295 }
296 }
297
298 init->ptr = (char *)info;
299
300 DBUG_RETURN(FALSE);
301
302error:
303 if (info) {
304 if (!info->use_shared_db) {
305 grn_obj_close(info->ctx, info->db);
306 }
307 mrn_context_pool->release(info->ctx);
308 my_free(info);
309 }
310 DBUG_RETURN(TRUE);
311}
312
313MRN_API char *mroonga_snippet_html(UDF_INIT *init,
314 UDF_ARGS *args,
315 char *result,
316 unsigned long *length,
317 char *is_null,
318 char *error)
319{
320 MRN_DBUG_ENTER_FUNCTION();
321
322 mrn_snippet_html_info *info =
323 reinterpret_cast<mrn_snippet_html_info *>(init->ptr);
324
325 grn_ctx *ctx = info->ctx;
326 grn_obj *snippet = info->snippet;
327 String *result_str = &(info->result_str);
328
329 if (!args->args[0]) {
330 *is_null = 1;
331 DBUG_RETURN(NULL);
332 }
333
334 if (!snippet) {
335 if (mrn_snippet_html_prepare(info, args, NULL, &snippet)) {
336 goto error;
337 }
338 }
339
340 {
341 char *target = args->args[0];
342 unsigned int target_length = args->lengths[0];
343
344 unsigned int n_results, max_tagged_length;
345 {
346 grn_rc rc = grn_snip_exec(ctx, snippet, target, target_length,
347 &n_results, &max_tagged_length);
348 if (rc != GRN_SUCCESS) {
349 my_printf_error(ER_MRN_ERROR_FROM_GROONGA_NUM,
350 ER_MRN_ERROR_FROM_GROONGA_STR, MYF(0), ctx->errbuf);
351 goto error;
352 }
353 }
354
355 *is_null = 0;
356 result_str->length(0);
357
358 {
359 const char *start_tag = "<div class=\"snippet\">";
360 const char *end_tag = "</div>";
361 size_t start_tag_length = strlen(start_tag);
362 size_t end_tag_length = strlen(end_tag);
363 unsigned int max_length_per_snippet =
364 start_tag_length + end_tag_length + max_tagged_length;
365 if (result_str->reserve(max_length_per_snippet * n_results)) {
366 my_error(ER_OUT_OF_RESOURCES, MYF(0), HA_ERR_OUT_OF_MEM);
367 goto error;
368 }
369
370 for (unsigned int i = 0; i < n_results; ++i) {
371 result_str->q_append(start_tag, start_tag_length);
372
373 unsigned int result_length;
374 grn_rc rc =
375 grn_snip_get_result(ctx, snippet, i,
376 (char *)result_str->ptr() + result_str->length(),
377 &result_length);
378 if (rc) {
379 my_printf_error(ER_MRN_ERROR_FROM_GROONGA_NUM,
380 ER_MRN_ERROR_FROM_GROONGA_STR, MYF(0), ctx->errbuf);
381 goto error;
382 }
383 result_str->length(result_str->length() + result_length);
384
385 result_str->q_append(end_tag, end_tag_length);
386 }
387 }
388
389 if (!info->snippet) {
390 grn_rc rc = grn_obj_close(ctx, snippet);
391 if (rc != GRN_SUCCESS) {
392 my_printf_error(ER_MRN_ERROR_FROM_GROONGA_NUM,
393 ER_MRN_ERROR_FROM_GROONGA_STR, MYF(0), ctx->errbuf);
394 goto error;
395 }
396 }
397 }
398
399 *length = result_str->length();
400 DBUG_RETURN((char *)result_str->ptr());
401
402error:
403 if (!info->snippet && snippet) {
404 grn_obj_close(ctx, snippet);
405 }
406
407 *is_null = 1;
408 *error = 1;
409
410 DBUG_RETURN(NULL);
411}
412
413MRN_API void mroonga_snippet_html_deinit(UDF_INIT *init)
414{
415 MRN_DBUG_ENTER_FUNCTION();
416
417 mrn_snippet_html_info *info =
418 reinterpret_cast<mrn_snippet_html_info *>(init->ptr);
419 if (!info) {
420 DBUG_VOID_RETURN;
421 }
422
423 if (info->snippet) {
424 grn_obj_close(info->ctx, info->snippet);
425 }
426 if (info->query_mode.used) {
427 if (info->query_mode.default_column) {
428 grn_obj_close(info->ctx, info->query_mode.default_column);
429 }
430 if (info->query_mode.table) {
431 grn_obj_close(info->ctx, info->query_mode.table);
432 }
433 }
434 MRN_STRING_FREE(info->result_str);
435 if (!info->use_shared_db) {
436 grn_obj_close(info->ctx, info->db);
437 }
438 mrn_context_pool->release(info->ctx);
439 my_free(info);
440
441 DBUG_VOID_RETURN;
442}
443
444MRN_END_DECLS
445