1/**
2 * Copyright 2021 Alibaba, Inc. and its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * \author guonix
17 * \date Nov 2020
18 * \brief
19 */
20
21#include "sqlite_statement.h"
22#include "common/error_code.h"
23#include "common/logger.h"
24
25namespace proxima {
26namespace be {
27namespace meta {
28namespace sqlite {
29
30Statement::Statement() = default;
31
32Statement::Statement(std::string database, const char *sql)
33 : database_(std::move(database)), sql_(sql) {}
34
35Statement::Statement(std::string database, std::string sql)
36 : database_(std::move(database)), sql_(std::move(sql)) {}
37
38Statement::Statement(Statement &&stmt) noexcept
39 : database_(std::move(stmt.database_)),
40 connection_(stmt.connection_),
41 sql_(std::move(stmt.sql_)),
42 statement_(stmt.statement_) {
43 stmt.connection_ = nullptr;
44 stmt.statement_ = nullptr;
45}
46
47Statement::~Statement() {
48 cleanup();
49}
50
51int Statement::initialize() {
52 if (connection_ != nullptr && statement_ != nullptr) {
53 return 0;
54 }
55
56 if (connection_ != nullptr || statement_ != nullptr) {
57 LOG_ERROR(
58 "Statement have been initialized partially, invoke cleanup, before "
59 "initialize");
60 return PROXIMA_BE_ERROR_CODE(RuntimeError);
61 }
62
63 int code = sqlite3_open_v2(
64 database_.c_str(), &connection_,
65 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX,
66 nullptr);
67 if (code == SQLITE_OK) {
68 code = compile_sql();
69 } else {
70 LOG_ERROR("Failed to open sqlite db, msg[%s]", sqlite3_errstr(code));
71 }
72
73 if (code != SQLITE_OK) {
74 do_cleanup();
75 }
76 return code;
77}
78
79int Statement::cleanup() {
80 return do_cleanup();
81}
82
83int Statement::exec(const std::function<int(sqlite3_stmt *)> &binder,
84 const std::function<int(sqlite3_stmt *)> &fetcher,
85 uint32_t retry) {
86 if (!statement_ || reset() != SQLITE_OK) {
87 return PROXIMA_BE_ERROR_CODE(RuntimeError);
88 }
89
90 // Bind values
91 int code = binder ? binder(statement_) : 0;
92 if (code != 0) {
93 LOG_ERROR("Failed to bind values to statement, code[%d]", code);
94 } else {
95 do {
96 code = sqlite3_step(statement_);
97 switch (code) {
98 case SQLITE_ROW: // have result
99 if (fetcher && fetcher(statement_) != 0) {
100 // Can't fetch the result, break loop
101 code = PROXIMA_BE_ERROR_CODE(RuntimeError);
102 }
103 break;
104 case SQLITE_BUSY: // try again
105 if (retry-- == 0) {
106 code = PROXIMA_BE_ERROR_CODE(RuntimeError);
107 }
108 break;
109 case SQLITE_DONE: // finished
110 case SQLITE_ERROR:
111 case SQLITE_MISUSE:
112 default:
113 break;
114 }
115 } while (code == SQLITE_ROW || code == SQLITE_BUSY);
116 if (code == SQLITE_DONE) { // return 0 if and only if code == SQLITE_DONE
117 code = 0;
118 }
119 }
120
121 if (reset() != SQLITE_OK) {
122 // Do not return error code if reset failed, block following write requests
123 // to sqlite, keep proxima be alive for QueryService and IndexAgent.
124 LOG_ERROR(
125 "Unexpected reset statements failed, contact administrator for "
126 "help.");
127 }
128 return code;
129}
130
131int Statement::prepare_sql(const std::string &sql) {
132 if (statement_ != nullptr) {
133 int code = sqlite3_finalize(statement_);
134 if (code != SQLITE_OK) {
135 LOG_ERROR("Failed to finalize statement. msg[%s]", sqlite3_errstr(code));
136 }
137 // Ignore return code of sqlite3_finalize, just reset statement_ to nullptr
138 statement_ = nullptr;
139 }
140 sql_ = sql;
141 return compile_sql();
142}
143
144int Statement::reset() {
145 int code = sqlite3_reset(statement_);
146 // There are nothing we can do when reset failed. all write requests to
147 // sqlite will be failed
148 if (code != SQLITE_OK) {
149 code = sqlite3_clear_bindings(statement_);
150 if (code != SQLITE_OK) {
151 const char *sql = sqlite3_expanded_sql(statement_);
152 LOG_ERROR("Can't reset statement. sql[%s] code[%d] what[%s]", sql, code,
153 sqlite3_errstr(code));
154 sqlite3_free(const_cast<char *>(sql));
155 }
156 }
157 return code;
158}
159
160int Statement::do_cleanup() {
161 int code = 0;
162 if (statement_ != nullptr) {
163 code = sqlite3_finalize(statement_);
164 if (code != SQLITE_OK) {
165 LOG_ERROR("Failed to finalize statement. msg[%s]", sqlite3_errstr(code));
166 }
167 // Ignore error code, just reset statement_ to nullptr (Memory Leak?)
168 statement_ = nullptr;
169 }
170
171 if (connection_ != nullptr) {
172 code = sqlite3_close_v2(connection_);
173 if (code != SQLITE_OK) { // Fatal Error, can't ignore this error,
174 // once we set connection to nullptr,
175 // cause to another unknown issue.
176 LOG_ERROR(
177 "Failed to close connection with sqlite database. code[%d], what[%s]",
178 code, sqlite3_errstr(code));
179 } else {
180 connection_ = nullptr;
181 }
182 }
183 return code;
184}
185
186int Statement::compile_sql() {
187 int code = sqlite3_prepare_v2(connection_, sql_.c_str(), sql_.length(),
188 &statement_, nullptr);
189 if (code == SQLITE_OK) {
190 LOG_DEBUG("Prepare statement succeeded");
191 } else {
192 LOG_ERROR("Failed to prepare statement. sql[%s], msg[%s]", sql_.c_str(),
193 sqlite3_errstr(code));
194 code = PROXIMA_BE_ERROR_CODE(RuntimeError);
195 }
196 return code;
197}
198
199} // namespace sqlite
200} // namespace meta
201} // namespace be
202} // namespace proxima
203