1/* -*- c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 Copyright(C) 2010 Tetsuro IKEDA
4 Copyright(C) 2010-2013 Kentoku SHIBA
5 Copyright(C) 2011-2017 Kouhei Sutou <kou@clear-code.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20*/
21
22#include <mrn_mysql.h>
23#include <mrn_mysql_compat.h>
24#include <mrn_path_mapper.hpp>
25#include <mrn_windows.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_current_thread.hpp>
31
32#include <sql_table.h>
33
34MRN_BEGIN_DECLS
35
36extern mrn::DatabaseManager *mrn_db_manager;
37extern mrn::ContextPool *mrn_context_pool;
38
39struct CommandInfo
40{
41 grn_ctx *ctx;
42 grn_obj *db;
43 bool use_shared_db;
44 grn_obj command;
45 String result;
46};
47
48MRN_API my_bool mroonga_command_init(UDF_INIT *init, UDF_ARGS *args,
49 char *message)
50{
51 CommandInfo *info = NULL;
52
53 init->ptr = NULL;
54 if (args->arg_count == 0) {
55 grn_snprintf(message,
56 MYSQL_ERRMSG_SIZE,
57 MYSQL_ERRMSG_SIZE,
58 "mroonga_command(): Wrong number of arguments: %u for 1..",
59 args->arg_count);
60 goto error;
61 }
62
63 if ((args->arg_count % 2) == 0) {
64 grn_snprintf(message,
65 MYSQL_ERRMSG_SIZE,
66 MYSQL_ERRMSG_SIZE,
67 "mroonga_command(): The number of arguments must be odd: %u",
68 args->arg_count);
69 goto error;
70 }
71
72 for (unsigned int i = 0; i < args->arg_count; ++i) {
73 switch (args->arg_type[i]) {
74 case STRING_RESULT:
75 // OK
76 break;
77 case REAL_RESULT:
78 grn_snprintf(message,
79 MYSQL_ERRMSG_SIZE,
80 MYSQL_ERRMSG_SIZE,
81 "mroonga_command(): Argument must be string: <%g>",
82 *reinterpret_cast<double *>(args->args[i]));
83 goto error;
84 break;
85 case INT_RESULT:
86 grn_snprintf(message,
87 MYSQL_ERRMSG_SIZE,
88 MYSQL_ERRMSG_SIZE,
89 "mroonga_command(): Argument must be string: <%lld>",
90 *reinterpret_cast<longlong *>(args->args[i]));
91 goto error;
92 break;
93 case DECIMAL_RESULT:
94 grn_snprintf(message,
95 MYSQL_ERRMSG_SIZE,
96 MYSQL_ERRMSG_SIZE,
97 "mroonga_command(): Argument must be string: <%.*s>",
98 static_cast<int>(args->lengths[i]),
99 args->args[i]);
100 goto error;
101 break;
102 default:
103 grn_snprintf(message,
104 MYSQL_ERRMSG_SIZE,
105 MYSQL_ERRMSG_SIZE,
106 "mroonga_command(): Argument must be string: <%d>(%u)",
107 args->arg_type[i],
108 i);
109 goto error;
110 break;
111 }
112 }
113 init->maybe_null = 1;
114 init->const_item = 0;
115
116 info = (CommandInfo *)mrn_my_malloc(sizeof(CommandInfo),
117 MYF(MY_WME | MY_ZEROFILL));
118 if (!info) {
119 strcpy(message, "mroonga_command(): out of memory");
120 goto error;
121 }
122
123 info->ctx = mrn_context_pool->pull();
124 {
125 const char *current_db_path = MRN_THD_DB_PATH(current_thd);
126 const char *action;
127 if (current_db_path) {
128 action = "open database";
129 char encoded_db_path[FN_REFLEN + 1];
130 uint encoded_db_path_length =
131 tablename_to_filename(current_db_path,
132 encoded_db_path,
133 sizeof(encoded_db_path));
134 encoded_db_path[encoded_db_path_length] = '\0';
135 mrn::Database *db;
136 int error = mrn_db_manager->open(encoded_db_path, &db);
137 if (error == 0) {
138 info->db = db->get();
139 grn_ctx_use(info->ctx, info->db);
140 info->use_shared_db = true;
141 }
142 } else {
143 action = "create anonymous database";
144 info->db = grn_db_create(info->ctx, NULL, NULL);
145 info->use_shared_db = false;
146 }
147 if (!info->db) {
148 grn_snprintf(message,
149 MYSQL_ERRMSG_SIZE,
150 MYSQL_ERRMSG_SIZE,
151 "mroonga_command(): failed to %s: %s",
152 action,
153 info->ctx->errbuf);
154 goto error;
155 }
156 }
157 GRN_TEXT_INIT(&(info->command), 0);
158
159 init->ptr = (char *)info;
160
161 return FALSE;
162
163error:
164 if (info) {
165 if (!info->use_shared_db) {
166 grn_obj_close(info->ctx, info->db);
167 }
168 mrn_context_pool->release(info->ctx);
169 my_free(info);
170 }
171 return TRUE;
172}
173
174static void mroonga_command_escape_value(grn_ctx *ctx,
175 grn_obj *command,
176 const char *value,
177 unsigned long value_length)
178{
179 GRN_TEXT_PUTC(ctx, command, '"');
180
181 const char *value_current = value;
182 const char *value_end = value_current + value_length;
183 while (value_current < value_end) {
184 int char_length = grn_charlen(ctx, value_current, value_end);
185
186 if (char_length == 0) {
187 break;
188 } else if (char_length == 1) {
189 switch (*value_current) {
190 case '\\':
191 case '"':
192 GRN_TEXT_PUTC(ctx, command, '\\');
193 GRN_TEXT_PUTC(ctx, command, *value_current);
194 break;
195 case '\n':
196 GRN_TEXT_PUTS(ctx, command, "\\n");
197 break;
198 default:
199 GRN_TEXT_PUTC(ctx, command, *value_current);
200 break;
201 }
202 } else {
203 GRN_TEXT_PUT(ctx, command, value_current, char_length);
204 }
205
206 value_current += char_length;
207 }
208
209 GRN_TEXT_PUTC(ctx, command, '"');
210}
211
212MRN_API char *mroonga_command(UDF_INIT *init, UDF_ARGS *args, char *result,
213 unsigned long *length, char *is_null, char *error)
214{
215 CommandInfo *info = (CommandInfo *)init->ptr;
216 grn_ctx *ctx = info->ctx;
217 int flags = 0;
218
219 if (!args->args[0]) {
220 *is_null = 1;
221 return NULL;
222 }
223
224 GRN_BULK_REWIND(&(info->command));
225 GRN_TEXT_PUT(ctx, &(info->command), args->args[0], args->lengths[0]);
226 for (unsigned int i = 1; i < args->arg_count; i += 2) {
227 if (!args->args[i] || !args->args[i + 1]) {
228 *is_null = 1;
229 return NULL;
230 }
231
232 const char *name = args->args[i];
233 unsigned long name_length = args->lengths[i];
234 GRN_TEXT_PUTS(ctx, &(info->command), " --");
235 GRN_TEXT_PUT(ctx, &(info->command), name, name_length);
236
237 const char *value = args->args[i + 1];
238 unsigned long value_length = args->lengths[i + 1];
239 GRN_TEXT_PUTS(ctx, &(info->command), " ");
240 mroonga_command_escape_value(ctx, &(info->command), value, value_length);
241 }
242
243 *is_null = 0;
244
245 grn_ctx_send(ctx,
246 GRN_TEXT_VALUE(&(info->command)),
247 GRN_TEXT_LEN(&(info->command)),
248 0);
249 if (ctx->rc) {
250 my_message(ER_ERROR_ON_WRITE, ctx->errbuf, MYF(0));
251 goto error;
252 }
253
254 info->result.length(0);
255 do {
256 char *buffer;
257 unsigned int buffer_length;
258 grn_ctx_recv(ctx, &buffer, &buffer_length, &flags);
259 if (ctx->rc) {
260 my_message(ER_ERROR_ON_READ, ctx->errbuf, MYF(0));
261 goto error;
262 }
263 if (buffer_length > 0) {
264 if (info->result.reserve(buffer_length)) {
265 my_error(ER_OUT_OF_RESOURCES, MYF(0), HA_ERR_OUT_OF_MEM);
266 goto error;
267 }
268 info->result.q_append(buffer, buffer_length);
269 }
270 } while (flags & GRN_CTX_MORE);
271
272 *length = info->result.length();
273 return (char *)(info->result.ptr());
274
275error:
276 *error = 1;
277 return NULL;
278}
279
280MRN_API void mroonga_command_deinit(UDF_INIT *init)
281{
282 CommandInfo *info = (CommandInfo *)init->ptr;
283 if (info) {
284 GRN_OBJ_FIN(info->ctx, &(info->command));
285 if (!info->use_shared_db) {
286 grn_obj_close(info->ctx, info->db);
287 }
288 mrn_context_pool->release(info->ctx);
289 MRN_STRING_FREE(info->result);
290 my_free(info);
291 }
292}
293
294MRN_END_DECLS
295