QSqlField: Add a means to see what the tablename is for a given field

When you are using a query that pulls from a number of different tables
then it can be ambiguous as to which table a particular field belongs to.
So this will make it possible to determine the table that a given field
belongs to if it is set.

Task-number: QTBUG-7170
Change-Id: I49b7890c0523d81272a153df3860df800ff853d5
Reviewed-by: Jesus Fernandez <Jesus.Fernandez@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Andy Shaw 2017-05-19 15:31:46 +02:00
parent 9423be1f19
commit 0843c6ca7f
14 changed files with 146 additions and 32 deletions

View File

@ -1,5 +1,5 @@
CONFIG(release, debug|release):DEFINES *= NDEBUG
DEFINES += SQLITE_OMIT_LOAD_EXTENSION SQLITE_OMIT_COMPLETE SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_FTS5 SQLITE_ENABLE_RTREE
DEFINES += SQLITE_ENABLE_COLUMN_METADATA SQLITE_OMIT_LOAD_EXTENSION SQLITE_OMIT_COMPLETE SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_FTS5 SQLITE_ENABLE_RTREE
!contains(CONFIG, largefile):DEFINES += SQLITE_DISABLE_LFS
qtConfig(posix_fallocate): DEFINES += HAVE_POSIX_FALLOCATE=1
winrt: DEFINES += SQLITE_OS_WINRT

View File

@ -66,6 +66,10 @@
QT_BEGIN_NAMESPACE
static const int COLNAMESIZE = 255;
// Based on what is mentioned in the documentation here:
// https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/sqlref/src/tpc/db2z_limits.html
// The limit is 128 bytes for table names
static const SQLSMALLINT TABLENAMESIZE = 128;
static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
class QDB2DriverPrivate : public QSqlDriverPrivate
@ -297,6 +301,12 @@ static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
f.setSqlType(int(colType));
SQLTCHAR tableName[TABLENAMESIZE];
SQLSMALLINT tableNameLen;
r = SQLColAttribute(d->hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName,
TABLENAMESIZE, &tableNameLen, 0);
if (r == SQL_SUCCESS)
f.setTableName(qFromTChar(tableName));
return f;
}
@ -1394,7 +1404,9 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const
SQL_FETCH_NEXT,
0);
while (r == SQL_SUCCESS) {
fil.append(qMakeFieldInfo(hStmt));
QSqlField fld = qMakeFieldInfo(hStmt);
fld.setTableName(tableName);
fil.append(fld);
r = SQLFetchScroll(hStmt,
SQL_FETCH_NEXT,
0);

View File

@ -1382,7 +1382,8 @@ QSqlRecord QIBaseResult::record() const
for (int i = 0; i < d->sqlda->sqld; ++i) {
v = d->sqlda->sqlvar[i];
QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(),
qIBaseTypeName2(v.sqltype, v.sqlscale < 0));
qIBaseTypeName2(v.sqltype, v.sqlscale < 0),
QString::fromLatin1(v.relname, v.relname_length));
f.setLength(v.sqllen);
f.setPrecision(qAbs(v.sqlscale));
f.setRequiredStatus((v.sqltype & 1) == 0 ? QSqlField::Required : QSqlField::Optional);
@ -1685,7 +1686,7 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const
while (q.next()) {
int type = q.value(1).toInt();
bool hasScale = q.value(3).toInt() < 0;
QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale));
QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale), tablename);
if(hasScale) {
f.setLength(q.value(4).toInt());
f.setPrecision(qAbs(q.value(3).toInt()));
@ -1726,7 +1727,9 @@ QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const
"ORDER BY b.RDB$FIELD_POSITION"));
while (q.next()) {
QSqlField field(q.value(1).toString().simplified(), qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0));
QSqlField field(q.value(1).toString().simplified(),
qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0),
tablename);
index.append(field); //TODO: asc? desc?
index.setName(q.value(0).toString());
}

View File

@ -331,7 +331,8 @@ static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags)
static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc)
{
QSqlField f(toUnicode(tc, field->name),
qDecodeMYSQLType(int(field->type), field->flags));
qDecodeMYSQLType(int(field->type), field->flags),
toUnicode(tc, field->table));
f.setRequired(IS_NOT_NULL(field->flags));
f.setLength(field->length);
f.setPrecision(field->decimals);

View File

@ -64,6 +64,7 @@ QT_BEGIN_NAMESPACE
#define ODBC_CHECK_DRIVER
static const int COLNAMESIZE = 256;
static const SQLSMALLINT TABLENAMESIZE = 128;
//Map Qt parameter types to ODBC types
static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
@ -730,6 +731,12 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
f.setRequired(false);
// else we don't know
f.setAutoValue(isAutoValue(hStmt, i));
QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE);
SQLSMALLINT tableNameLen;
r = SQLColAttribute(hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName.data(),
TABLENAMESIZE, &tableNameLen, 0);
if (r == SQL_SUCCESS)
f.setTableName(fromSQLTCHAR(tableName, tableNameLen));
return f;
}

View File

@ -552,6 +552,11 @@ QSqlRecord QPSQLResult::record() const
f.setName(QString::fromUtf8(PQfname(d->result, i)));
else
f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
QSqlQuery qry(driver()->createResult());
if (qry.exec(QStringLiteral("SELECT relname FROM pg_class WHERE pg_class.oid = %1")
.arg(PQftable(d->result, i))) && qry.next()) {
f.setTableName(qry.value(0).toString());
}
int ptype = PQftype(d->result, i);
f.setType(qDecodePSQLType(ptype));
int len = PQfsize(d->result, i);
@ -1132,7 +1137,7 @@ QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const
i.exec(stmt.arg(tbl));
while (i.isActive() && i.next()) {
QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()));
QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()), tablename);
idx.append(f);
idx.setName(i.value(2).toString());
}
@ -1237,7 +1242,7 @@ QSqlRecord QPSQLDriver::record(const QString& tablename) const
QString defVal = query.value(5).toString();
if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
defVal = defVal.mid(1, defVal.length() - 2);
QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()), tablename);
f.setRequired(query.value(2).toBool());
f.setLength(len);
f.setPrecision(precision);
@ -1264,7 +1269,7 @@ QSqlRecord QPSQLDriver::record(const QString& tablename) const
len = precision - 4;
precision = -1;
}
QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()), tablename);
f.setRequired(query.value(2).toBool());
f.setLength(len);
f.setPrecision(precision);

View File

@ -212,7 +212,9 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
QString colName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_name16(stmt, i))
).remove(QLatin1Char('"'));
const QString tableName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_table_name16(stmt, i))
).remove(QLatin1Char('"'));
// must use typeName for resolving the type to match QSqliteDriver::record
QString typeName = QString(reinterpret_cast<const QChar *>(
sqlite3_column_decltype16(stmt, i)));
@ -245,7 +247,7 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
}
}
QSqlField fld(colName, fieldType);
QSqlField fld(colName, fieldType, tableName);
fld.setSqlType(stp);
rInf.append(fld);
}
@ -872,7 +874,7 @@ static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool only
if (onlyPIndex && !isPk)
continue;
QString typeName = q.value(2).toString().toLower();
QSqlField fld(q.value(1).toString(), qGetColumnType(typeName));
QSqlField fld(q.value(1).toString(), qGetColumnType(typeName), tableName);
if (isPk && (typeName == QLatin1String("integer")))
// INTEGER PRIMARY KEY fields are auto-generated in sqlite
// INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!

View File

@ -576,7 +576,7 @@ QSqlIndex QSQLite2Driver::primaryIndex(const QString &tblname) const
QVariant::Type type = QVariant::Invalid;
if (rec.contains(name))
type = rec.field(name).type();
index.append(QSqlField(name, type));
index.append(QSqlField(name, type, tblname));
}
return index;
}

View File

@ -767,7 +767,7 @@ QSqlRecord QTDSDriver::record(const QString& tablename) const
"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()));
QSqlField f(t.value(0).toString().simplified(), qDecodeTDSType(t.value(1).toInt()), tablename);
f.setLength(t.value(2).toInt());
f.setPrecision(t.value(3).toInt());
f.setSqlType(t.value(1).toInt());
@ -853,7 +853,7 @@ QSqlIndex QTDSDriver::primaryIndex(const QString& tablename) const
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());
QSqlField f(regx.cap(1), rec.field(regx.cap(1)).type(), tablename);
if (regx.cap(2).toLower() == QLatin1String("desc")) {
idx.append(f, true);
} else {

View File

@ -47,9 +47,9 @@ class QSqlFieldPrivate
{
public:
QSqlFieldPrivate(const QString &name,
QVariant::Type type) :
QVariant::Type type, const QString &tableName) :
ref(1), nm(name), ro(false), type(type), req(QSqlField::Unknown),
len(-1), prec(-1), tp(-1), gen(true), autoval(false)
len(-1), prec(-1), tp(-1), gen(true), autoval(false), table(tableName)
{
}
@ -64,7 +64,8 @@ public:
def(other.def),
tp(other.tp),
gen(other.gen),
autoval(other.autoval)
autoval(other.autoval),
table(other.table)
{}
bool operator==(const QSqlFieldPrivate& other) const
@ -77,7 +78,8 @@ public:
&& prec == other.prec
&& def == other.def
&& gen == other.gen
&& autoval == other.autoval);
&& autoval == other.autoval
&& table == other.table);
}
QAtomicInt ref;
@ -91,6 +93,7 @@ public:
int tp;
uint gen: 1;
uint autoval: 1;
QString table;
};
@ -153,14 +156,15 @@ public:
/*!
Constructs an empty field called \a fieldName of variant type \a
type.
type in \a table.
\sa setRequiredStatus(), setLength(), setPrecision(), setDefaultValue(),
setGenerated(), setReadOnly()
*/
QSqlField::QSqlField(const QString& fieldName, QVariant::Type type)
QSqlField::QSqlField(const QString &fieldName, QVariant::Type type,
const QString &table)
{
d = new QSqlFieldPrivate(fieldName, type);
d = new QSqlFieldPrivate(fieldName, type, table);
val = QVariant(type);
}
@ -518,6 +522,7 @@ QDebug operator<<(QDebug dbg, const QSqlField &f)
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg << "QSqlField(" << f.name() << ", " << QMetaType::typeName(f.type());
dbg << ", tableName: " << (f.tableName().isEmpty() ? QStringLiteral("(not specified)") : f.tableName());
if (f.length() >= 0)
dbg << ", length: " << f.length();
if (f.precision() >= 0)
@ -565,4 +570,27 @@ void QSqlField::setAutoValue(bool autoVal)
d->autoval = autoVal;
}
/*!
Sets the tableName of the field to \a table.
\sa tableName()
*/
void QSqlField::setTableName(const QString &table)
{
detach();
d->table = table;
}
/*!
Returns the tableName of the field.
\sa setTableName()
*/
QString QSqlField::tableName() const
{
return d->table;
}
QT_END_NAMESPACE

View File

@ -55,7 +55,8 @@ public:
enum RequiredStatus { Unknown = -1, Optional = 0, Required = 1 };
explicit QSqlField(const QString& fieldName = QString(),
QVariant::Type type = QVariant::Invalid);
QVariant::Type type = QVariant::Invalid,
const QString &tableName = QString());
QSqlField(const QSqlField& other);
QSqlField& operator=(const QSqlField& other);
@ -68,6 +69,8 @@ public:
{ return val; }
void setName(const QString& name);
QString name() const;
void setTableName(const QString &tableName);
QString tableName() const;
bool isNull() const;
void setReadOnly(bool readOnly);
bool isReadOnly() const;

View File

@ -232,10 +232,23 @@ QString QSqlRecord::fieldName(int index) const
int QSqlRecord::indexOf(const QString& name) const
{
QString nm = name.toUpper();
QString tableName;
QString fieldName = name;
const int idx = name.indexOf(QLatin1Char('.'));
if (idx != -1) {
tableName = name.left(idx);
fieldName = name.mid(idx + 1);
}
for (int i = 0; i < count(); ++i) {
if (d->fields.at(i).name().toUpper() == nm) // TODO: case-insensitive comparison
// Check the passed in name first in case it is an alias using a dot.
// Then check if both the table and field match when there is a table name specified.
const auto currentField = d->fields.at(i);
const auto currentFieldName = currentField.name();
if (currentFieldName.compare(name, Qt::CaseInsensitive) == 0
|| (idx != -1 && currentFieldName.compare(fieldName, Qt::CaseInsensitive) == 0
&& currentField.tableName().compare(tableName, Qt::CaseInsensitive) == 0)) {
return i;
}
}
return -1;
}

View File

@ -62,6 +62,8 @@ private slots:
void isNull();
void clear_data();
void clear();
void setTableName_data();
void setTableName();
};
// Testing get/set functions
@ -212,6 +214,9 @@ void tst_QSqlField::operator_Assign()
field3.clear();
field1 = field3;
QVERIFY( field1 == field3 );
QSqlField field4("test", QVariant::String, "ATable");
field1 = field4;
QVERIFY(field1 == field4);
}
void tst_QSqlField::operator_Equal()
@ -219,8 +224,18 @@ void tst_QSqlField::operator_Equal()
QSqlField field1( "test", QVariant::String );
QSqlField field2( "test2", QVariant::String );
QSqlField field3( "test", QVariant::Int );
QSqlField field4("test", QVariant::String, QString("ATable"));
QSqlField field5("test2", QVariant::String, QString("ATable"));
QSqlField field6("test", QVariant::String, QString("BTable"));
QVERIFY( !(field1 == field2) );
QVERIFY( !(field1 == field3) );
QVERIFY(field1 != field4);
QVERIFY(field1 != field5);
QVERIFY(field1 != field6);
QVERIFY(field4 != field5);
QVERIFY(field4 != field6);
field2.setName( "test" );
QVERIFY( field1 == field2 );
QVERIFY( field1 == field2 );
@ -232,6 +247,10 @@ void tst_QSqlField::operator_Equal()
QVERIFY( !(field1 == field2) );
field2.setReadOnly( true );
QVERIFY( field1 == field2 );
field4.setTableName("BTable");
QCOMPARE(field4, field6);
field6.setName("test3");
QVERIFY(field4 != field6);
}
void tst_QSqlField::setName_data()
@ -333,5 +352,22 @@ void tst_QSqlField::type()
QVERIFY( field3.type() == QVariant::Double );
}
void tst_QSqlField::setTableName_data()
{
QTest::addColumn<QString>("tableName");
QTest::newRow("data0") << QString("");
QTest::newRow("data1") << QString("tbl");
}
void tst_QSqlField::setTableName()
{
QSqlField field("test", QVariant::String, "test");
QFETCH(QString, tableName);
QCOMPARE(field.tableName(), QLatin1String("test"));
field.setTableName(tableName);
QCOMPARE(field.tableName(), tableName);
}
QTEST_MAIN(tst_QSqlField)
#include "tst_qsqlfield.moc"

View File

@ -111,10 +111,10 @@ void tst_QSqlRecord::createTestRecord()
{
delete rec;
rec = new QSqlRecord();
fields[ 0 ] = new QSqlField( "string", QVariant::String );
fields[ 1 ] = new QSqlField( "int", QVariant::Int );
fields[ 2 ] = new QSqlField( "double", QVariant::Double );
fields[ 3 ] = new QSqlField( "bool", QVariant::Bool );
fields[0] = new QSqlField(QStringLiteral("string"), QVariant::String, QStringLiteral("stringtable"));
fields[1] = new QSqlField(QStringLiteral("int"), QVariant::Int, QStringLiteral("inttable"));
fields[2] = new QSqlField(QStringLiteral("double"), QVariant::Double, QStringLiteral("doubletable"));
fields[3] = new QSqlField(QStringLiteral("bool"), QVariant::Bool);
for ( int i = 0; i < NUM_FIELDS; ++i )
rec->append( *(fields[ i ] ) );
}
@ -124,12 +124,14 @@ void tst_QSqlRecord::append()
{
delete rec;
rec = new QSqlRecord();
rec->append( QSqlField( "string", QVariant::String ) );
rec->append(QSqlField("string", QVariant::String, QStringLiteral("stringtable")));
QCOMPARE( rec->field( 0 ).name(), (QString) "string" );
QCOMPARE(rec->field(0).tableName(), QStringLiteral("stringtable"));
QVERIFY( !rec->isEmpty() );
QCOMPARE( (int)rec->count(), 1 );
rec->append( QSqlField( "int", QVariant::Int ) );
rec->append(QSqlField("int", QVariant::Int, QStringLiteral("inttable")));
QCOMPARE( rec->field( 1 ).name(), (QString) "int" );
QCOMPARE(rec->field(1).tableName(), QStringLiteral("inttable"));
QCOMPARE( (int)rec->count(), 2 );
rec->append( QSqlField( "double", QVariant::Double ) );
QCOMPARE( rec->field( 2 ).name(), (QString) "double" );
@ -381,7 +383,7 @@ void tst_QSqlRecord::operator_Assign()
buf3.remove( NUM_FIELDS - 1 );
QSqlRecord buf5 = buf3;
for ( i = 0; i < NUM_FIELDS - 1; ++i ) {
QSqlField fi ( fields[ i ]->name(), fields[ i ]->type() );
QSqlField fi(fields[i]->name(), fields[i]->type(), fields[i]->tableName());
fi.clear();
QVERIFY( buf5.field( i ) == fi );
QVERIFY( buf5.isGenerated( i ) );
@ -394,6 +396,8 @@ void tst_QSqlRecord::position()
int i;
for ( i = 0; i < NUM_FIELDS; ++i ) {
QCOMPARE( rec->indexOf( fields[ i ]->name() ), i );
if (!fields[i]->tableName().isEmpty())
QCOMPARE(rec->indexOf(fields[i]->tableName() + QChar('.') + fields[i]->name()), i);
}
}