blob: 7e15cca792a6f0526e57d4ee5c2fe158abf5cb98 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtSql module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qglobal.h>
#ifdef Q_OS_WIN32 // We assume that MS SQL Server is used. Set Q_USE_SYBASE to force Sybase.
// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h
#define _WINSCARD_H_
#include <windows.h>
#else
#define Q_USE_SYBASE
#endif
#include "qsql_tds.h"
#include <qvariant.h>
#include <qdatetime.h>
#include <qhash.h>
#include <qregexp.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
#include <qsqlquery.h>
#include <qstringlist.h>
#include <qvector.h>
#include <stdlib.h>
QT_BEGIN_NAMESPACE
#ifdef DBNTWIN32
#define QMSGHANDLE DBMSGHANDLE_PROC
#define QERRHANDLE DBERRHANDLE_PROC
#define QTDSCHAR SQLCHAR
#define QTDSDATETIME4 SQLDATETIM4
#define QTDSDATETIME SQLDATETIME
#define QTDSDATETIME_N SQLDATETIMN
#define QTDSDECIMAL SQLDECIMAL
#define QTDSFLT4 SQLFLT4
#define QTDSFLT8 SQLFLT8
#define QTDSFLT8_N SQLFLTN
#define QTDSINT1 SQLINT1
#define QTDSINT2 SQLINT2
#define QTDSINT4 SQLINT4
#define QTDSINT4_N SQLINTN
#define QTDSMONEY4 SQLMONEY4
#define QTDSMONEY SQLMONEY
#define QTDSMONEY_N SQLMONEYN
#define QTDSNUMERIC SQLNUMERIC
#define QTDSTEXT SQLTEXT
#define QTDSVARCHAR SQLVARCHAR
#define QTDSBIT SQLBIT
#define QTDSBINARY SQLBINARY
#define QTDSVARBINARY SQLVARBINARY
#define QTDSIMAGE SQLIMAGE
#else
#define QMSGHANDLE MHANDLEFUNC
#define QERRHANDLE EHANDLEFUNC
#define QTDSCHAR SYBCHAR
#define QTDSDATETIME4 SYBDATETIME4
#define QTDSDATETIME SYBDATETIME
#define QTDSDATETIME_N SYBDATETIMN
#define QTDSDECIMAL SYBDECIMAL
#define QTDSFLT8 SYBFLT8
#define QTDSFLT8_N SYBFLTN
#define QTDSFLT4 SYBREAL
#define QTDSINT1 SYBINT1
#define QTDSINT2 SYBINT2
#define QTDSINT4 SYBINT4
#define QTDSINT4_N SYBINTN
#define QTDSMONEY4 SYBMONEY4
#define QTDSMONEY SYBMONEY
#define QTDSMONEY_N SYBMONEYN
#define QTDSNUMERIC SYBNUMERIC
#define QTDSTEXT SYBTEXT
#define QTDSVARCHAR SYBVARCHAR
#define QTDSBIT SYBBIT
#define QTDSBINARY SYBBINARY
#define QTDSVARBINARY SYBVARBINARY
#define QTDSIMAGE SYBIMAGE
// magic numbers not defined anywhere in Sybase headers
#define QTDSDECIMAL_2 55
#define QTDSNUMERIC_2 63
#endif //DBNTWIN32
#define TDS_CURSOR_SIZE 50
// workaround for FreeTDS
#ifndef CS_PUBLIC
#define CS_PUBLIC
#endif
QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, int errNo = -1)
{
return QSqlError(QLatin1String("QTDS: ") + err, QString(), type, errNo);
}
class QTDSDriverPrivate
{
public:
QTDSDriverPrivate(): login(0) {}
LOGINREC* login; // login information
QString hostName;
QString db;
};
class QTDSResultPrivate
{
public:
QTDSResultPrivate():login(0), dbproc(0) {}
LOGINREC* login; // login information
DBPROCESS* dbproc; // connection from app to server
QSqlError lastError;
void addErrorMsg(QString& errMsg) { errorMsgs.append(errMsg); }
QString getErrorMsgs() { return errorMsgs.join(QLatin1String("\n")); }
void clearErrorMsgs() { errorMsgs.clear(); }
QVector<void *> buffer;
QSqlRecord rec;
private:
QStringList errorMsgs;
};
typedef QHash<DBPROCESS *, QTDSResultPrivate *> QTDSErrorHash;
Q_GLOBAL_STATIC(QTDSErrorHash, errs)
extern "C" {
static int CS_PUBLIC qTdsMsgHandler (DBPROCESS* dbproc,
DBINT msgno,
int msgstate,
int severity,
char* msgtext,
char* srvname,
char* /*procname*/,
int line)
{
QTDSResultPrivate* p = errs()->value(dbproc);
if (!p) {
// ### umm... temporary disabled since this throws a lot of warnings...
// qWarning("QTDSDriver warning (%d): [%s] from server [%s]", msgstate, msgtext, srvname);
return INT_CANCEL;
}
if (severity > 0) {
QString errMsg = QString::fromLatin1("%1 (Msg %2, Level %3, State %4, Server %5, Line %6)")
.arg(QString::fromAscii(msgtext))
.arg(msgno)
.arg(severity)
.arg(msgstate)
.arg(QString::fromAscii(srvname))
.arg(line);
p->addErrorMsg(errMsg);
if (severity > 10) {
// Severe messages are really errors in the sense of lastError
errMsg = p->getErrorMsgs();
p->lastError = qMakeError(errMsg, QSqlError::UnknownError, msgno);
p->clearErrorMsgs();
}
}
return INT_CANCEL;
}
static int CS_PUBLIC qTdsErrHandler(DBPROCESS* dbproc,
int /*severity*/,
int dberr,
int /*oserr*/,
char* dberrstr,
char* oserrstr)
{
QTDSResultPrivate* p = errs()->value(dbproc);
if (!p) {
qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr);
return INT_CANCEL;
}
/*
* If the process is dead or NULL and
* we are not in the middle of logging in...
*/
if((dbproc == NULL || DBDEAD(dbproc))) {
qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr);
return INT_CANCEL;
}
QString errMsg = QString::fromLatin1("%1 %2\n").arg(QLatin1String(dberrstr)).arg(
QLatin1String(oserrstr));
errMsg += p->getErrorMsgs();
p->lastError = qMakeError(errMsg, QSqlError::UnknownError, dberr);
p->clearErrorMsgs();
return INT_CANCEL ;
}
} //extern "C"
QVariant::Type qDecodeTDSType(int type)
{
QVariant::Type t = QVariant::Invalid;
switch (type) {
case QTDSCHAR:
case QTDSTEXT:
case QTDSVARCHAR:
t = QVariant::String;
break;
case QTDSINT1:
case QTDSINT2:
case QTDSINT4:
case QTDSINT4_N:
case QTDSBIT:
t = QVariant::Int;
break;
case QTDSFLT4:
case QTDSFLT8:
case QTDSFLT8_N:
case QTDSMONEY4:
case QTDSMONEY:
case QTDSDECIMAL:
case QTDSNUMERIC:
#ifdef QTDSNUMERIC_2
case QTDSNUMERIC_2:
#endif
#ifdef QTDSDECIMAL_2
case QTDSDECIMAL_2:
#endif
case QTDSMONEY_N:
t = QVariant::Double;
break;
case QTDSDATETIME4:
case QTDSDATETIME:
case QTDSDATETIME_N:
t = QVariant::DateTime;
break;
case QTDSBINARY:
case QTDSVARBINARY:
case QTDSIMAGE:
t = QVariant::ByteArray;
break;
default:
t = QVariant::Invalid;
break;
}
return t;
}
QVariant::Type qFieldType(QTDSResultPrivate* d, int i)
{
QVariant::Type type = qDecodeTDSType(dbcoltype(d->dbproc, i+1));
return type;
}
QTDSResult::QTDSResult(const QTDSDriver* db)
: QSqlCachedResult(db)
{
d = new QTDSResultPrivate();
d->login = db->d->login;
d->dbproc = dbopen(d->login, const_cast<char*>(db->d->hostName.toLatin1().constData()));
if (!d->dbproc)
return;
if (dbuse(d->dbproc, const_cast<char*>(db->d->db.toLatin1().constData())) == FAIL)
return;
// insert d in error handler dict
errs()->insert(d->dbproc, d);
dbcmd(d->dbproc, "set quoted_identifier on");
dbsqlexec(d->dbproc);
}
QTDSResult::~QTDSResult()
{
cleanup();
if (d->dbproc)
dbclose(d->dbproc);
errs()->remove(d->dbproc);
delete d;
}
void QTDSResult::cleanup()
{
d->clearErrorMsgs();
d->rec.clear();
for (int i = 0; i < d->buffer.size() / 2; ++i)
free(d->buffer.at(i * 2));
d->buffer.clear();
// "can" stands for "cancel"... very clever.
dbcanquery(d->dbproc);
dbfreebuf(d->dbproc);
QSqlCachedResult::cleanup();
}
QVariant QTDSResult::handle() const
{
return QVariant(qRegisterMetaType<DBPROCESS *>("DBPROCESS*"), &d->dbproc);
}
static inline bool qIsNull(const void *ind)
{
return *reinterpret_cast<const DBINT *>(&ind) == -1;
}
bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
{
STATUS stat = dbnextrow(d->dbproc);
if (stat == NO_MORE_ROWS) {
setAt(QSql::AfterLastRow);
return false;
}
if ((stat == FAIL) || (stat == BUF_FULL)) {
setLastError(d->lastError);
return false;
}
if (index < 0)
return true;
for (int i = 0; i < d->rec.count(); ++i) {
int idx = index + i;
switch (d->rec.field(i).type()) {
case QVariant::DateTime:
if (qIsNull(d->buffer.at(i * 2 + 1))) {
values[idx] = QVariant(QVariant::DateTime);
} else {
DBDATETIME *bdt = (DBDATETIME*) d->buffer.at(i * 2);
QDate date = QDate::fromString(QLatin1String("1900-01-01"), Qt::ISODate);
QTime time = QTime::fromString(QLatin1String("00:00:00"), Qt::ISODate);
values[idx] = QDateTime(date.addDays(bdt->dtdays), time.addMSecs(int(bdt->dttime / 0.3)));
}
break;
case QVariant::Int:
if (qIsNull(d->buffer.at(i * 2 + 1)))
values[idx] = QVariant(QVariant::Int);
else
values[idx] = *((int*)d->buffer.at(i * 2));
break;
case QVariant::Double:
case QVariant::String:
if (qIsNull(d->buffer.at(i * 2 + 1)))
values[idx] = QVariant(QVariant::String);
else
values[idx] = QString::fromLocal8Bit((const char*)d->buffer.at(i * 2)).trimmed();
break;
case QVariant::ByteArray: {
if (qIsNull(d->buffer.at(i * 2 + 1)))
values[idx] = QVariant(QVariant::ByteArray);
else
values[idx] = QByteArray((const char*)d->buffer.at(i * 2));
break;
}
default:
// should never happen, and we already fired
// a warning while binding.
values[idx] = QVariant();
break;
}
}
return true;
}
bool QTDSResult::reset (const QString& query)
{
cleanup();
if (!driver() || !driver()-> isOpen() || driver()->isOpenError())
return false;
setActive(false);
setAt(QSql::BeforeFirstRow);
if (dbcmd(d->dbproc, const_cast<char*>(query.toLocal8Bit().constData())) == FAIL) {
setLastError(d->lastError);
return false;
}
if (dbsqlexec(d->dbproc) == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
if (dbresults(d->dbproc) != SUCCEED) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
setSelect((DBCMDROW(d->dbproc) == SUCCEED)); // decide whether or not we are dealing with a SELECT query
int numCols = dbnumcols(d->dbproc);
if (numCols > 0) {
d->buffer.resize(numCols * 2);
init(numCols);
}
for (int i = 0; i < numCols; ++i) {
int dbType = dbcoltype(d->dbproc, i+1);
QVariant::Type vType = qDecodeTDSType(dbType);
QSqlField f(QString::fromAscii(dbcolname(d->dbproc, i+1)), vType);
f.setSqlType(dbType);
f.setLength(dbcollen(d->dbproc, i+1));
d->rec.append(f);
RETCODE ret = -1;
void* p = 0;
switch (vType) {
case QVariant::Int:
p = malloc(4);
ret = dbbind(d->dbproc, i+1, INTBIND, (DBINT) 4, (unsigned char *)p);
break;
case QVariant::Double:
// use string binding to prevent loss of precision
p = malloc(50);
ret = dbbind(d->dbproc, i+1, STRINGBIND, 50, (unsigned char *)p);
break;
case QVariant::String:
p = malloc(dbcollen(d->dbproc, i+1) + 1);
ret = dbbind(d->dbproc, i+1, STRINGBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p);
break;
case QVariant::DateTime:
p = malloc(8);
ret = dbbind(d->dbproc, i+1, DATETIMEBIND, (DBINT) 8, (unsigned char *)p);
break;
case QVariant::ByteArray:
p = malloc(dbcollen(d->dbproc, i+1) + 1);
ret = dbbind(d->dbproc, i+1, BINARYBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p);
break;
default: //don't bind the field since we do not support it
qWarning("QTDSResult::reset: Unsupported type for field \"%s\"", dbcolname(d->dbproc, i+1));
break;
}
if (ret == SUCCEED) {
d->buffer[i * 2] = p;
ret = dbnullbind(d->dbproc, i+1, (DBINT*)(&d->buffer[i * 2 + 1]));
} else {
d->buffer[i * 2] = 0;
d->buffer[i * 2 + 1] = 0;
free(p);
}
if ((ret != SUCCEED) && (ret != -1)) {
setLastError(d->lastError);
return false;
}
}
setActive(true);
return true;
}
int QTDSResult::size()
{
return -1;
}
int QTDSResult::numRowsAffected()
{
#ifdef DBNTWIN32
if (dbiscount(d->dbproc)) {
return DBCOUNT(d->dbproc);
}
return -1;
#else
return DBCOUNT(d->dbproc);
#endif
}
QSqlRecord QTDSResult::record() const
{
return d->rec;
}
///////////////////////////////////////////////////////////////////
QTDSDriver::QTDSDriver(QObject* parent)
: QSqlDriver(parent)
{
init();
}
QTDSDriver::QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent)
: QSqlDriver(parent)
{
init();
d->login = rec;
d->hostName = host;
d->db = db;
if (rec) {
setOpen(true);
setOpenError(false);
}
}
QVariant QTDSDriver::handle() const
{
return QVariant(qRegisterMetaType<LOGINREC *>("LOGINREC*"), &d->login);
}
void QTDSDriver::init()
{
d = new QTDSDriverPrivate();
// the following two code-lines will fail compilation on some FreeTDS versions
// just comment them out if you have FreeTDS (you won't get any errors and warnings then)
dberrhandle((QERRHANDLE)qTdsErrHandler);
dbmsghandle((QMSGHANDLE)qTdsMsgHandler);
}
QTDSDriver::~QTDSDriver()
{
dberrhandle(0);
dbmsghandle(0);
// dbexit also calls dbclose if necessary
dbexit();
delete d;
}
bool QTDSDriver::hasFeature(DriverFeature f) const
{
switch (f) {
case Transactions:
case QuerySize:
case Unicode:
case SimpleLocking:
case EventNotifications:
case MultipleResultSets:
return false;
case BLOB:
return true;
default:
return false;
}
}
bool QTDSDriver::open(const QString & db,
const QString & user,
const QString & password,
const QString & host,
int /*port*/,
const QString& /*connOpts*/)
{
if (isOpen())
close();
if (!dbinit()) {
setOpenError(true);
return false;
}
d->login = dblogin();
if (!d->login) {
setOpenError(true);
return false;
}
DBSETLPWD(d->login, const_cast<char*>(password.toLocal8Bit().constData()));
DBSETLUSER(d->login, const_cast<char*>(user.toLocal8Bit().constData()));
// Now, try to open and use the database. If this fails, return false.
DBPROCESS* dbproc;
dbproc = dbopen(d->login, const_cast<char*>(host.toLatin1().constData()));
if (!dbproc) {
setLastError(qMakeError(tr("Unable to open connection"), QSqlError::ConnectionError, -1));
setOpenError(true);
return false;
}
if (dbuse(dbproc, const_cast<char*>(db.toLatin1().constData())) == FAIL) {
setLastError(qMakeError(tr("Unable to use database"), QSqlError::ConnectionError, -1));
setOpenError(true);
return false;
}
dbclose( dbproc );
setOpen(true);
setOpenError(false);
d->hostName = host;
d->db = db;
return true;
}
void QTDSDriver::close()
{
if (isOpen()) {
#ifdef Q_USE_SYBASE
dbloginfree(d->login);
#else
dbfreelogin(d->login);
#endif
d->login = 0;
setOpen(false);
setOpenError(false);
}
}
QSqlResult *QTDSDriver::createResult() const
{
return new QTDSResult(this);
}
bool QTDSDriver::beginTransaction()
{
return false;
/*
if (!isOpen()) {
qWarning("QTDSDriver::beginTransaction: Database not open");
return false;
}
if (dbcmd(d->dbproc, "BEGIN TRANSACTION") == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
if (dbsqlexec(d->dbproc) == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
dbfreebuf(d->dbproc);
inTransaction = true;
return true;
*/
}
bool QTDSDriver::commitTransaction()
{
return false;
/*
if (!isOpen()) {
qWarning("QTDSDriver::commitTransaction: Database not open");
return false;
}
if (dbcmd(d->dbproc, "COMMIT TRANSACTION") == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
if (dbsqlexec(d->dbproc) == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
dbfreebuf(d->dbproc);
inTransaction = false;
return true;
*/
}
bool QTDSDriver::rollbackTransaction()
{
return false;
/*
if (!isOpen()) {
qWarning("QTDSDriver::rollbackTransaction: Database not open");
return false;
}
if (dbcmd(d->dbproc, "ROLLBACK TRANSACTION") == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
if (dbsqlexec(d->dbproc) == FAIL) {
setLastError(d->lastError);
dbfreebuf(d->dbproc);
return false;
}
while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
dbfreebuf(d->dbproc);
inTransaction = false;
return true;
*/
}
QSqlRecord QTDSDriver::record(const QString& tablename) const
{
QSqlRecord info;
if (!isOpen())
return info;
QSqlQuery t(createResult());
t.setForwardOnly(true);
QString table = tablename;
if (isIdentifierEscaped(table, QSqlDriver::TableName))
table = stripDelimiters(table, QSqlDriver::TableName);
QString stmt (QLatin1String("select name, type, length, prec from syscolumns "
"where id = (select id from sysobjects where name = '%1')"));
t.exec(stmt.arg(table));
while (t.next()) {
QSqlField f(t.value(0).toString().simplified(), qDecodeTDSType(t.value(1).toInt()));
f.setLength(t.value(2).toInt());
f.setPrecision(t.value(3).toInt());
f.setSqlType(t.value(1).toInt());
info.append(f);
}
return info;
}
QStringList QTDSDriver::tables(QSql::TableType type) const
{
QStringList list;
if (!isOpen())
return list;
QStringList typeFilter;
if (type & QSql::Tables)
typeFilter += QLatin1String("type='U'");
if (type & QSql::SystemTables)
typeFilter += QLatin1String("type='S'");
if (type & QSql::Views)
typeFilter += QLatin1String("type='V'");
if (typeFilter.isEmpty())
return list;
QSqlQuery t(createResult());
t.setForwardOnly(true);
t.exec(QLatin1String("select name from sysobjects where ") + typeFilter.join(QLatin1String(" or ")));
while (t.next())
list.append(t.value(0).toString().simplified());
return list;
}
QString QTDSDriver::formatValue(const QSqlField &field,
bool trim) const
{
QString r;
if (field.isNull())
r = QLatin1String("NULL");
else if (field.type() == QVariant::DateTime) {
if (field.value().toDateTime().isValid()){
r = field.value().toDateTime().toString(QLatin1String("yyyyMMdd hh:mm:ss"));
r.prepend(QLatin1String("'"));
r.append(QLatin1String("'"));
} else
r = QLatin1String("NULL");
} else if (field.type() == QVariant::ByteArray) {
QByteArray ba = field.value().toByteArray();
QString res;
static const char hexchars[] = "0123456789abcdef";
for (int i = 0; i < ba.size(); ++i) {
uchar s = (uchar) ba[i];
res += QLatin1Char(hexchars[s >> 4]);
res += QLatin1Char(hexchars[s & 0x0f]);
}
r = QLatin1String("0x") + res;
} else {
r = QSqlDriver::formatValue(field, trim);
}
return r;
}
QSqlIndex QTDSDriver::primaryIndex(const QString& tablename) const
{
QSqlRecord rec = record(tablename);
QString table = tablename;
if (isIdentifierEscaped(table, QSqlDriver::TableName))
table = stripDelimiters(table, QSqlDriver::TableName);
QSqlIndex idx(table);
if ((!isOpen()) || (table.isEmpty()))
return QSqlIndex();
QSqlQuery t(createResult());
t.setForwardOnly(true);
t.exec(QString::fromLatin1("sp_helpindex '%1'").arg(table));
if (t.next()) {
QStringList fNames = t.value(2).toString().simplified().split(QLatin1Char(','));
QRegExp regx(QLatin1String("\\s*(\\S+)(?:\\s+(DESC|desc))?\\s*"));
for(QStringList::Iterator it = fNames.begin(); it != fNames.end(); ++it) {
regx.indexIn(*it);
QSqlField f(regx.cap(1), rec.field(regx.cap(1)).type());
if (regx.cap(2).toLower() == QLatin1String("desc")) {
idx.append(f, true);
} else {
idx.append(f, false);
}
}
idx.setName(t.value(0).toString().simplified());
}
return idx;
}
QString QTDSDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
{
QString res = identifier;
if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
res.replace(QLatin1Char('"'), QLatin1String("\"\""));
res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
res.replace(QLatin1Char('.'), QLatin1String("\".\""));
}
return res;
}
QT_END_NAMESPACE