@@ -121,6 +121,20 @@ static inline QVariant qDateTimeFromString(QString &val)
121121#endif
122122}
123123
124+ // check if this client and server version of MySQL/MariaDB support prepared statements
125+ static inline bool checkPreparedQueries (MYSQL *mysql)
126+ {
127+ std::unique_ptr<MYSQL_STMT, decltype (&mysql_stmt_close)> stmt (mysql_stmt_init (mysql), &mysql_stmt_close);
128+ if (!stmt)
129+ return false ;
130+
131+ static const char dummyQuery[] = " SELECT ? + ?" ;
132+ if (mysql_stmt_prepare (stmt.get (), dummyQuery, sizeof (dummyQuery) - 1 ))
133+ return false ;
134+
135+ return mysql_stmt_param_count (stmt.get ()) == 2 ;
136+ }
137+
124138class QMYSQLResultPrivate ;
125139
126140class QMYSQLResult : public QSqlResult
@@ -172,7 +186,7 @@ class QMYSQLResultPrivate: public QSqlResultPrivate
172186 struct QMyField
173187 {
174188 char *outField = nullptr ;
175- MYSQL_FIELD *myField = nullptr ;
189+ const MYSQL_FIELD *myField = nullptr ;
176190 QMetaType type = QMetaType();
177191 my_bool nullIndicator = false ;
178192 ulong bufLength = 0ul ;
@@ -299,14 +313,10 @@ static bool qIsInteger(int t)
299313
300314void QMYSQLResultPrivate::bindBlobs ()
301315{
302- int i;
303- MYSQL_FIELD *fieldInfo;
304- MYSQL_BIND *bind;
305-
306- for (i = 0 ; i < fields.count (); ++i) {
307- fieldInfo = fields.at (i).myField ;
316+ for (int i = 0 ; i < fields.count (); ++i) {
317+ const MYSQL_FIELD *fieldInfo = fields.at (i).myField ;
308318 if (qIsBlob (inBinds[i].buffer_type ) && meta && fieldInfo) {
309- bind = &inBinds[i];
319+ MYSQL_BIND * bind = &inBinds[i];
310320 bind->buffer_length = fieldInfo->max_length ;
311321 delete[] static_cast <char *>(bind->buffer );
312322 bind->buffer = new char [fieldInfo->max_length ];
@@ -322,43 +332,40 @@ bool QMYSQLResultPrivate::bindInValues()
322332 if (!meta)
323333 return false ;
324334
325- MYSQL_BIND *bind;
326- char *field;
327- int i = 0 ;
328335 fields.resize (mysql_num_fields (meta));
329336
330337 inBinds = new MYSQL_BIND[fields.size ()];
331338 memset (inBinds, 0 , fields.size () * sizeof (MYSQL_BIND));
332339
333- MYSQL_FIELD *fieldInfo;
340+ const MYSQL_FIELD *fieldInfo;
334341
342+ int i = 0 ;
335343 while ((fieldInfo = mysql_fetch_field (meta))) {
344+ MYSQL_BIND *bind = &inBinds[i];
345+
336346 QMyField &f = fields[i];
337347 f.myField = fieldInfo;
338-
348+ bind->buffer_length = f.bufLength = fieldInfo->length + 1 ;
349+ bind->buffer_type = fieldInfo->type ;
339350 f.type = qDecodeMYSQLType (fieldInfo->type , fieldInfo->flags );
340351 if (qIsBlob (fieldInfo->type )) {
341352 // the size of a blob-field is available as soon as we call
342353 // mysql_stmt_store_result()
343354 // after mysql_stmt_exec() in QMYSQLResult::exec()
344- fieldInfo-> length = 0 ;
355+ bind-> buffer_length = f. bufLength = 0 ;
345356 hasBlobs = true ;
346357 } else if (qIsInteger (f.type .id ())) {
347- fieldInfo-> length = 8 ;
358+ bind-> buffer_length = f. bufLength = 8 ;
348359 } else {
349- fieldInfo-> type = MYSQL_TYPE_STRING;
360+ bind-> buffer_type = MYSQL_TYPE_STRING;
350361 }
351- bind = &inBinds[i];
352- field = new char [fieldInfo->length + 1 ];
353- memset (field, 0 , fieldInfo->length + 1 );
354362
355- bind->buffer_type = fieldInfo->type ;
356- bind->buffer = field;
357- bind->buffer_length = f.bufLength = fieldInfo->length + 1 ;
358363 bind->is_null = &f.nullIndicator ;
359364 bind->length = &f.bufLength ;
360365 bind->is_unsigned = fieldInfo->flags & UNSIGNED_FLAG ? 1 : 0 ;
361- f.outField =field;
366+
367+ char *field = new char [bind->buffer_length + 1 ]{};
368+ bind->buffer = f.outField = field;
362369
363370 ++i;
364371 }
@@ -425,8 +432,8 @@ void QMYSQLResult::cleanup()
425432
426433 d->hasBlobs = false ;
427434 d->fields .clear ();
428- d->result = NULL ;
429- d->row = NULL ;
435+ d->result = nullptr ;
436+ d->row = nullptr ;
430437 setAt (-1 );
431438 setActive (false );
432439}
@@ -535,7 +542,7 @@ QVariant QMYSQLResult::data(int field)
535542 if (!driver ())
536543 return QVariant ();
537544
538- int fieldLength = 0 ;
545+ my_ulonglong fieldLength = 0 ;
539546 const QMYSQLResultPrivate::QMyField &f = d->fields .at (field);
540547 QString val;
541548 if (d->preparedQuery ) {
@@ -555,7 +562,7 @@ QVariant QMYSQLResult::data(int field)
555562 if (f.type .id () != QMetaType::QByteArray)
556563 val = QString::fromUtf8 (f.outField , f.bufLength );
557564 } else {
558- if (d->row [field] == NULL ) {
565+ if (d->row [field] == nullptr ) {
559566 // NULL value
560567 return QVariant (f.type );
561568 }
@@ -634,7 +641,7 @@ bool QMYSQLResult::isNull(int field)
634641 if (d->preparedQuery )
635642 return d->fields .at (field).nullIndicator ;
636643 else
637- return d->row [field] == NULL ;
644+ return d->row [field] == nullptr ;
638645}
639646
640647bool QMYSQLResult::reset (const QString& query)
@@ -1076,7 +1083,7 @@ QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent)
10761083 Q_D (QMYSQLDriver);
10771084 init ();
10781085 if (con) {
1079- d->mysql = (MYSQL *) con;
1086+ d->mysql = con;
10801087 setOpen (true );
10811088 setOpenError (false );
10821089 if (qMySqlConnectionCount == 1 )
@@ -1219,26 +1226,39 @@ bool QMYSQLDriver::open(const QString& db,
12191226 }
12201227 }
12211228
1222- if (!(d->mysql = mysql_init ((MYSQL*) 0 ))) {
1229+ if (!(d->mysql = mysql_init (nullptr ))) {
12231230 setLastError (qMakeError (tr (" Unable to allocate a MYSQL object" ),
12241231 QSqlError::ConnectionError, d));
12251232 setOpenError (true );
12261233 return false ;
12271234 }
12281235
1236+ // try utf8 with non BMP first, utf8 (BMP only) if that fails
1237+ static const char wanted_charsets[][8 ] = { " utf8mb4" , " utf8" };
1238+ #ifdef MARIADB_VERSION_ID
1239+ MARIADB_CHARSET_INFO *cs = nullptr ;
1240+ for (const char *p : wanted_charsets) {
1241+ cs = mariadb_get_charset_by_name (p);
1242+ if (cs) {
1243+ d->mysql ->charset = cs;
1244+ break ;
1245+ }
1246+ }
1247+ #else
1248+ // dummy
1249+ struct {
1250+ const char *csname;
1251+ } *cs = nullptr ;
1252+ #endif
1253+
12291254 if (!sslKey.isNull () || !sslCert.isNull () || !sslCA.isNull () ||
12301255 !sslCAPath.isNull () || !sslCipher.isNull ()) {
12311256 mysql_ssl_set (d->mysql ,
1232- sslKey.isNull () ? static_cast <const char *>(0 )
1233- : QFile::encodeName (sslKey).constData (),
1234- sslCert.isNull () ? static_cast <const char *>(0 )
1235- : QFile::encodeName (sslCert).constData (),
1236- sslCA.isNull () ? static_cast <const char *>(0 )
1237- : QFile::encodeName (sslCA).constData (),
1238- sslCAPath.isNull () ? static_cast <const char *>(0 )
1239- : QFile::encodeName (sslCAPath).constData (),
1240- sslCipher.isNull () ? static_cast <const char *>(0 )
1241- : sslCipher.toLocal8Bit ().constData ());
1257+ sslKey.isNull () ? nullptr : sslKey.toUtf8 ().constData (),
1258+ sslCert.isNull () ? nullptr : sslCert.toUtf8 ().constData (),
1259+ sslCA.isNull () ? nullptr : sslCA.toUtf8 ().constData (),
1260+ sslCAPath.isNull () ? nullptr : sslCAPath.toUtf8 ().constData (),
1261+ sslCipher.isNull () ? nullptr : sslCipher.toUtf8 ().constData ());
12421262 }
12431263
12441264 if (connectTimeout != 0 )
@@ -1247,27 +1267,33 @@ bool QMYSQLDriver::open(const QString& db,
12471267 mysql_options (d->mysql , MYSQL_OPT_READ_TIMEOUT, &readTimeout);
12481268 if (writeTimeout != 0 )
12491269 mysql_options (d->mysql , MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
1270+
12501271 MYSQL *mysql = mysql_real_connect (d->mysql ,
1251- host.isNull () ? static_cast <const char *>(0 )
1252- : host.toLocal8Bit ().constData (),
1253- user.isNull () ? static_cast <const char *>(0 )
1254- : user.toLocal8Bit ().constData (),
1255- password.isNull () ? static_cast <const char *>(0 )
1256- : password.toLocal8Bit ().constData (),
1257- db.isNull () ? static_cast <const char *>(0 )
1258- : db.toLocal8Bit ().constData (),
1272+ host.isNull () ? nullptr : host.toUtf8 ().constData (),
1273+ user.isNull () ? nullptr : user.toUtf8 ().constData (),
1274+ password.isNull () ? nullptr : password.toUtf8 ().constData (),
1275+ db.isNull () ? nullptr : db.toUtf8 ().constData (),
12591276 (port > -1 ) ? port : 0 ,
1260- unixSocket.isNull () ? static_cast <const char *>(0 )
1261- : unixSocket.toLocal8Bit ().constData (),
1277+ unixSocket.isNull () ? nullptr : unixSocket.toUtf8 ().constData (),
12621278 optionFlags);
12631279
1264- // try utf8 with non BMP first, utf8 (BMP only) if that fails
1265- if (mysql_set_character_set (d->mysql , " utf8mb4" ))
1266- if (mysql_set_character_set (d->mysql , " utf8" ))
1267- qWarning () << " MySQL: Unable to set the client character set to utf8." ;
1280+ // now ask the server to match the charset we selected
1281+ if (!cs || mysql_set_character_set (d->mysql , cs->csname )) {
1282+ bool ok = false ;
1283+ for (const char *p : wanted_charsets) {
1284+ if (mysql_set_character_set (d->mysql , p)) {
1285+ ok = true ;
1286+ break ;
1287+ }
1288+ }
1289+ if (!ok)
1290+ qWarning (" MySQL: Unable to set the client character set to utf8 (\" %s\" ). Using '%s' instead." ,
1291+ mysql_error (d->mysql ),
1292+ mysql_character_set_name (d->mysql ));
1293+ }
12681294
12691295 if (mysql == d->mysql ) {
1270- if (!db.isEmpty () && mysql_select_db (d->mysql , db.toLocal8Bit ().constData ())) {
1296+ if (!db.isEmpty () && mysql_select_db (d->mysql , db.toUtf8 ().constData ())) {
12711297 setLastError (qMakeError (tr (" Unable to open database '%1'" ).arg (db), QSqlError::ConnectionError, d));
12721298 mysql_close (d->mysql );
12731299 setOpenError (true );
@@ -1279,7 +1305,7 @@ bool QMYSQLDriver::open(const QString& db,
12791305 setLastError (qMakeError (tr (" Unable to connect" ),
12801306 QSqlError::ConnectionError, d));
12811307 mysql_close (d->mysql );
1282- d->mysql = NULL ;
1308+ d->mysql = nullptr ;
12831309 setOpenError (true );
12841310 return false ;
12851311 }
@@ -1291,8 +1317,7 @@ bool QMYSQLDriver::open(const QString& db,
12911317 qWarning () << " MySQL: Unable to set the client character set to utf8." ;
12921318 }
12931319
1294- d->preparedQuerysEnabled = mysql_get_client_version () >= 40108
1295- && mysql_get_server_version (d->mysql ) >= 40100 ;
1320+ d->preparedQuerysEnabled = checkPreparedQueries (d->mysql );
12961321
12971322#if QT_CONFIG(thread)
12981323 mysql_thread_init ();
@@ -1311,7 +1336,7 @@ void QMYSQLDriver::close()
13111336 mysql_thread_end ();
13121337#endif
13131338 mysql_close (d->mysql );
1314- d->mysql = NULL ;
1339+ d->mysql = nullptr ;
13151340 setOpen (false );
13161341 setOpenError (false );
13171342 }
@@ -1375,7 +1400,7 @@ QSqlRecord QMYSQLDriver::record(const QString& tablename) const
13751400 QSqlRecord info;
13761401 if (!isOpen ())
13771402 return info;
1378- MYSQL_RES* r = mysql_list_fields (d->mysql , table.toLocal8Bit ().constData (), 0 );
1403+ MYSQL_RES* r = mysql_list_fields (d->mysql , table.toUtf8 ().constData (), 0 );
13791404 if (!r) {
13801405 return info;
13811406 }
@@ -1458,12 +1483,10 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons
14581483 if (isOpen ()) {
14591484 const QByteArray ba = field.value ().toByteArray ();
14601485 // buffer has to be at least length*2+1 bytes
1461- char * buffer = new char [ba.size () * 2 + 1 ];
1462- int escapedSize = int (mysql_real_escape_string (d->mysql , buffer,
1463- ba.data (), ba.size ()));
1486+ QVarLengthArray<char , 512 > buffer (ba.size () * 2 + 1 );
1487+ auto escapedSize = mysql_real_escape_string (d->mysql , buffer.data (), ba.data (), ba.size ());
14641488 r.reserve (escapedSize + 3 );
1465- r.append (QLatin1Char (' \' ' )).append (QString::fromUtf8 (buffer)).append (QLatin1Char (' \' ' ));
1466- delete[] buffer;
1489+ r = QLatin1Char (' \' ' ) + QString::fromUtf8 (buffer) + QLatin1Char (' \' ' );
14671490 break ;
14681491 } else {
14691492 qWarning (" QMYSQLDriver::formatValue: Database not open" );
0 commit comments