Coverage Report

Created: 2025-07-01 06:58

/src/tarantool/src/box/execute.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
3
 *
4
 * Redistribution and use in source and binary forms, with or
5
 * without modification, are permitted provided that the following
6
 * conditions are met:
7
 *
8
 * 1. Redistributions of source code must retain the above
9
 *    copyright notice, this list of conditions and the
10
 *    following disclaimer.
11
 *
12
 * 2. Redistributions in binary form must reproduce the above
13
 *    copyright notice, this list of conditions and the following
14
 *    disclaimer in the documentation and/or other materials
15
 *    provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21
 * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
 * SUCH DAMAGE.
30
 */
31
#include "execute.h"
32
33
#include "assoc.h"
34
#include "bind.h"
35
#include "iproto_constants.h"
36
#include "sql/sqlInt.h"
37
#include "sql/sqlLimit.h"
38
#include "errcode.h"
39
#include "small/region.h"
40
#include "diag.h"
41
#include "sql.h"
42
#include "xrow.h"
43
#include "schema.h"
44
#include "port.h"
45
#include "tuple.h"
46
#include "sql/vdbe.h"
47
#include "box/lua/execute.h"
48
#include "box/sql_stmt_cache.h"
49
#include "session.h"
50
#include "rmean.h"
51
#include "box/sql/port.h"
52
#include "tweaks.h"
53
54
const char *sql_info_key_strs[] = {
55
  "row_count",
56
  "autoincrement_ids",
57
};
58
59
/** Whether to enable access checks for SQL requests. */
60
static bool sql_access_check_is_enabled = true;
61
TWEAK_BOOL(sql_access_check_is_enabled);
62
63
/** Checks if the current user may execute an SQL request. */
64
static int
65
access_check_sql(void)
66
0
{
67
0
  if (!sql_access_check_is_enabled)
68
0
    return 0;
69
0
  struct credentials *cr = effective_user();
70
0
  user_access_t access = PRIV_X | PRIV_U;
71
0
  access &= ~cr->universal_access;
72
0
  if (access == 0)
73
0
    return 0;
74
0
  access &= ~universe.access_sql[cr->auth_token].effective;
75
0
  if (access == 0)
76
0
    return 0;
77
0
  struct user *user = user_find(cr->uid);
78
0
  if (user != NULL)
79
0
    diag_set(AccessDeniedError, priv_name(PRIV_X),
80
0
       schema_object_name(SC_SQL), "", user->def->name);
81
0
  return -1;
82
0
}
83
84
/**
85
 * Convert sql row into a tuple and append to a port.
86
 * @param stmt Started prepared statement. At least one
87
 *        sql_step must be done.
88
 * @param region Runtime allocator for temporary objects.
89
 * @param port Port to store tuples.
90
 *
91
 * @retval  0 Success.
92
 * @retval -1 Memory error.
93
 */
94
static inline int
95
sql_row_to_port(struct Vdbe *stmt, struct region *region, struct port *port)
96
0
{
97
0
  uint32_t size;
98
0
  size_t svp = region_used(region);
99
0
  char *pos = sql_stmt_result_to_msgpack(stmt, &size, region);
100
0
  struct tuple *tuple =
101
0
    tuple_new(box_tuple_format_default(), pos, pos + size);
102
0
  if (tuple == NULL)
103
0
    goto error;
104
0
  region_truncate(region, svp);
105
0
  port_c_add_tuple(port, tuple);
106
0
  return 0;
107
108
0
error:
109
0
  region_truncate(region, svp);
110
0
  return -1;
111
0
}
112
113
static bool
114
sql_stmt_schema_version_is_valid(struct Vdbe *stmt)
115
0
{
116
0
  return sql_stmt_schema_version(stmt) == box_schema_version();
117
0
}
118
119
/**
120
 * Re-compile statement and refresh global prepared statement
121
 * cache with the newest value.
122
 */
123
static int
124
sql_reprepare(struct Vdbe **stmt)
125
0
{
126
0
  const char *sql_str = sql_stmt_query_str(*stmt);
127
0
  struct Vdbe *new_stmt;
128
0
  if (sql_stmt_compile(sql_str, strlen(sql_str), NULL,
129
0
           &new_stmt, NULL) != 0)
130
0
    return -1;
131
0
  if (sql_stmt_cache_update(*stmt, new_stmt) != 0)
132
0
    return -1;
133
0
  *stmt = new_stmt;
134
0
  return 0;
135
0
}
136
137
/**
138
 * Compile statement and save it to the global holder;
139
 * update session hash with prepared statement ID (if
140
 * it's not already there).
141
 */
142
int
143
sql_prepare(const char *sql, int len, struct port *port)
144
0
{
145
0
  uint32_t stmt_id = sql_stmt_calculate_id(sql, len);
146
0
  struct Vdbe *stmt = sql_stmt_cache_find(stmt_id);
147
0
  rmean_collect(rmean_box, IPROTO_PREPARE, 1);
148
0
  if (stmt == NULL) {
149
0
    if (sql_stmt_compile(sql, len, NULL, &stmt, NULL) != 0)
150
0
      return -1;
151
0
    if (sql_stmt_cache_insert(stmt) != 0) {
152
0
      sql_stmt_finalize(stmt);
153
0
      return -1;
154
0
    }
155
0
  } else {
156
0
    if (!sql_stmt_schema_version_is_valid(stmt) &&
157
0
        !sql_stmt_busy(stmt)) {
158
0
      if (sql_reprepare(&stmt) != 0)
159
0
        return -1;
160
0
    }
161
0
  }
162
0
  assert(stmt != NULL);
163
  /* Add id to the list of available statements in session. */
164
0
  if (!session_check_stmt_id(current_session(), stmt_id))
165
0
    session_add_stmt_id(current_session(), stmt_id);
166
0
  enum sql_serialization_format format = sql_column_count(stmt) > 0 ?
167
0
             DQL_PREPARE : DML_PREPARE;
168
0
  port_sql_create(port, stmt, format, false);
169
170
0
  return 0;
171
0
}
172
173
/**
174
 * Deallocate prepared statement from current session:
175
 * remove its ID from session-local hash and unref entry
176
 * in global holder.
177
 */
178
int
179
sql_unprepare(uint32_t stmt_id)
180
0
{
181
0
  if (!session_check_stmt_id(current_session(), stmt_id)) {
182
0
    diag_set(ClientError, ER_WRONG_QUERY_ID, stmt_id);
183
0
    return -1;
184
0
  }
185
0
  session_remove_stmt_id(current_session(), stmt_id);
186
0
  sql_stmt_unref(stmt_id);
187
0
  return 0;
188
0
}
189
190
/**
191
 * Execute prepared SQL statement.
192
 *
193
 * This function uses region to allocate memory for temporary
194
 * objects. After this function, region will be in the same state
195
 * in which it was before this function.
196
 *
197
 * @param db SQL handle.
198
 * @param stmt Prepared statement.
199
 * @param port Port to store SQL response.
200
 * @param region Region to allocate temporary objects.
201
 *
202
 * @retval  0 Success.
203
 * @retval -1 Error.
204
 */
205
static inline int
206
sql_execute(struct Vdbe *stmt, struct port *port, struct region *region)
207
0
{
208
0
  int rc, column_count = sql_column_count(stmt);
209
0
  rmean_collect(rmean_box, IPROTO_EXECUTE, 1);
210
0
  if (column_count > 0) {
211
    /* Either ROW or DONE or ERROR. */
212
0
    while ((rc = sql_step(stmt)) == SQL_ROW) {
213
0
      if (sql_row_to_port(stmt, region, port) != 0)
214
0
        return -1;
215
0
    }
216
0
    assert(rc == SQL_DONE || rc != 0);
217
0
  } else {
218
    /* No rows. Either DONE or ERROR. */
219
0
    rc = sql_step(stmt);
220
0
    assert(rc != SQL_ROW && rc != 0);
221
0
  }
222
0
  if (rc != SQL_DONE)
223
0
    return -1;
224
0
  return 0;
225
0
}
226
227
int
228
sql_execute_prepared(uint32_t stmt_id, const struct sql_bind *bind,
229
         uint32_t bind_count, struct port *port,
230
         struct region *region)
231
0
{
232
233
0
  if (!session_check_stmt_id(current_session(), stmt_id)) {
234
0
    diag_set(ClientError, ER_WRONG_QUERY_ID, stmt_id);
235
0
    return -1;
236
0
  }
237
0
  struct Vdbe *stmt = sql_stmt_cache_find(stmt_id);
238
0
  assert(stmt != NULL);
239
0
  if (!sql_stmt_schema_version_is_valid(stmt)) {
240
0
    diag_set(ClientError, ER_SQL_EXECUTE, "statement has expired");
241
0
    return -1;
242
0
  }
243
0
  if (sql_stmt_busy(stmt)) {
244
0
    const char *sql_str = sql_stmt_query_str(stmt);
245
0
    return sql_prepare_and_execute(sql_str, strlen(sql_str), bind,
246
0
                 bind_count, port, region);
247
0
  }
248
  /*
249
   * Clear all set from previous execution cycle values to be bound and
250
   * remove autoincrement IDs generated in that cycle.
251
   */
252
0
  sql_unbind(stmt);
253
0
  if (sql_bind(stmt, bind, bind_count) != 0)
254
0
    return -1;
255
0
  sql_reset_autoinc_id_list(stmt);
256
0
  enum sql_serialization_format format = sql_column_count(stmt) > 0 ?
257
0
                 DQL_EXECUTE : DML_EXECUTE;
258
0
  port_sql_create(port, stmt, format, false);
259
0
  if (sql_execute(stmt, port, region) != 0) {
260
0
    port_destroy(port);
261
0
    sql_stmt_reset(stmt);
262
0
    return -1;
263
0
  }
264
0
  sql_stmt_reset(stmt);
265
266
0
  return 0;
267
0
}
268
269
int
270
sql_prepare_and_execute(const char *sql, int len, const struct sql_bind *bind,
271
      uint32_t bind_count, struct port *port,
272
      struct region *region)
273
0
{
274
0
  struct Vdbe *stmt;
275
0
  if (sql_stmt_compile(sql, len, NULL, &stmt, NULL) != 0)
276
0
    return -1;
277
0
  assert(stmt != NULL);
278
0
  enum sql_serialization_format format = sql_column_count(stmt) > 0 ?
279
0
             DQL_EXECUTE : DML_EXECUTE;
280
0
  port_sql_create(port, stmt, format, true);
281
0
  if (sql_bind(stmt, bind, bind_count) == 0 &&
282
0
      sql_execute(stmt, port, region) == 0)
283
0
    return 0;
284
0
  port_destroy(port);
285
0
  return -1;
286
0
}
287
288
int
289
box_process_sql(const struct sql_request *request, struct port *port)
290
0
{
291
0
  if (access_check_sql() != 0)
292
0
    return -1;
293
0
  struct region *region = &fiber()->gc;
294
0
  struct sql_bind *bind = NULL;
295
0
  int bind_count = 0;
296
0
  if (request->bind != NULL) {
297
0
    bind_count = sql_bind_list_decode(request->bind, &bind);
298
0
    if (bind_count < 0)
299
0
      return -1;
300
0
  }
301
  /*
302
   * There are four options:
303
   * 1. Prepare SQL query (IPROTO_PREPARE + SQL string);
304
   * 2. Unprepare SQL query (IPROTO_PREPARE + stmt id);
305
   * 3. Execute SQL query (IPROTO_EXECUTE + SQL string);
306
   * 4. Execute prepared query (IPROTO_EXECUTE + stmt id).
307
   */
308
0
  if (request->execute) {
309
0
    if (request->sql_text != NULL) {
310
0
      assert(request->stmt_id == NULL);
311
0
      const char *sql = request->sql_text;
312
0
      uint32_t len;
313
0
      sql = mp_decode_str(&sql, &len);
314
0
      return sql_prepare_and_execute(sql, len,
315
0
                   bind, bind_count,
316
0
                   port, region);
317
0
    } else {
318
0
      assert(request->stmt_id != NULL);
319
0
      const char *data = request->stmt_id;
320
0
      uint32_t stmt_id = mp_decode_uint(&data);
321
0
      return sql_execute_prepared(stmt_id, bind, bind_count,
322
0
                port, region);
323
0
    }
324
0
  } else {
325
0
    if (request->sql_text != NULL) {
326
0
      assert(request->stmt_id == NULL);
327
0
      const char *sql = request->sql_text;
328
0
      uint32_t len;
329
0
      sql = mp_decode_str(&sql, &len);
330
0
      return sql_prepare(sql, len, port);
331
0
    } else {
332
0
      assert(request->stmt_id != NULL);
333
0
      const char *data = request->stmt_id;
334
0
      uint32_t stmt_id = mp_decode_uint(&data);
335
0
      if (sql_unprepare(stmt_id) != 0)
336
0
        return -1;
337
0
      port_sql_create(port, NULL, UNPREPARE, false);
338
0
      return 0;
339
0
    }
340
0
  }
341
0
}