Better error on library initialisation failure in rlm_sql_mysql
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_mysql / rlm_sql_mysql.c
1 /*
2  * sql_mysql.c          SQL Module
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000-2007  The FreeRADIUS server project
21  * Copyright 2000  Mike Machado <mike@innercite.com>
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 #include <sys/stat.h>
31
32 #include "config.h"
33
34 #ifdef HAVE_MYSQL_MYSQL_H
35 #  include <mysql/mysql_version.h>
36 #  include <mysql/errmsg.h>
37 #  include <mysql/mysql.h>
38 #  include <mysql/mysqld_error.h>
39 #elif defined(HAVE_MYSQL_H)
40 #  include <mysql_version.h>
41 #  include <errmsg.h>
42 #  include <mysql.h>
43 #  include <mysqld_error.h>
44 #endif
45
46 #include "rlm_sql.h"
47
48 static int mysql_instance_count = 0;
49
50 typedef struct rlm_sql_mysql_conn {
51         MYSQL           db;
52         MYSQL           *sock;
53         MYSQL_RES       *result;
54         rlm_sql_row_t   row;
55 } rlm_sql_mysql_conn_t;
56
57 typedef struct rlm_sql_mysql_config {
58         char const      *tls_ca_file;
59         char const      *tls_ca_path;
60         char const      *tls_certificate_file;
61         char const      *tls_private_key_file;
62         char const      *tls_cipher;
63 } rlm_sql_mysql_config_t;
64
65 static CONF_PARSER tls_config[] = {
66         { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_file), NULL },
67         { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_path), NULL },
68         { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_certificate_file), NULL },
69         { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_private_key_file), NULL },
70
71         /*
72          *      MySQL Specific TLS attributes
73          */
74         { "cipher", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, tls_cipher), NULL },
75
76         { NULL, -1, 0, NULL, NULL }
77 };
78
79 static const CONF_PARSER driver_config[] = {
80         { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
81
82         {NULL, -1, 0, NULL, NULL}
83 };
84
85 /* Prototypes */
86 static sql_rcode_t sql_free_result(rlm_sql_handle_t*, rlm_sql_config_t*);
87
88 static int _sql_socket_destructor(rlm_sql_mysql_conn_t *conn)
89 {
90         DEBUG2("rlm_sql_mysql: Socket destructor called, closing socket");
91
92         if (conn->sock){
93                 mysql_close(conn->sock);
94         }
95
96         return 0;
97 }
98
99 static int _mod_destructor(UNUSED rlm_sql_mysql_config_t *driver)
100 {
101         mysql_instance_count--;
102
103         if (mysql_instance_count == 0) {
104                  mysql_library_end();
105         }
106
107         return 0;
108 }
109
110 static int mod_instantiate(CONF_SECTION *conf, rlm_sql_config_t *config)
111 {
112         rlm_sql_mysql_config_t *driver;
113
114         static bool version_done = false;
115
116         if (!version_done) {
117                 version_done = true;
118
119                 INFO("rlm_sql_mysql: libmysql version: %s", mysql_get_client_info());
120         }
121
122         if (mysql_instance_count == 0) {
123                 if (mysql_library_init(0, NULL, NULL)) {
124                         ERROR("rlm_sql_mysql: libmysql initialisation failed");
125
126                         return -1;
127                 }
128         }
129         mysql_instance_count++;
130
131         MEM(driver = config->driver = talloc_zero(config, rlm_sql_mysql_config_t));
132         talloc_set_destructor(driver, _mod_destructor);
133
134         if (cf_section_parse(conf, driver, driver_config) < 0) {
135                 return -1;
136         }
137
138         return 0;
139 }
140
141 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
142 {
143         rlm_sql_mysql_conn_t *conn;
144         rlm_sql_mysql_config_t *driver = config->driver;
145         unsigned long sql_flags;
146
147         MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_mysql_conn_t));
148         talloc_set_destructor(conn, _sql_socket_destructor);
149
150         DEBUG("rlm_sql_mysql: Starting connect to MySQL server");
151
152         mysql_init(&(conn->db));
153
154         /*
155          *      If any of the TLS options are set, configure TLS
156          *
157          *      According to MySQL docs this function always returns 0, so we won't
158          *      know if ssl setup succeeded until mysql_real_connect is called below.
159          */
160         if (driver->tls_ca_file || driver->tls_ca_path ||
161             driver->tls_certificate_file || driver->tls_private_key_file) {
162                 mysql_ssl_set(&(conn->db), driver->tls_private_key_file, driver->tls_certificate_file,
163                               driver->tls_ca_file, driver->tls_ca_path, driver->tls_cipher);
164         }
165
166         mysql_options(&(conn->db), MYSQL_READ_DEFAULT_GROUP, "freeradius");
167
168         /*
169          *      We need to know about connection errors, and are capable
170          *      of reconnecting automatically.
171          */
172 #ifdef MYSQL_OPT_RECONNECT
173         {
174                 my_bool reconnect = 0;
175                 mysql_options(&(conn->db), MYSQL_OPT_RECONNECT, &reconnect);
176         }
177 #endif
178
179 #if (MYSQL_VERSION_ID >= 50000)
180         if (config->query_timeout) {
181                 unsigned int connect_timeout = config->query_timeout;
182                 unsigned int read_timeout = config->query_timeout;
183                 unsigned int write_timeout = config->query_timeout;
184
185                 /*
186                  *      The timeout in seconds for each attempt to read from the server.
187                  *      There are retries if necessary, so the total effective timeout
188                  *      value is three times the option value.
189                  */
190                 if (config->query_timeout >= 3) read_timeout /= 3;
191
192                 /*
193                  *      The timeout in seconds for each attempt to write to the server.
194                  *      There is a retry if necessary, so the total effective timeout
195                  *      value is two times the option value.
196                  */
197                 if (config->query_timeout >= 2) write_timeout /= 2;
198
199                 /*
200                  *      Connect timeout is actually connect timeout (according to the
201                  *      docs) there are no automatic retries.
202                  */
203                 mysql_options(&(conn->db), MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
204                 mysql_options(&(conn->db), MYSQL_OPT_READ_TIMEOUT, &read_timeout);
205                 mysql_options(&(conn->db), MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
206         }
207 #endif
208
209 #if (MYSQL_VERSION_ID >= 40100)
210         sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS;
211 #else
212         sql_flags = CLIENT_FOUND_ROWS;
213 #endif
214
215 #ifdef CLIENT_MULTI_STATEMENTS
216         sql_flags |= CLIENT_MULTI_STATEMENTS;
217 #endif
218         conn->sock = mysql_real_connect(&(conn->db),
219                                         config->sql_server,
220                                         config->sql_login,
221                                         config->sql_password,
222                                         config->sql_db,
223                                         atoi(config->sql_port),
224                                         NULL,
225                                         sql_flags);
226         if (!conn->sock) {
227                 ERROR("rlm_sql_mysql: Couldn't connect socket to MySQL server %s@%s:%s", config->sql_login,
228                       config->sql_server, config->sql_db);
229                 ERROR("rlm_sql_mysql: MySQL error: %s", mysql_error(&conn->db));
230
231                 conn->sock = NULL;
232                 return RLM_SQL_ERROR;
233         }
234
235         DEBUG2("rlm_sql_mysql: Connected to database '%s' on %s, server version %s, protocol version %i",
236                config->sql_db, mysql_get_host_info(conn->sock),
237                mysql_get_server_info(conn->sock), mysql_get_proto_info(conn->sock));
238
239         return RLM_SQL_OK;
240 }
241
242 /** Analyse the last error that occurred on the socket, and determine an actions
243  *
244  * @param server Socket from which to extract the server error. May be NULL.
245  * @param client_errno Error from the client.
246  * @return an action for rlm_sql to take.
247  */
248 static sql_rcode_t sql_check_error(MYSQL *server, int client_errno)
249 {
250         int server_errno = 0;
251         if (server) server_errno = mysql_errno(server);
252
253         /*
254          *      Process errors from the client
255          */
256         if (client_errno > 0) switch (client_errno) {
257         case CR_SERVER_GONE_ERROR:
258         case CR_SERVER_LOST:
259         case -1:
260                 return RLM_SQL_RECONNECT;
261
262         case CR_OUT_OF_MEMORY:
263         case CR_COMMANDS_OUT_OF_SYNC:
264         case CR_UNKNOWN_ERROR:
265         default:
266                 return RLM_SQL_ERROR;
267         }
268
269         /*
270          *      Process errors from the server
271          */
272         if (server_errno > 0) switch (server_errno) {
273         /*
274          *      Constraints errors that signify a duplicate, or that we might
275          *      want to try an alternative query.
276          *
277          *      Errors not found in the 3.23/4.0/4.1 manual page checked for.
278          *      Other error constants should always be available.
279          */
280
281         case ER_DUP_UNIQUE:                     /* Can't write, because of unique constraint, to table '%s'. */
282         case ER_DUP_KEY:                        /* Can't write; duplicate key in table '%s' */
283
284         case ER_DUP_ENTRY:                      /* Duplicate entry '%s' for key %d. */
285         case ER_NO_REFERENCED_ROW:              /* Cannot add or update a child row: a foreign key constraint fails */
286         case ER_ROW_IS_REFERENCED:              /* Cannot delete or update a parent row: a foreign key constraint fails */
287 #ifdef ER_FOREIGN_DUPLICATE_KEY
288         case ER_FOREIGN_DUPLICATE_KEY:          /* Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry. */
289 #endif
290 #ifdef ER_DUP_ENTRY_WITH_KEY_NAME
291         case ER_DUP_ENTRY_WITH_KEY_NAME:        /* Duplicate entry '%s' for key '%s' */
292 #endif
293 #ifdef ER_NO_REFERENCED_ROW_2
294         case ER_NO_REFERENCED_ROW_2:
295 #endif
296 #ifdef ER_ROW_IS_REFERENCED_2
297         case ER_ROW_IS_REFERENCED_2:
298 #endif
299                 return RLM_SQL_ALT_QUERY;
300
301         /*
302          *      Constraints errors that signify an invalid query
303          *      that can never succeed.
304          */
305         case ER_BAD_NULL_ERROR:                 /* Column '%s' cannot be null */
306         case ER_NON_UNIQ_ERROR:                 /* Column '%s' in %s is ambiguous */
307                 return RLM_SQL_QUERY_INVALID;
308
309         default:
310                 break;
311         }
312
313         return RLM_SQL_OK;
314 }
315
316 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
317 {
318         rlm_sql_mysql_conn_t *conn = handle->conn;
319         sql_rcode_t rcode;
320         char const *info;
321
322         if (!conn->sock) {
323                 ERROR("rlm_sql_mysql: Socket not connected");
324                 return RLM_SQL_RECONNECT;
325         }
326
327         mysql_query(conn->sock, query);
328         rcode = sql_check_error(conn->sock, 0);
329         if (rcode != RLM_SQL_OK) {
330                 return rcode;
331         }
332
333         /* Only returns non-null string for INSERTS */
334         info = mysql_info(conn->sock);
335         if (info) DEBUG2("rlm_sql_mysql: %s", info);
336
337         return RLM_SQL_OK;
338 }
339
340 static sql_rcode_t sql_store_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
341 {
342         rlm_sql_mysql_conn_t *conn = handle->conn;
343         sql_rcode_t rcode;
344         int ret;
345
346         if (!conn->sock) {
347                 ERROR("rlm_sql_mysql: Socket not connected");
348                 return RLM_SQL_RECONNECT;
349         }
350
351 retry_store_result:
352         if (!(conn->result = mysql_store_result(conn->sock))) {
353                 rcode = sql_check_error(conn->sock, 0);
354                 if (rcode != RLM_SQL_OK) return rcode;
355 #if (MYSQL_VERSION_ID >= 40100)
356                 ret = mysql_next_result(conn->sock);
357                 if (ret == 0) {
358                         /* there are more results */
359                         goto retry_store_result;
360                 } else if (ret > 0) return sql_check_error(NULL, ret);
361 #endif
362         }
363         return RLM_SQL_OK;
364 }
365
366 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
367 {
368         int num = 0;
369         rlm_sql_mysql_conn_t *conn = handle->conn;
370
371 #if MYSQL_VERSION_ID >= 32224
372         if (!(num = mysql_field_count(conn->sock))) {
373 #else
374         if (!(num = mysql_num_fields(conn->sock))) {
375 #endif
376                 return -1;
377         }
378         return num;
379 }
380
381 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
382 {
383         sql_rcode_t rcode;
384
385         rcode = sql_query(handle, config, query);
386         if (rcode != RLM_SQL_OK) {
387                 return rcode;
388         }
389
390         rcode = sql_store_result(handle, config);
391         if (rcode != RLM_SQL_OK) {
392                 return rcode;
393         }
394
395         /* Why? Per http://www.mysql.com/doc/n/o/node_591.html,
396          * this cannot return an error.  Perhaps just to complain if no
397          * fields are found?
398          */
399         sql_num_fields(handle, config);
400
401         return rcode;
402 }
403
404 static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
405 {
406         rlm_sql_mysql_conn_t *conn = handle->conn;
407
408         if (conn->result) {
409                 return mysql_num_rows(conn->result);
410         }
411
412         return 0;
413 }
414
415 static sql_rcode_t sql_fetch_row(rlm_sql_row_t *out, rlm_sql_handle_t *handle, rlm_sql_config_t *config)
416 {
417         rlm_sql_mysql_conn_t *conn = handle->conn;
418         sql_rcode_t rcode;
419         int ret;
420
421         *out = NULL;
422
423         /*
424          *  Check pointer before de-referencing it.
425          */
426         if (!conn->result) return RLM_SQL_RECONNECT;
427
428 retry_fetch_row:
429         *out = handle->row = mysql_fetch_row(conn->result);
430         if (!handle->row) {
431                 rcode = sql_check_error(conn->sock, 0);
432                 if (rcode != RLM_SQL_OK) return rcode;
433
434 #if (MYSQL_VERSION_ID >= 40100)
435                 sql_free_result(handle, config);
436
437                 ret = mysql_next_result(conn->sock);
438                 if (ret == 0) {
439                         /* there are more results */
440                         if ((sql_store_result(handle, config) == 0) && (conn->result != NULL)) {
441                                 goto retry_fetch_row;
442                         }
443                 } else if (ret > 0) return sql_check_error(NULL, ret);
444 #endif
445         }
446         return RLM_SQL_OK;
447 }
448
449 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
450 {
451         rlm_sql_mysql_conn_t *conn = handle->conn;
452
453         if (conn->result) {
454                 mysql_free_result(conn->result);
455                 conn->result = NULL;
456         }
457
458         return 0;
459 }
460
461 /** Retrieves any warnings associated with the last query
462  *
463  * MySQL stores a limited number of warnings associated with the last query
464  * executed. These can be very useful in diagnosing issues, or in some cases
465  * working around bugs in MySQL which causes it to return the wrong error.
466  *
467  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
468  *
469  * @param ctx to allocate temporary error buffers in.
470  * @param out Array of sql_log_entrys to fill.
471  * @param outlen Length of out array.
472  * @param handle rlm_sql connection handle.
473  * @param config rlm_sql config.
474  * @return number of errors written to the sql_log_entry array or -1 on error.
475  */
476 static size_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
477                            rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
478 {
479         rlm_sql_mysql_conn_t    *conn = handle->conn;
480
481         MYSQL_RES               *result;
482         MYSQL_ROW               row;
483         unsigned int            num_fields;
484         size_t                  i = 0;
485         unsigned int            msgs;
486
487         if (outlen == 0) return 0;
488
489         /*
490          *      Check to see if any warnings can be retrieved from the server.
491          */
492         msgs = mysql_warning_count(conn->sock);
493         if (msgs == 0) {
494                 DEBUG3("rlm_sql_mysql: No additional diagnostic info on server");
495                 return 0;
496         }
497
498         /*
499          *      Retrieve any warnings associated with the previous query
500          *      that were left lingering on the server.
501          */
502         if (mysql_query(conn->sock, "SHOW WARNINGS") != 0) return -1;
503         result = mysql_store_result(conn->sock);
504         if (!result) return -1;
505
506         /*
507          *      Fields should be [0] = Level, [1] = Code, [2] = Message
508          */
509         num_fields = mysql_field_count(conn->sock);
510         if (num_fields < 3) {
511                 WARN("rlm_sql_mysql: Failed retrieving warnings, expected 3 fields got %u", num_fields);
512                 mysql_free_result(result);
513
514                 return -1;
515         }
516
517         while ((row = mysql_fetch_row(result))) {
518                 char *msg = NULL;
519                 log_type_t type;
520
521                 /*
522                  *      Translate the MySQL log level into our internal
523                  *      log levels, so they get colourised correctly.
524                  */
525                 if (strcasecmp(row[0], "warning") == 0) type = L_WARN;
526                 else if (strcasecmp(row[0], "note") == 0) type = L_DBG;
527                 else type = L_ERR;
528
529                 msg = talloc_asprintf(ctx, "%s: %s", row[1], row[2]);
530                 out[i].type = type;
531                 out[i].msg = msg;
532                 if (++i == outlen) break;
533         }
534
535         mysql_free_result(result);
536
537         return i;
538 }
539
540 /** Retrieves any errors associated with the connection handle
541  *
542  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
543  *
544  * @param ctx to allocate temporary error buffers in.
545  * @param out Array of sql_log_entrys to fill.
546  * @param outlen Length of out array.
547  * @param handle rlm_sql connection handle.
548  * @param config rlm_sql config.
549  * @return number of errors written to the sql_log_entry array.
550  */
551 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
552                         rlm_sql_handle_t *handle, rlm_sql_config_t *config)
553 {
554         rlm_sql_mysql_conn_t    *conn = handle->conn;
555         char const              *error;
556         size_t                  i = 0;
557
558         rad_assert(conn && conn->sock);
559         rad_assert(outlen > 0);
560
561         error = mysql_error(conn->sock);
562
563         /*
564          *      Grab the error now in case it gets cleared on the next operation.
565          */
566         if (error && (error[0] != '\0')) {
567                 error = talloc_asprintf(ctx, "ERROR %u (%s): %s", mysql_errno(conn->sock), error,
568                                         mysql_sqlstate(conn->sock));
569         }
570
571         /*
572          *      Don't attempt to get errors from the server, if the last error
573          *      was that the server was unavailable.
574          */
575         if ((outlen > 1) && (sql_check_error(conn->sock, 0) != RLM_SQL_RECONNECT)) {
576                 size_t ret;
577
578                 ret = sql_warnings(ctx, out, outlen - 1, handle, config);
579                 if (ret > 0) i += ret;
580         }
581
582         if (error) {
583                 out[i].type = L_ERR;
584                 out[i].msg = error;
585         }
586         i++;
587
588         return i;
589 }
590
591 /** Finish query
592  *
593  * As a single SQL statement may return multiple results
594  * sets, (for example stored procedures) it is necessary to check
595  * whether more results exist and process them in turn if so.
596  *
597  */
598 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
599 {
600 #if (MYSQL_VERSION_ID >= 40100)
601         rlm_sql_mysql_conn_t *conn = handle->conn;
602         sql_rcode_t rcode;
603         int ret;
604
605 skip_next_result:
606         rcode = sql_store_result(handle, config);
607         if (rcode != RLM_SQL_OK) {
608                 return rcode;
609         } else if (conn->result != NULL) {
610                 DEBUG("rlm_sql_mysql: SQL statement returned unexpected result");
611                 sql_free_result(handle, config);
612         }
613
614         ret = mysql_next_result(conn->sock);
615         if (ret == 0) {
616                 /* there are more results */
617                 goto skip_next_result;
618         }  else if (ret > 0) return sql_check_error(NULL, ret);
619 #endif
620         return RLM_SQL_OK;
621 }
622
623 static sql_rcode_t sql_finish_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
624 {
625 #if (MYSQL_VERSION_ID >= 40100)
626         int ret;
627         rlm_sql_mysql_conn_t *conn = handle->conn;
628 #endif
629         sql_free_result(handle, config);
630 #if (MYSQL_VERSION_ID >= 40100)
631         ret = mysql_next_result(conn->sock);
632         if (ret == 0) {
633                 /* there are more results */
634                 sql_finish_query(handle, config);
635         }  else if (ret > 0) return sql_check_error(NULL, ret);
636 #endif
637         return RLM_SQL_OK;
638 }
639
640 static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
641 {
642         rlm_sql_mysql_conn_t *conn = handle->conn;
643
644         return mysql_affected_rows(conn->sock);
645 }
646
647
648 /* Exported to rlm_sql */
649 extern rlm_sql_module_t rlm_sql_mysql;
650 rlm_sql_module_t rlm_sql_mysql = {
651         .name                           = "rlm_sql_mysql",
652         .flags                          = RLM_SQL_RCODE_FLAGS_ALT_QUERY,
653         .mod_instantiate                = mod_instantiate,
654         .sql_socket_init                = sql_socket_init,
655         .sql_query                      = sql_query,
656         .sql_select_query               = sql_select_query,
657         .sql_store_result               = sql_store_result,
658         .sql_num_fields                 = sql_num_fields,
659         .sql_num_rows                   = sql_num_rows,
660         .sql_affected_rows              = sql_affected_rows,
661         .sql_fetch_row                  = sql_fetch_row,
662         .sql_free_result                = sql_free_result,
663         .sql_error                      = sql_error,
664         .sql_finish_query               = sql_finish_query,
665         .sql_finish_select_query        = sql_finish_select_query
666 };