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