1 /* This file is part of the KDE project
2 Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
3 Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org>
4 Copyright (C) 2004-2016 Jarosław Staniek <staniek@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (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 GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this program; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 #include "MysqlConnection.h"
23 #include "MysqlDriver.h"
24 #include "MysqlCursor.h"
25 #include "MysqlPreparedStatement.h"
26 #include "mysql_debug.h"
27 #include "KDbConnectionData.h"
28 #include "KDbVersionInfo.h"
29
30 #include <QRegularExpression>
31
MysqlConnection(KDbDriver * driver,const KDbConnectionData & connData,const KDbConnectionOptions & options)32 MysqlConnection::MysqlConnection(KDbDriver *driver, const KDbConnectionData& connData,
33 const KDbConnectionOptions &options)
34 : KDbConnection(driver, connData, options)
35 , d(new MysqlConnectionInternal(this))
36 {
37 }
38
~MysqlConnection()39 MysqlConnection::~MysqlConnection()
40 {
41 destroy();
42 delete d;
43 }
44
drv_connect()45 bool MysqlConnection::drv_connect()
46 {
47 const bool ok = d->db_connect(data());
48 if (!ok) {
49 storeResult(); //store error msg, if any - can be destroyed after disconnect()
50 d->db_disconnect();
51 return false;
52 }
53
54 // Get lower_case_table_name value so we know if there's case sensitivity supported
55 // See https://dev.mysql.com/doc/refman/5.0/en/identifier-case-sensitivity.html
56 int intLowerCaseTableNames = 0;
57 const tristate res = querySingleNumber(
58 KDbEscapedString("SHOW VARIABLES LIKE 'lower_case_table_name'"), &intLowerCaseTableNames,
59 0 /*col*/,
60 QueryRecordOptions(QueryRecordOption::Default) & ~QueryRecordOptions(QueryRecordOption::AddLimitTo1));
61 if (res == false) // sanity
62 return false;
63 d->lowerCaseTableNames = intLowerCaseTableNames > 0;
64 return true;
65 }
66
drv_getServerVersion(KDbServerVersionInfo * version)67 bool MysqlConnection::drv_getServerVersion(KDbServerVersionInfo* version)
68 {
69 // https://dev.mysql.com/doc/refman/5.1/en/mysql-get-server-info.html
70 version->setString(QLatin1String(mysql_get_server_info(d->mysql)));
71
72 // get the version info using 'version' built-in variable:
73 //! @todo this is hardcoded for now; define api for retrieving variables and use this API...
74 // https://dev.mysql.com/doc/refman/5.1/en/mysql-get-server-version.html
75 QString versionString;
76 tristate res = querySingleString(KDbEscapedString("SELECT @@version"), &versionString,
77 /*column*/ 0,
78 QueryRecordOptions(QueryRecordOption::Default) & ~QueryRecordOptions(QueryRecordOption::AddLimitTo1));
79
80 QRegularExpression versionRe(QLatin1String("^(\\d+)\\.(\\d+)\\.(\\d+)$"));
81 QRegularExpressionMatch match = versionRe.match(versionString);
82 if (res == false) // sanity
83 return false;
84 if (match.hasMatch()) {
85 // (if querySingleString failed, the version will be 0.0.0...
86 version->setMajor(match.captured(1).toInt());
87 version->setMinor(match.captured(2).toInt());
88 version->setRelease(match.captured(3).toInt());
89 }
90 return true;
91 }
92
drv_disconnect()93 bool MysqlConnection::drv_disconnect()
94 {
95 return d->db_disconnect();
96 }
97
prepareQuery(const KDbEscapedString & sql,KDbCursor::Options options)98 KDbCursor* MysqlConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options)
99 {
100 return new MysqlCursor(this, sql, options);
101 }
102
prepareQuery(KDbQuerySchema * query,KDbCursor::Options options)103 KDbCursor* MysqlConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options)
104 {
105 return new MysqlCursor(this, query, options);
106 }
107
drv_getDatabasesList(QStringList * list)108 bool MysqlConnection::drv_getDatabasesList(QStringList* list)
109 {
110 mysqlDebug();
111 list->clear();
112 MYSQL_RES *res = mysql_list_dbs(d->mysql, nullptr);
113 if (res != nullptr) {
114 MYSQL_ROW row;
115 while ((row = mysql_fetch_row(res)) != nullptr) {
116 *list << QString::fromUtf8(row[0]);
117 }
118 mysql_free_result(res);
119 return true;
120 }
121 storeResult();
122 return false;
123 }
124
drv_databaseExists(const QString & dbName,bool ignoreErrors)125 bool MysqlConnection::drv_databaseExists(const QString &dbName, bool ignoreErrors)
126 {
127 /* db names can be lower case in mysql */
128 const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName);
129 const tristate result = resultExists(
130 KDbEscapedString("SHOW DATABASES LIKE %1").arg(escapeString(storedDbName)));
131 if (result == true) {
132 return true;
133 }
134 if (!ignoreErrors) {
135 m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
136 tr("The database \"%1\" does not exist.").arg(storedDbName));
137 }
138 return false;
139 }
140
drv_createDatabase(const QString & dbName)141 bool MysqlConnection::drv_createDatabase(const QString &dbName)
142 {
143 const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName);
144 mysqlDebug() << storedDbName;
145 // mysql_create_db deprecated, use SQL here.
146 // db names are lower case in mysql
147 return drv_executeSql(KDbEscapedString("CREATE DATABASE %1").arg(escapeIdentifier(storedDbName)));
148 }
149
drv_useDatabase(const QString & dbName,bool * cancelled,KDbMessageHandler * msgHandler)150 bool MysqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled, KDbMessageHandler* msgHandler)
151 {
152 Q_UNUSED(cancelled);
153 Q_UNUSED(msgHandler);
154 //! @todo is here escaping needed?
155 const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName);
156 if (!d->useDatabase(storedDbName)) {
157 storeResult();
158 return false;
159 }
160 return true;
161 }
162
drv_closeDatabase()163 bool MysqlConnection::drv_closeDatabase()
164 {
165 //! @todo free resources, as far as I know, mysql doesn't support that
166 return true;
167 }
168
drv_dropDatabase(const QString & dbName)169 bool MysqlConnection::drv_dropDatabase(const QString &dbName)
170 {
171 //! @todo is here escaping needed?
172 const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName);
173 return drv_executeSql(KDbEscapedString("DROP DATABASE %1").arg(escapeIdentifier(storedDbName)));
174 }
175
drv_prepareSql(const KDbEscapedString & sql)176 KDbSqlResult* MysqlConnection::drv_prepareSql(const KDbEscapedString& sql)
177 {
178 if (!drv_executeSql(sql)) {
179 return nullptr;
180 }
181 MYSQL_RES *data = mysql_use_result(d->mysql); // more optimal than mysql_store_result
182 //! @todo use mysql_error()
183 return new MysqlSqlResult(this, data);
184 }
185
drv_executeSql(const KDbEscapedString & sql)186 bool MysqlConnection::drv_executeSql(const KDbEscapedString& sql)
187 {
188 if (!d->executeSql(sql)) {
189 storeResult();
190 return false;
191 }
192 return true;
193 }
194
serverResultName() const195 QString MysqlConnection::serverResultName() const
196 {
197 return MysqlConnectionInternal::serverResultName(d->mysql);
198 }
199
drv_containsTable(const QString & tableName)200 tristate MysqlConnection::drv_containsTable(const QString& tableName)
201 {
202 return resultExists(KDbEscapedString("SHOW TABLES LIKE %1")
203 .arg(escapeString(tableName)));
204 }
205
prepareStatementInternal()206 KDbPreparedStatementInterface* MysqlConnection::prepareStatementInternal()
207 {
208 return new MysqlPreparedStatement(d);
209 }
210
storeResult()211 void MysqlConnection::storeResult()
212 {
213 d->storeResult(&m_result);
214 }
215