diff --git a/autotests/ExpressionsTest.cpp b/autotests/ExpressionsTest.cpp index d237b4d7..02e473b2 100644 --- a/autotests/ExpressionsTest.cpp +++ b/autotests/ExpressionsTest.cpp @@ -1,1430 +1,1430 @@ /* This file is part of the KDE project Copyright (C) 2011-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ExpressionsTest.h" #include #include #include "parser/generated/sqlparser.h" #include "parser/KDbParser_p.h" Q_DECLARE_METATYPE(KDb::ExpressionClass) Q_DECLARE_METATYPE(KDbEscapedString) Q_DECLARE_METATYPE(KDbField::Type) Q_DECLARE_METATYPE(KDbToken) namespace QTest { template<> char *toString(const KDbEscapedString &string) { return qstrdup(qPrintable(string.toString())); } template<> char *toString(const KDbField::Type &type) { return qstrdup(qPrintable(KDbField::typeString(type))); } //! Adds a quote if this is the single-character token to match the format of Bison template<> char* toString(const KDbToken &token) { return qstrdup(qPrintable( token.toChar() ? QString::fromLatin1("'%1'").arg(token.toString()) : token.toString() )); } } QTEST_GUILESS_MAIN(ExpressionsTest) //! Used in macros so characters and KDbTokens can be used interchangeably static inline KDbToken TO_TOKEN(char charValue) { return KDbToken(charValue); } //! Used in macros so characters and KDbTokens can be used interchangeably static inline KDbToken TO_TOKEN(KDbToken token) { return token; } void ExpressionsTest::initTestCase() { } //! compares two expression @a e1 and @a e2 based on strings/debug strings //! and token strings template static void compareStrings(const T1 &e1, const T2 &e2) { //qDebug() << "compareStrings():" // << "\ne1:" << e1.toString() << e1.token() << e1.token().ToString() // << "\ne2:" << e2.toString() << e2.token() << e2.token().toString(); - QCOMPARE(e1.toString(0), e2.toString(0)); + QCOMPARE(e1.toString(nullptr), e2.toString(nullptr)); QCOMPARE(e1.token(), e2.token()); QCOMPARE(e1.token().value(), e2.token().value()); QCOMPARE(e1.token().toString(), e2.token().toString()); } //! tests clone and copy ctor for @a e1 template static void testCloneExpression(const T &e1) { KDbExpression e1clone = e1.clone(); //qDebug() << e1; //qDebug() << e1clone; QVERIFY(e1 != e1.clone()); QVERIFY(e1 != e1clone); QVERIFY(e1.clone() != e1clone); QVERIFY(e1.expressionClass() == e1clone.expressionClass()); QVERIFY(e1.token() == e1clone.token()); compareStrings(e1, e1clone); const T copied(e1); QVERIFY(e1 == copied); QVERIFY(e1.clone() != copied); QVERIFY(e1.expressionClass() == copied.expressionClass()); QVERIFY(e1.token() == copied.token()); compareStrings(e1, copied); } //! Validates expression @a expr and shows error message on failure static bool validate(KDbExpression *expr) { - KDbParseInfoInternal parseInfo(0); + KDbParseInfoInternal parseInfo(nullptr); bool ok = expr->validate(&parseInfo); if (!ok) { qDebug() << "Validation of" << *expr << "FAILED."; if (!parseInfo.errorMessage().isEmpty()) { qDebug() << "Error message:" << parseInfo.errorMessage(); } if (!parseInfo.errorDescription().isEmpty()) { qDebug() << "Error description:" << parseInfo.errorDescription(); } } return ok; } void ExpressionsTest::testNullExpression() { QVERIFY(KDbExpression() != KDbExpression()); KDbExpression e1; KDbExpression e2; QVERIFY(e1.isNull()); QVERIFY(!e1.isValid()); QVERIFY(!e1.isBinary()); QVERIFY(e1.toBinary().isNull()); QVERIFY(!e1.isConst()); QVERIFY(e1.toConst().isNull()); QVERIFY(!e1.isFunction()); QVERIFY(e1.toFunction().isNull()); QVERIFY(!e1.isNArg()); QVERIFY(e1.toNArg().isNull()); QVERIFY(!e1.isQueryParameter()); QVERIFY(e1.toQueryParameter().isNull()); QVERIFY(!e1.isUnary()); QVERIFY(e1.toUnary().isNull()); QVERIFY(!e1.isVariable()); QVERIFY(e1.toVariable().isNull()); QCOMPARE(e1.expressionClass(), KDb::UnknownExpression); QCOMPARE(e1.token(), KDbToken()); QVERIFY(e1 != KDbExpression()); QVERIFY(e1 == e1); QVERIFY(e1 != e2); e1 = e2; QVERIFY(e1.isNull()); QCOMPARE(e1, e2); - QCOMPARE(e1.toString(0), KDbEscapedString("")); + QCOMPARE(e1.toString(nullptr), KDbEscapedString("")); QCOMPARE(e1.token().name(), QLatin1String("")); - QCOMPARE(e1.token().toString(0), QString("")); + QCOMPARE(e1.token().toString(nullptr), QString("")); compareStrings(e1, e2); KDbExpression e3(e2); QVERIFY(e3.isNull()); QCOMPARE(e2, e3); compareStrings(e2, e3); //ExpressionDebug << "$$$" << e1.toString() << e1.token() << e1.token().toString(); e1 = KDbExpression(); testCloneExpression(e1); } void ExpressionsTest::testExpressionClassName_data() { QTest::addColumn("expClass"); QTest::addColumn("name"); int c = 0; #define T(n, t) ++c; QTest::newRow(n) << t << n T("Unknown", KDb::UnknownExpression); T("Unary", KDb::UnaryExpression); T("Arithm", KDb::ArithmeticExpression); T("Logical", KDb::LogicalExpression); T("Relational", KDb::RelationalExpression); T("SpecialBinary", KDb::SpecialBinaryExpression); T("Const", KDb::ConstExpression); T("Variable", KDb::VariableExpression); T("Function", KDb::FunctionExpression); T("Aggregation", KDb::AggregationExpression); T("FieldList", KDb::FieldListExpression); T("TableList", KDb::TableListExpression); T("ArgumentList", KDb::ArgumentListExpression); T("QueryParameter", KDb::QueryParameterExpression); #undef T QCOMPARE(c, int(KDb::LastExpressionClass) + 1); } void ExpressionsTest::testExpressionClassName() { QFETCH(KDb::ExpressionClass, expClass); QTEST(expressionClassName(expClass), "name"); } #include "KDbUtils_p.h" void ExpressionsTest::testExpressionToken() { KDbExpression e1; QVERIFY(!e1.isValid()); QVERIFY(!KDbToken().isValid()); QCOMPARE(e1.token(), KDbToken()); QVERIFY(KDbToken('+').toChar() > 0); QCOMPARE(KDbToken('*'), KDbToken('*')); QCOMPARE(KDbToken('*').toChar(), '*'); QCOMPARE(KDbToken('*').value(), int('*')); QCOMPARE(KDbToken('*').name(), QString::fromLatin1("*")); QCOMPARE(KDbToken('*').toString(), QString::fromLatin1("*")); QCOMPARE(KDbToken::LEFT.toChar(), char(0)); QCOMPARE(KDbToken().toChar(), char(0)); QVERIFY(KDbToken::LEFT.isValid()); QVERIFY(KDbToken::maxCharTokenValue > 0); QVERIFY(KDbToken::LEFT.value() > KDbToken::maxCharTokenValue); const QList allTokens(KDbToken::allTokens()); QVERIFY(!allTokens.isEmpty()); for(const KDbToken &t : allTokens) { //qDebug() << t << t.value(); if (t.toChar() > 0) { QVERIFY(t.value() <= KDbToken::maxCharTokenValue); QCOMPARE(t, KDbToken(char(t.value()))); QCOMPARE(t.name(), isprint(t.value()) ? QString(QLatin1Char(uchar(t.value()))) : QString::number(t.value())); QCOMPARE(QTest::toString(t), QString::fromLatin1(g_tokenName(t.value())).toLatin1().data()); } else { QCOMPARE(t.name(), QString::fromLatin1(g_tokenName(t.value()))); } } } void ExpressionsTest::testNArgExpression() { KDbNArgExpression n; KDbNArgExpression n2; KDbConstExpression c; KDbConstExpression c1; KDbConstExpression c2; KDbConstExpression c3; // -- empty KDbNArgExpression emptyNarg; QVERIFY(emptyNarg.isNArg()); QVERIFY(emptyNarg.clone().isNArg()); QVERIFY(emptyNarg.isEmpty()); QCOMPARE(emptyNarg.argCount(), 0); QVERIFY(emptyNarg.arg(-1).isNull()); QVERIFY(emptyNarg.arg(0).isNull()); QVERIFY(!emptyNarg.containsInvalidArgument()); QVERIFY(!emptyNarg.containsNullArgument()); // -- copy ctor & cloning n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 7); c2 = KDbConstExpression(KDbToken::INTEGER_CONST, 8); n.append(c1); n.append(c2); testCloneExpression(n); // copy on stack n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); { KDbConstExpression s1(KDbToken::INTEGER_CONST, 7); KDbConstExpression s2(KDbToken::INTEGER_CONST, 8); n.append(s1); n.append(s2); c1 = s1; c2 = s2; } QCOMPARE(n.argCount(), 2); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(n.arg(1).toConst(), c2); QCOMPARE(n.token().name(), QString("+")); - QCOMPARE(n.toString(0), KDbEscapedString("7, 8")); + QCOMPARE(n.toString(nullptr), KDbEscapedString("7, 8")); n.setToken('*'); QCOMPARE(n.token().name(), QString("*")); // -- append(KDbExpression), prepend(KDbExpression) KDbExpression e; KDbNArgExpression nNull; QCOMPARE(nNull.argCount(), 0); // empty nNull.append(e); QCOMPARE(nNull.argCount(), 1); // n-arg expression can have null elements nNull = KDbNArgExpression(); QCOMPARE(nNull.argCount(), 0); // cleared n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 1); n.append(c1); QVERIFY(!n.isEmpty()); QCOMPARE(n.argCount(), 1); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(c1.parent().toNArg(), n); n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.append(n); QCOMPARE(n.argCount(), 0); // append should fail since appending expression // to itself is not allowed n.prepend(n); QCOMPARE(n.argCount(), 0); // append should fail since prepending expression // to itself is not allowed n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 2); n.append(c1); n.append(c1); // cannot append the same expression twice QCOMPARE(n.argCount(), 1); QCOMPARE(n.arg(0).toConst(), c1); n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); n.prepend(c1); n.prepend(c1); // cannot prepend the same expression twice QCOMPARE(n.argCount(), 1); QCOMPARE(n.arg(0).toConst(), c1); n.append(c1); // cannot append/prepend the same expression twice QCOMPARE(n.argCount(), 1); QCOMPARE(n.arg(0).toConst(), c1); n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n2 = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 4); n.append(c1); n2.append(c1); // c moves from n to n2 QVERIFY(n.isEmpty()); QCOMPARE(n2.argCount(), 1); QCOMPARE(c1.parent().toNArg(), n2); n.prepend(c1); // c moves from n2 to n QCOMPARE(n.argCount(), 1); QVERIFY(n2.isEmpty()); QCOMPARE(c1.parent().toNArg(), n); // -- insert(int, KDbExpression) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); c2 = KDbConstExpression(KDbToken::INTEGER_CONST, 4); // it must be a valid index position in the list (i.e., 0 <= i < argCount()). n.insert(-10, c1); QVERIFY(n.isEmpty()); n.insert(1, c1); QVERIFY(n.isEmpty()); // if i is 0, the expression is prepended to the list of arguments n.insert(0, c1); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(n.argCount(), 1); QCOMPARE(c1.parent().toNArg(), n); n.insert(0, c2); QCOMPARE(n.argCount(), 2); QCOMPARE(n.arg(0).toConst(), c2); QCOMPARE(n.arg(1).toConst(), c1); // if i is argCount(), the value is appended to the list of arguments n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.insert(0, c1); n.insert(1, c2); QCOMPARE(n.argCount(), 2); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(n.arg(1).toConst(), c2); // expression cannot be own child n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.insert(0, n); QVERIFY(n.isEmpty()); // cannot insert child twice n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.insert(0, c1); n.insert(1, c1); QCOMPARE(n.argCount(), 1); // -- remove(KDbExpression) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.append(c1); n.append(c2); n.remove(c1); // remove first QCOMPARE(n.argCount(), 1); QCOMPARE(n.arg(0).toConst(), c2); // -- remove(KDbExpression) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.prepend(c1); n.append(c2); c3 = KDbConstExpression(KDbToken::INTEGER_CONST, 5); QVERIFY(!n.remove(c3)); // not found QCOMPARE(n.argCount(), 2); n.append(c3); QCOMPARE(n.argCount(), 3); QVERIFY(n.remove(c2)); // remove 2nd of 3, leaves c1 and c3 QCOMPARE(n.argCount(), 2); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(n.arg(1).toConst(), c3); // -- removeAt(int) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n.prepend(c1); n.append(c2); n.removeAt(-1); // not found QCOMPARE(n.argCount(), 2); n.removeAt(3); // not found QCOMPARE(n.argCount(), 2); n.append(c3); n.removeAt(1); // remove 2nd of 3, leaves c1 and c3 QCOMPARE(n.argCount(), 2); QCOMPARE(n.arg(0).toConst(), c1); QCOMPARE(n.arg(1).toConst(), c3); n.removeAt(0); QCOMPARE(n.argCount(), 1); n.removeAt(0); QCOMPARE(n.argCount(), 0); // -- takeAt(int) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); n2 = n; c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 1); c2 = KDbConstExpression(KDbToken::INTEGER_CONST, 2); c3 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); n.append(c1); n.append(c2); n.append(c3); n.takeAt(-1); // not found QCOMPARE(n.argCount(), 3); n.takeAt(3); // not found QCOMPARE(n.argCount(), 3); e = n.takeAt(1); QCOMPARE(e.toConst(), c2); // e is 2nd QCOMPARE(n.argCount(), 2); // 1 arg taken QCOMPARE(n, n2); // -- indexOf(KDbExpression, int) n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c = KDbConstExpression(KDbToken::INTEGER_CONST, 0); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 1); c2 = KDbConstExpression(KDbToken::INTEGER_CONST, 2); c3 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); n.append(c1); n.append(c2); n.append(c3); QCOMPARE(n.indexOf(c), -1); QCOMPARE(n.indexOf(c1), 0); QCOMPARE(n.indexOf(c2), 1); QCOMPARE(n.indexOf(c3), 2); QCOMPARE(n.indexOf(c1, 1), -1); QCOMPARE(n.indexOf(c2, 1), 1); // -- lastIndexOf(KDbExpression, int) QCOMPARE(n.lastIndexOf(c), -1); QCOMPARE(n.lastIndexOf(c1), 0); QCOMPARE(n.lastIndexOf(c2), 1); QCOMPARE(n.lastIndexOf(c3), 2); QCOMPARE(n.lastIndexOf(c1, 1), 0); QCOMPARE(n.lastIndexOf(c2, 0), -1); // -- a list of arguments n = KDbNArgExpression(KDb::ArgumentListExpression, ','); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 2)); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 3)); - QCOMPARE(n.toString(0), KDbEscapedString("1, 2, 3")); + QCOMPARE(n.toString(nullptr), KDbEscapedString("1, 2, 3")); QCOMPARE(n.argCount(), 3); QVERIFY(!n.containsInvalidArgument()); QVERIFY(!n.containsNullArgument()); // -- a list of arguments contains invalid argument n = KDbNArgExpression(KDb::ArgumentListExpression, ','); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); n.append(KDbExpression()); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 3)); QVERIFY(n.containsInvalidArgument()); QVERIFY(!n.containsNullArgument()); QVERIFY(!n.isNull()); - QCOMPARE(n.toString(0), KDbEscapedString("1, , 3")); + QCOMPARE(n.toString(nullptr), KDbEscapedString("1, , 3")); // -- a list of arguments contains null argument n = KDbNArgExpression(KDb::ArgumentListExpression, ','); n.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); n.append(KDbConstExpression(KDbToken::SQL_NULL, QVariant())); n.prepend(KDbConstExpression(KDbToken::INTEGER_CONST, 0)); QVERIFY(!n.containsInvalidArgument()); QVERIFY(n.containsNullArgument()); - QCOMPARE(n.toString(0), KDbEscapedString("0, 1, NULL")); + QCOMPARE(n.toString(nullptr), KDbEscapedString("0, 1, NULL")); } void ExpressionsTest::testUnaryExpression() { KDbUnaryExpression u; KDbUnaryExpression u2; KDbConstExpression c; KDbConstExpression c1; // -- empty KDbUnaryExpression emptyUnary; QVERIFY(emptyUnary.isUnary()); QVERIFY(emptyUnary.clone().isUnary()); QVERIFY(emptyUnary.arg().isNull()); u = KDbUnaryExpression('-', KDbExpression()); QVERIFY(u.arg().isNull()); // -- copy ctor & cloning c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 7); u = KDbUnaryExpression('-', c1); testCloneExpression(u); QCOMPARE(u.token().name(), QString("-")); - QCOMPARE(u.toString(0), KDbEscapedString("-7")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("-7")); QCOMPARE(c1, u.arg().toConst()); u2 = KDbUnaryExpression('-', u); testCloneExpression(u); QCOMPARE(u2.token().name(), QString("-")); - QCOMPARE(u2.toString(0), KDbEscapedString("--7")); + QCOMPARE(u2.toString(nullptr), KDbEscapedString("--7")); QCOMPARE(u, u2.arg().toUnary()); u = KDbUnaryExpression('(', c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("(7)")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("(7)")); QCOMPARE(c1, u.arg().toConst()); c1 = KDbConstExpression(KDbToken::SQL_TRUE, true); u = KDbUnaryExpression(KDbToken::NOT, c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("NOT TRUE")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("NOT TRUE")); QCOMPARE(c1, u.arg().toConst()); c1 = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); u = KDbUnaryExpression(KDbToken::NOT, c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("NOT NULL")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("NOT NULL")); QCOMPARE(c1, u.arg().toConst()); c1 = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); u = KDbUnaryExpression(KDbToken::SQL_IS_NULL, c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("NULL IS NULL")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("NULL IS NULL")); QCOMPARE(c1, u.arg().toConst()); c1 = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); u = KDbUnaryExpression(KDbToken::SQL_IS_NOT_NULL, c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("NULL IS NOT NULL")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("NULL IS NOT NULL")); QCOMPARE(c1, u.arg().toConst()); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 17); u = KDbUnaryExpression(KDbToken::SQL, c1); testCloneExpression(u); - QCOMPARE(u.toString(0), KDbEscapedString("SQL 17")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("SQL 17")); QCOMPARE(c1, u.arg().toConst()); // -- exchanging arg between two unary expressions c = KDbConstExpression(KDbToken::INTEGER_CONST, 17); u = KDbUnaryExpression('-', c); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); u2 = KDbUnaryExpression('+', c1); u2.setArg(c); // this should take c arg from u to u2 QCOMPARE(c, u2.arg().toConst()); // c is now in u2 QVERIFY(u.arg().isNull()); // u has null arg now c = KDbConstExpression(KDbToken::INTEGER_CONST, 17); u = KDbUnaryExpression('-', c); u2 = KDbUnaryExpression('+', c); // u2 takes c arg from u QCOMPARE(c, u2.arg().toConst()); // c is now in u2 QVERIFY(u.arg().isNull()); // u has null arg now // -- cycles c = KDbConstExpression(KDbToken::INTEGER_CONST, 17); u = KDbUnaryExpression('-', c); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); u2 = KDbUnaryExpression('+', c1); u2.setArg(u); u.setArg(u2); - QCOMPARE(u.toString(0), KDbEscapedString("-+")); - QCOMPARE(u2.toString(0), KDbEscapedString("+-")); + QCOMPARE(u.toString(nullptr), KDbEscapedString("-+")); + QCOMPARE(u2.toString(nullptr), KDbEscapedString("+-")); } void ExpressionsTest::testBinaryExpression() { KDbBinaryExpression b; KDbBinaryExpression b2; KDbConstExpression c; KDbConstExpression c1; // -- empty KDbBinaryExpression emptyBinary; QVERIFY(emptyBinary.isNull()); QVERIFY(emptyBinary.isBinary()); QVERIFY(emptyBinary.clone().isBinary()); QVERIFY(emptyBinary.left().isNull()); QVERIFY(emptyBinary.right().isNull()); b = KDbBinaryExpression(KDbExpression(), '-', KDbExpression()); QVERIFY(b.left().isNull()); QVERIFY(b.right().isNull()); QVERIFY(b.isNull()); // it's null because args are null - qDebug() << b.toString(0); - QCOMPARE(b.toString(0), KDbEscapedString("")); + qDebug() << b.toString(nullptr); + QCOMPARE(b.toString(nullptr), KDbEscapedString("")); c = KDbConstExpression(KDbToken::INTEGER_CONST, 10); b = KDbBinaryExpression(c, '-', KDbExpression()); QVERIFY(b.left().isNull()); QVERIFY(b.right().isNull()); QVERIFY(b.isNull()); // it's null because one arg is null - qDebug() << b.toString(0); - QCOMPARE(b.toString(0), KDbEscapedString("")); + qDebug() << b.toString(nullptr); + QCOMPARE(b.toString(nullptr), KDbEscapedString("")); b = KDbBinaryExpression(KDbExpression(), '-', c); QVERIFY(b.left().isNull()); QVERIFY(b.right().isNull()); QVERIFY(b.isNull()); // it's null because one arg is null - qDebug() << b.toString(0); - QCOMPARE(b.toString(0), KDbEscapedString("")); + qDebug() << b.toString(nullptr); + QCOMPARE(b.toString(nullptr), KDbEscapedString("")); // -- copy ctor & cloning c = KDbConstExpression(KDbToken::INTEGER_CONST, 3); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 4); b = KDbBinaryExpression(c, '/', c1); testCloneExpression(b); QCOMPARE(b.token().name(), QString("/")); - QCOMPARE(b.toString(0), KDbEscapedString("3 / 4")); + QCOMPARE(b.toString(nullptr), KDbEscapedString("3 / 4")); QCOMPARE(c1, b.right().toConst()); b2 = KDbBinaryExpression(b, '*', b.clone()); testCloneExpression(b2); QCOMPARE(b2.token().name(), QString("*")); - QCOMPARE(b2.toString(0), KDbEscapedString("3 / 4 * 3 / 4")); + QCOMPARE(b2.toString(nullptr), KDbEscapedString("3 / 4 * 3 / 4")); QCOMPARE(b, b2.left().toBinary()); // -- cycles // --- ref to parent b = KDbBinaryExpression( KDbConstExpression(KDbToken::INTEGER_CONST, 1), '+', KDbConstExpression(KDbToken::INTEGER_CONST, 2)); - KDbEscapedString s = b.toString(0); + KDbEscapedString s = b.toString(nullptr); b.setLeft(b); // should not work - qDebug() << b.toString(0); - QCOMPARE(s, b.toString(0)); + qDebug() << b.toString(nullptr); + QCOMPARE(s, b.toString(nullptr)); // --- cannot set twice c = b.left().toConst(); b.setLeft(c); - QCOMPARE(s, b.toString(0)); + QCOMPARE(s, b.toString(nullptr)); // --- ref to grandparent b = KDbBinaryExpression( KDbConstExpression(KDbToken::INTEGER_CONST, 1), '+', KDbConstExpression(KDbToken::INTEGER_CONST, 2)); c = KDbConstExpression(KDbToken::INTEGER_CONST, 10); b2 = KDbBinaryExpression(b, '-', c); - qDebug() << b2.toString(0); - QCOMPARE(b2.toString(0), KDbEscapedString("1 + 2 - 10")); + qDebug() << b2.toString(nullptr); + QCOMPARE(b2.toString(nullptr), KDbEscapedString("1 + 2 - 10")); b.setRight(b2); - qDebug() << b2.toString(0); - QCOMPARE(b2.toString(0), KDbEscapedString("1 + - 10")); + qDebug() << b2.toString(nullptr); + QCOMPARE(b2.toString(nullptr), KDbEscapedString("1 + - 10")); // -- moving right argument to left should remove right arg b = KDbBinaryExpression( KDbConstExpression(KDbToken::INTEGER_CONST, 1), '+', KDbConstExpression(KDbToken::INTEGER_CONST, 2)); c = b.right().toConst(); b.setLeft(c); - qDebug() << b.toString(0); - QCOMPARE(b.toString(0), KDbEscapedString("2 + ")); + qDebug() << b.toString(nullptr); + QCOMPARE(b.toString(nullptr), KDbEscapedString("2 + ")); // -- moving left argument to right should remove left arg b = KDbBinaryExpression( KDbConstExpression(KDbToken::INTEGER_CONST, 1), '+', KDbConstExpression(KDbToken::INTEGER_CONST, 2)); c = b.left().toConst(); b.setRight(c); - qDebug() << b.toString(0); - QCOMPARE(b.toString(0), KDbEscapedString(" + 1")); + qDebug() << b.toString(nullptr); + QCOMPARE(b.toString(nullptr), KDbEscapedString(" + 1")); } void ExpressionsTest::testBinaryExpressionCloning_data() { QTest::addColumn("type1"); QTest::addColumn("const1"); QTest::addColumn("token"); QTest::addColumn("type2"); QTest::addColumn("const2"); QTest::addColumn("string"); #define T(type1, const1, token, type2, const2, string) \ QTest::newRow(qPrintable(TO_TOKEN(token).name())) \ << type1 << QVariant(const1) << TO_TOKEN(token) \ << type2 << QVariant(const2) << QString(string) T(KDbToken::INTEGER_CONST, 3, '/', KDbToken::INTEGER_CONST, 4, "3 / 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::BITWISE_SHIFT_RIGHT, KDbToken::INTEGER_CONST, 4, "3 >> 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::BITWISE_SHIFT_LEFT, KDbToken::INTEGER_CONST, 4, "3 << 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::NOT_EQUAL, KDbToken::INTEGER_CONST, 4, "3 <> 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::NOT_EQUAL2, KDbToken::INTEGER_CONST, 4, "3 != 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::LESS_OR_EQUAL, KDbToken::INTEGER_CONST, 4, "3 <= 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::GREATER_OR_EQUAL, KDbToken::INTEGER_CONST, 4, "3 >= 4"); T(KDbToken::CHARACTER_STRING_LITERAL, "ABC", KDbToken::LIKE, KDbToken::CHARACTER_STRING_LITERAL, "A%", "'ABC' LIKE 'A%'"); T(KDbToken::INTEGER_CONST, 3, KDbToken::SQL_IN, KDbToken::INTEGER_CONST, 4, "3 IN 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::SIMILAR_TO, KDbToken::INTEGER_CONST, 4, "3 SIMILAR TO 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::NOT_SIMILAR_TO, KDbToken::INTEGER_CONST, 4, "3 NOT SIMILAR TO 4"); T(KDbToken::SQL_TRUE, true, KDbToken::OR, KDbToken::SQL_FALSE, false, "TRUE OR FALSE"); T(KDbToken::INTEGER_CONST, 3, KDbToken::AND, KDbToken::INTEGER_CONST, 4, "3 AND 4"); T(KDbToken::INTEGER_CONST, 3, KDbToken::XOR, KDbToken::INTEGER_CONST, 4, "3 XOR 4"); T(KDbToken::CHARACTER_STRING_LITERAL, "AB", KDbToken::CONCATENATION, KDbToken::CHARACTER_STRING_LITERAL, "CD", "'AB' || 'CD'"); T(KDbToken::CHARACTER_STRING_LITERAL, "AB", '+', KDbToken::CHARACTER_STRING_LITERAL, "CD", "'AB' + 'CD'"); #undef T } void ExpressionsTest::testBinaryExpressionCloning() { QFETCH(KDbToken, type1); QFETCH(QVariant, const1); QFETCH(KDbToken, token); QFETCH(KDbToken, type2); QFETCH(QVariant, const2); QFETCH(QString, string); KDbConstExpression c(type1, const1); KDbConstExpression c1(type2, const2); KDbBinaryExpression b(c, token, c1); testCloneExpression(b); QCOMPARE(b.token(), token); QCOMPARE(b.token().name(), token.name()); //qDebug() << token << b; - QCOMPARE(b.toString(0), KDbEscapedString(string)); + QCOMPARE(b.toString(nullptr), KDbEscapedString(string)); QCOMPARE(c, b.left().toConst()); QCOMPARE(c1, b.right().toConst()); } void ExpressionsTest::testFunctionExpression() { KDbFunctionExpression emptyFunction; QVERIFY(emptyFunction.isFunction()); QVERIFY(emptyFunction.clone().isFunction()); QVERIFY(emptyFunction.arguments().isEmpty()); QVERIFY(emptyFunction.isNull()); KDbNArgExpression args; args.append(KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "abc")); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 2)); KDbFunctionExpression f_substr("SUBSTR", args); //qDebug() << f_substr.toString(); //qDebug() << f_substr.token().name(); //qDebug() << f_substr.token().toString(); testCloneExpression(f_substr); QCOMPARE(f_substr.type(), KDbField::Text); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); KDbFunctionExpression f_substr2("SUBSTR", args); testCloneExpression(f_substr2); QCOMPARE(f_substr2.type(), KDbField::Text); //qDebug() << f_substr.toString(); //qDebug() << f_substr2.toString(); QVERIFY(f_substr != f_substr2); // other objects - QCOMPARE(f_substr.toString(0), f_substr2.toString(0)); // the same signatures + QCOMPARE(f_substr.toString(nullptr), f_substr2.toString(nullptr)); // the same signatures QCOMPARE(f_substr.arguments(), f_substr2.arguments()); // the same arg lists // clone the args KDbNArgExpression args2 = args.clone().toNArg(); //qDebug() << f_substr2; f_substr2.setArguments(args2); //qDebug() << f_substr2; - QCOMPARE(f_substr.toString(0), f_substr2.toString(0)); // still the same signatures + QCOMPARE(f_substr.toString(nullptr), f_substr2.toString(nullptr)); // still the same signatures QVERIFY(f_substr.arguments() != f_substr2.arguments()); // not the same arg lists KDbExpression e = f_substr; QCOMPARE(e.toFunction(), f_substr); QCOMPARE(e.toFunction(), f_substr.toFunction()); QVERIFY(e.isFunction()); // nested functions f_substr2.arguments().replace(0, f_substr); QCOMPARE(f_substr2.type(), KDbField::Text); } void ExpressionsTest::testConstExpressionValidate() { KDbConstExpression c; c = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); QCOMPARE(c.type(), KDbField::Null); QVERIFY(c.isValid()); QVERIFY(!c.isNull()); QVERIFY(validate(&c)); // null c = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); QCOMPARE(c.type(), KDbField::Null); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // integer c = KDbConstExpression(KDbToken::INTEGER_CONST, -0x7f); QCOMPARE(c.type(), KDbField::Byte); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QVERIFY(!c.isNull()); QCOMPARE(c.value(), QVariant(-0x7f)); QVERIFY(validate(&c)); testCloneExpression(c); c.setValue(-0x80); QCOMPARE(c.type(), KDbField::ShortInteger); // type has been changed by setValue QCOMPARE(c.value(), QVariant(-0x80)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, -10); QCOMPARE(c.type(), KDbField::Byte); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(-10)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, 0); QCOMPARE(c.type(), KDbField::Byte); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(0)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, 20); QCOMPARE(c.type(), KDbField::Byte); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(20)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, 255); QCOMPARE(c.type(), KDbField::Byte); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(255)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, -0x80); QCOMPARE(c.type(), KDbField::ShortInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(-0x80)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, -0x7fff); QCOMPARE(c.type(), KDbField::ShortInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(-0x7fff)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, 256); QCOMPARE(c.type(), KDbField::ShortInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(256)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, 0xffff); QCOMPARE(c.type(), KDbField::ShortInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(0xffff)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, -0x8000); QCOMPARE(c.type(), KDbField::Integer); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(-0x8000)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, uint(0x10000)); QCOMPARE(c.type(), KDbField::Integer); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(0x10000)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, qlonglong(-0x100000)); QCOMPARE(c.type(), KDbField::BigInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(-0x100000)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::INTEGER_CONST, qulonglong(0x1000000)); QCOMPARE(c.type(), KDbField::BigInteger); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QCOMPARE(c.value(), QVariant(0x1000000)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // string int oldMaxLen = KDbField::defaultMaxLength(); // save KDbField::setDefaultMaxLength(0); c = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "01234567890"); QVERIFY(c.isValid()); QVERIFY(c.isTextType()); QCOMPARE(c.type(), KDbField::Text); QCOMPARE(c.value(), QVariant("01234567890")); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; KDbField::setDefaultMaxLength(10); c = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QString()); QCOMPARE(c.type(), KDbField::Text); QVERIFY(c.isValid()); QVERIFY(c.isTextType()); QCOMPARE(c.value(), QVariant(QString())); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()); QCOMPARE(c.type(), KDbField::Text); QVERIFY(c.isValid()); QVERIFY(c.isTextType()); QCOMPARE(c.value(), QVariant()); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "01234567890"); QCOMPARE(c.type(), KDbField::LongText); QVERIFY(c.isValid()); QVERIFY(c.isTextType()); QCOMPARE(c.value(), QVariant("01234567890")); QVERIFY(validate(&c)); qDebug() << c; c.setValue("ąćę"); QCOMPARE(c.value(), QVariant("ąćę")); QCOMPARE(c.type(), KDbField::Text); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; KDbField::setDefaultMaxLength(oldMaxLen); // restore // bool c = KDbConstExpression(KDbToken::SQL_TRUE, true); QCOMPARE(c.type(), KDbField::Boolean); QVERIFY(c.isValid()); QVERIFY(!c.isTextType()); QVERIFY(!c.isNumericType()); QCOMPARE(c.value(), QVariant(true)); QVERIFY(validate(&c)); qDebug() << c; c.setValue(false); QCOMPARE(c.value(), QVariant(false)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::SQL_FALSE, false); QCOMPARE(c.type(), KDbField::Boolean); QVERIFY(c.isValid()); QVERIFY(!c.isTextType()); QVERIFY(!c.isNumericType()); QCOMPARE(c.value(), QVariant(false)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // real c = KDbConstExpression(KDbToken::REAL_CONST, QVariant()); QCOMPARE(c.type(), KDbField::Double); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QVERIFY(c.isFPNumericType()); QCOMPARE(c.value(), QVariant()); - QCOMPARE(c.toString(0), KDbEscapedString()); + QCOMPARE(c.toString(nullptr), KDbEscapedString()); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::REAL_CONST, 3.14159); QCOMPARE(c.type(), KDbField::Double); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QVERIFY(c.isFPNumericType()); QCOMPARE(c.value(), QVariant(3.14159)); QString piString("3.14159"); // limit precision because it depends on the OS - QCOMPARE(c.toString(0).toString().left(piString.length() - 1), piString.left(piString.length() - 1)); + QCOMPARE(c.toString(nullptr).toString().left(piString.length() - 1), piString.left(piString.length() - 1)); QVERIFY(validate(&c)); qDebug() << c; c.setValue(-18.012); QCOMPARE(c.value(), QVariant(-18.012)); - QCOMPARE(c.toString(0), KDbEscapedString("-18.012")); + QCOMPARE(c.toString(nullptr), KDbEscapedString("-18.012")); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; QByteArray largeDecimal("2147483647.2147483647"); c = KDbConstExpression(KDbToken::REAL_CONST, largeDecimal); QCOMPARE(c.type(), KDbField::Double); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QVERIFY(c.isFPNumericType()); QCOMPARE(c.value(), QVariant(largeDecimal)); - QCOMPARE(c.toString(0), KDbEscapedString(largeDecimal)); + QCOMPARE(c.toString(nullptr), KDbEscapedString(largeDecimal)); largeDecimal = "-10.2147483647"; QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; c = KDbConstExpression(KDbToken::REAL_CONST, largeDecimal); QCOMPARE(c.value(), QVariant(largeDecimal)); - QCOMPARE(c.toString(0), KDbEscapedString(largeDecimal)); + QCOMPARE(c.toString(nullptr), KDbEscapedString(largeDecimal)); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // date QDate date(QDate::currentDate()); c = KDbConstExpression(KDbToken::DATE_CONST, date); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QCOMPARE(c.type(), KDbField::Date); QCOMPARE(c.value(), QVariant(date)); QVERIFY(validate(&c)); qDebug() << c; date = date.addDays(17); c.setValue(date); QCOMPARE(c.value(), QVariant(date)); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // date/time QDateTime dateTime(QDateTime::currentDateTime()); c = KDbConstExpression(KDbToken::DATETIME_CONST, dateTime); QCOMPARE(c.type(), KDbField::DateTime); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QCOMPARE(c.value(), QVariant(dateTime)); QVERIFY(validate(&c)); qDebug() << c; dateTime = dateTime.addDays(-17); c.setValue(dateTime); QCOMPARE(c.value(), QVariant(dateTime)); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // time QTime time(QTime::currentTime()); c = KDbConstExpression(KDbToken::TIME_CONST, time); QCOMPARE(c.type(), KDbField::Time); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QCOMPARE(c.value(), QVariant(time)); qDebug() << c; QVERIFY(validate(&c)); time = time.addSecs(1200); c.setValue(time); QCOMPARE(c.value(), QVariant(time)); QVERIFY(c.isValid()); QVERIFY(c.isDateTimeType()); QVERIFY(validate(&c)); testCloneExpression(c); qDebug() << c; // setValue() c = KDbConstExpression(KDbToken::INTEGER_CONST, 124); QCOMPARE(c.value(), QVariant(124)); c.setValue(299); QCOMPARE(c.value(), QVariant(299)); QVERIFY(c.isValid()); QVERIFY(c.isNumericType()); QVERIFY(!c.isFPNumericType()); testCloneExpression(c); qDebug() << c; } void ExpressionsTest::testUnaryExpressionValidate() { KDbConstExpression c; KDbConstExpression c1; KDbUnaryExpression u; KDbUnaryExpression u2; // cycles detected by validate() c = KDbConstExpression(KDbToken::INTEGER_CONST, 17); u = KDbUnaryExpression('-', c); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); u2 = KDbUnaryExpression('+', c1); u2.setArg(u); u.setArg(u2); QVERIFY(!validate(&u)); //qDebug() << c << u << c1 << u2; // NOT NULL is NULL c = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); u = KDbUnaryExpression(KDbToken::NOT, c); QCOMPARE(u.type(), KDbField::Null); QVERIFY(validate(&u)); testCloneExpression(u); // NOT "abc" is INVALID c = KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "abc"); u = KDbUnaryExpression(KDbToken::NOT, c); QCOMPARE(u.type(), KDbField::InvalidType); QVERIFY(!validate(&u)); testCloneExpression(u); } void ExpressionsTest::testNArgExpressionValidate() { KDbNArgExpression n; KDbConstExpression c; KDbConstExpression c1; KDbConstExpression c2; c = KDbConstExpression(KDbToken::SQL_NULL, QVariant()); QCOMPARE(c.type(), KDbField::Null); QVERIFY(validate(&c)); n = KDbNArgExpression(KDb::ArithmeticExpression, '+'); c = KDbConstExpression(KDbToken::INTEGER_CONST, 0); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 1); n.append(c); n.append(c1); QCOMPARE(n.type(), KDbField::Tuple); QVERIFY(validate(&n)); testCloneExpression(n); //qDebug() << c << c1 << n; // -- a list of arguments n = KDbNArgExpression(KDb::ArgumentListExpression, ','); c = KDbConstExpression(KDbToken::INTEGER_CONST, 1); c1 = KDbConstExpression(KDbToken::INTEGER_CONST, 2); c2 = KDbConstExpression(KDbToken::INTEGER_CONST, 3); n.append(c); n.append(c1); n.append(c2); QCOMPARE(n.type(), KDbField::Tuple); QVERIFY(validate(&n)); QVERIFY(n.isValid()); } void ExpressionsTest::testBinaryExpressionValidate_data() { QTest::addColumn("type1"); QTest::addColumn("const1"); QTest::addColumn("token"); QTest::addColumn("type2"); QTest::addColumn("const2"); QTest::addColumn("type3"); // invalid KDbConstExpression c(KDbToken::INTEGER_CONST, 7); KDbBinaryExpression b(c, '+', KDbExpression()); QCOMPARE(b.type(), KDbField::InvalidType); QVERIFY(!validate(&b)); testCloneExpression(b); qDebug() << b; b = KDbBinaryExpression(KDbExpression(), '/', KDbExpression()); QCOMPARE(b.type(), KDbField::InvalidType); QVERIFY(!validate(&b)); // unknown class testCloneExpression(b); qDebug() << b; // invalid left or right KDbBinaryExpression b2(b, '*', c.clone()); QCOMPARE(b2.type(), KDbField::InvalidType); QVERIFY(!validate(&b2)); // unknown class testCloneExpression(b2); qDebug() << b2; KDbBinaryExpression b3(c.clone(), '*', b); QCOMPARE(b3.type(), KDbField::InvalidType); QVERIFY(!validate(&b3)); // unknown class testCloneExpression(b3); qDebug() << b3; #define TNAME(type) type.name().toLatin1() #define T1(type1, const1, tokenOrChar, type2, const2, type3) \ QTest::newRow( \ qPrintable(QString::number(__LINE__) + ": " + TNAME(type1) + " " \ + QVariant(const1).toString() + " " \ + TNAME(TO_TOKEN(tokenOrChar)) + " " \ + TNAME(type2) + " " \ + QVariant(const2).toString().toLatin1())) \ << type1 << QVariant(const1) \ << TO_TOKEN(tokenOrChar) << type2 << QVariant(const2) \ << type3 // tests both f(x, y) and f(y, x) #define T(type1, const1, token, type2, const2, type3) \ T1(type1, const1, token, type2, const2, type3); \ T1(type2, const2, token, type1, const1, type3) // null T(KDbToken::SQL_NULL, QVariant(), '+', KDbToken::INTEGER_CONST, 7, KDbField::Null); // NULL OR true is true T(KDbToken::SQL_NULL, QVariant(), KDbToken::OR, KDbToken::SQL_TRUE, true, KDbField::Boolean); // NULL AND true is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::AND, KDbToken::SQL_TRUE, true, KDbField::Null); // NULL OR false is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::OR, KDbToken::SQL_FALSE, false, KDbField::Null); // NULL AND false is false T(KDbToken::SQL_NULL, QVariant(), KDbToken::AND, KDbToken::SQL_FALSE, false, KDbField::Boolean); // NULL AND NULL is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::AND, KDbToken::SQL_NULL, QVariant(), KDbField::Null); // NULL OR NULL is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::OR, KDbToken::SQL_NULL, QVariant(), KDbField::Null); // NULL XOR TRUE is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::XOR, KDbToken::SQL_TRUE, true, KDbField::Null); // NULL XOR NULL is NULL T(KDbToken::SQL_NULL, QVariant(), KDbToken::XOR, KDbToken::SQL_NULL, QVariant(), KDbField::Null); // NULL AND "xyz" is invalid T(KDbToken::SQL_NULL, QVariant(), KDbToken::OR, KDbToken::CHARACTER_STRING_LITERAL, "xyz", KDbField::InvalidType); // integer // -- KDb::ArithmeticExpression only: resulting type is Integer or more // see explanation for KDb::maximumForIntegerFieldTypes() T(KDbToken::INTEGER_CONST, 50, '+', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '-', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '*', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '/', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '&', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '|', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '%', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, KDbToken::BITWISE_SHIFT_RIGHT, KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, KDbToken::BITWISE_SHIFT_LEFT, KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 300, '+', KDbToken::INTEGER_CONST, 20, KDbField::Integer); T(KDbToken::INTEGER_CONST, 300, '+', KDbToken::INTEGER_CONST, 300, KDbField::Integer); T(KDbToken::INTEGER_CONST, 300, '+', KDbToken::INTEGER_CONST, 300, KDbField::Integer); T(KDbToken::INTEGER_CONST, 50, '+', KDbToken::INTEGER_CONST, qulonglong(INT_MAX), KDbField::BigInteger); T(KDbToken::INTEGER_CONST, INT_MAX, '+', KDbToken::INTEGER_CONST, qulonglong(INT_MAX), KDbField::BigInteger); T(KDbToken::INTEGER_CONST, 50, '<', KDbToken::INTEGER_CONST, 20, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 50, '=', KDbToken::INTEGER_CONST, 20, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 50, '>', KDbToken::INTEGER_CONST, 20, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 50, '<', KDbToken::INTEGER_CONST, INT_MAX, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 50, '<', KDbToken::INTEGER_CONST, qulonglong(INT_MAX), KDbField::Boolean); T(KDbToken::INTEGER_CONST, qulonglong(INT_MAX), '<', KDbToken::INTEGER_CONST, INT_MAX, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 300, KDbToken::LESS_OR_EQUAL, KDbToken::INTEGER_CONST, 20, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 300, KDbToken::GREATER_OR_EQUAL, KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 300, '>', KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::INTEGER_CONST, 300, KDbToken::OR, KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::INTEGER_CONST, 300, KDbToken::AND, KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::INTEGER_CONST, 300, KDbToken::XOR, KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::INTEGER_CONST, 300, KDbToken::OR, KDbToken::SQL_NULL, QVariant(), KDbField::InvalidType); // real T(KDbToken::REAL_CONST, 0.5, '+', KDbToken::REAL_CONST, -9.4, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '-', KDbToken::REAL_CONST, -9.4, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '*', KDbToken::REAL_CONST, -9.4, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '/', KDbToken::REAL_CONST, -9.4, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '&', KDbToken::REAL_CONST, -9.4, KDbField::Integer); T(KDbToken::REAL_CONST, 0.5, '&', KDbToken::INTEGER_CONST, 9, KDbField::Byte); T(KDbToken::REAL_CONST, 0.5, '&', KDbToken::INTEGER_CONST, 1000, KDbField::ShortInteger); T(KDbToken::REAL_CONST, 0.5, '&', KDbToken::INTEGER_CONST, qulonglong(INT_MAX), KDbField::BigInteger); T(KDbToken::REAL_CONST, 0.5, '%', KDbToken::REAL_CONST, -9.4, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, KDbToken::BITWISE_SHIFT_RIGHT, KDbToken::REAL_CONST, 9.4, KDbField::Integer); T(KDbToken::REAL_CONST, 0.5, KDbToken::BITWISE_SHIFT_LEFT, KDbToken::REAL_CONST, 9.4, KDbField::Integer); T(KDbToken::REAL_CONST, 0.5, '+', KDbToken::INTEGER_CONST, 300, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '-', KDbToken::INTEGER_CONST, 300, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '/', KDbToken::INTEGER_CONST, 300, KDbField::Double); T(KDbToken::REAL_CONST, 0.5, '-', KDbToken::SQL_NULL, QVariant(), KDbField::Null); T(KDbToken::REAL_CONST, 0.5, '>', KDbToken::REAL_CONST, -9.4, KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, '>', KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, '=', KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, '<', KDbToken::INTEGER_CONST, qulonglong(INT_MAX), KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, KDbToken::LESS_OR_EQUAL, KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, KDbToken::GREATER_OR_EQUAL, KDbToken::INTEGER_CONST, 300, KDbField::Boolean); T(KDbToken::REAL_CONST, 0.5, '>', KDbToken::SQL_NULL, QVariant(), KDbField::Null); T(KDbToken::REAL_CONST, 30.2, KDbToken::OR, KDbToken::REAL_CONST, 20, KDbField::InvalidType); T(KDbToken::REAL_CONST, 30.2, KDbToken::AND, KDbToken::REAL_CONST, 20, KDbField::InvalidType); T(KDbToken::REAL_CONST, 30.2, KDbToken::XOR, KDbToken::REAL_CONST, 20, KDbField::InvalidType); // string T(KDbToken::CHARACTER_STRING_LITERAL, "ab", KDbToken::CONCATENATION, KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Text); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", '+', KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Text); T(KDbToken::SQL_NULL, QVariant(), KDbToken::CONCATENATION, KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Null); T(KDbToken::SQL_NULL, QVariant(), '+', KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Null); T(KDbToken::INTEGER_CONST, 50, KDbToken::CONCATENATION, KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", KDbToken::CONCATENATION, KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", '+', KDbToken::INTEGER_CONST, 20, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", KDbToken::GREATER_OR_EQUAL, KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Boolean); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", '<', KDbToken::INTEGER_CONST, 3, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "ab", '+', KDbToken::CHARACTER_STRING_LITERAL, "cd", KDbField::Text); T(KDbToken::CHARACTER_STRING_LITERAL, "A", KDbToken::OR, KDbToken::REAL_CONST, 20, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "A", KDbToken::AND, KDbToken::REAL_CONST, 20, KDbField::InvalidType); T(KDbToken::CHARACTER_STRING_LITERAL, "A", KDbToken::XOR, KDbToken::REAL_CONST, 20, KDbField::InvalidType); // bool T(KDbToken::SQL_TRUE, true, '<', KDbToken::SQL_FALSE, false, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, '=', KDbToken::SQL_FALSE, false, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, '+', KDbToken::SQL_FALSE, false, KDbField::InvalidType); T(KDbToken::SQL_TRUE, true, '<', KDbToken::INTEGER_CONST, 20, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, '<', KDbToken::REAL_CONST, -10.1, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, '-', KDbToken::SQL_NULL, QVariant(), KDbField::Null); T(KDbToken::SQL_TRUE, true, '<', KDbToken::SQL_NULL, QVariant(), KDbField::Null); T(KDbToken::SQL_TRUE, true, KDbToken::OR, KDbToken::SQL_FALSE, false, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, KDbToken::AND, KDbToken::SQL_FALSE, false, KDbField::Boolean); T(KDbToken::SQL_TRUE, true, KDbToken::XOR, KDbToken::SQL_FALSE, false, KDbField::Boolean); // date/time T(KDbToken::DATE_CONST, QDate(2001, 1, 2), '=', KDbToken::DATE_CONST, QDate(2002, 1, 2), KDbField::Boolean); T(KDbToken::DATETIME_CONST, QDateTime(QDate(2001, 1, 2), QTime(1, 2, 3)), KDbToken::LESS_OR_EQUAL, KDbToken::DATE_CONST, QDateTime::currentDateTime(), KDbField::Boolean); T(KDbToken::TIME_CONST, QTime(1, 2, 3), '<', KDbToken::DATE_CONST, QTime::currentTime(), KDbField::Boolean); T(KDbToken::DATE_CONST, QDate(2001, 1, 2), '=', KDbToken::INTEGER_CONST, 17, KDbField::InvalidType); T(KDbToken::DATE_CONST, QDate(2001, 1, 2), '=', KDbToken::SQL_NULL, QVariant(), KDbField::Null); T(KDbToken::DATE_CONST, QDate(2001, 1, 2), KDbToken::OR, KDbToken::SQL_FALSE, false, KDbField::InvalidType); T(KDbToken::DATE_CONST, QDate(2001, 1, 2), KDbToken::AND, KDbToken::SQL_FALSE, false, KDbField::InvalidType); T(KDbToken::DATE_CONST, QDate(2001, 1, 2), KDbToken::XOR, KDbToken::SQL_FALSE, false, KDbField::InvalidType); #undef T #undef T1 #undef TNAME } void ExpressionsTest::testBinaryExpressionValidate() { QFETCH(KDbToken, type1); QFETCH(QVariant, const1); QFETCH(KDbToken, token); QFETCH(KDbToken, type2); QFETCH(QVariant, const2); QFETCH(KDbField::Type, type3); KDbConstExpression c(type1, const1); KDbConstExpression c1(type2, const2); KDbBinaryExpression b(c, token, c1); qDebug() << b.type(); qDebug() << type3; QCOMPARE(b.type(), type3); QVERIFY(validate(&b) == (type3 != KDbField::InvalidType)); testCloneExpression(b); } void ExpressionsTest::testFunctionExpressionValidate() { KDbFunctionExpression emptyFunction; QVERIFY(!validate(&emptyFunction)); KDbNArgExpression args; args.append(KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "abc")); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 2)); KDbFunctionExpression f_substr("SUBSTR", args); QVERIFY(validate(&f_substr)); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); KDbFunctionExpression f_substr2("SUBSTR", args); QVERIFY(validate(&f_substr2)); // clone the args KDbNArgExpression args2 = args.clone().toNArg(); f_substr2.setArguments(args2); QVERIFY(validate(&f_substr2)); // wrong type (1st arg) args = KDbNArgExpression(); args.append(KDbConstExpression(KDbToken::DATETIME_CONST, QDateTime::currentDateTime())); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 1)); f_substr2.setArguments(args); QVERIFY(!validate(&f_substr2)); // fixed type KDbConstExpression first = args.arg(0).toConst(); first.setToken(KDbToken::CHARACTER_STRING_LITERAL); first.setValue("xyz"); QVERIFY(validate(&f_substr2)); // wrong type (2nd arg) KDbConstExpression second = args.arg(1).toConst(); second.setToken(KDbToken::REAL_CONST); second.setValue(3.14); QVERIFY(!validate(&f_substr2)); // nested functions KDbFunctionExpression f_substr3 = f_substr.clone().toFunction(); f_substr3.arguments().replace(0, f_substr.clone()); QVERIFY(validate(&f_substr3)); // fixed type args.replace(1, KDbConstExpression(KDbToken::INTEGER_CONST, 1)); QVERIFY(validate(&f_substr2)); // wrong type (3rd arg) args.append(KDbConstExpression(KDbToken::REAL_CONST, 1.111)); //qDebug() << args; //qDebug() << f_substr2; QVERIFY(!validate(&f_substr2)); // wrong number of args f_substr2.setArguments(KDbNArgExpression()); args.append(KDbConstExpression(KDbToken::INTEGER_CONST, 77)); QVERIFY(!validate(&f_substr2)); KDbFunctionExpression f_noname("", args); QVERIFY(!validate(&f_noname)); } void ExpressionsTest::cleanupTestCase() { } diff --git a/autotests/KDbTest.cpp b/autotests/KDbTest.cpp index ad557df4..81196620 100644 --- a/autotests/KDbTest.cpp +++ b/autotests/KDbTest.cpp @@ -1,1210 +1,1210 @@ /* This file is part of the KDE project Copyright (C) 2015-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTest.h" #include #include #include #include #include QTEST_GUILESS_MAIN(KDbTest) Q_DECLARE_METATYPE(KDbField::TypeGroup) Q_DECLARE_METATYPE(KDbField::Type) Q_DECLARE_METATYPE(KDb::Signedness) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(KDb::BLOBEscapingType) void KDbTest::initTestCase() { } void KDbTest::testVersionInfo() { KDbVersionInfo info = KDb::version(); KDbVersionInfo info2(KDb::version()); QCOMPARE(info, info2); KDbVersionInfo info3(info.major(), info.minor(), info.release()); QCOMPARE(info, info3); QVERIFY(KDbVersionInfo(0, 0, 0).isNull()); QVERIFY(!info.isNull()); QVERIFY(!info2.isNull()); QVERIFY(!info3.isNull()); } //! @todo add tests requiring connection #if 0 //! @overload bool deleteRecord(KDbConnection*, const KDbTableSchema&, const QString &, KDbField::Type, const QVariant &) KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname, KDbField::Type keytype, const QVariant &keyval); //! Deletes records using one generic criteria. inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString &keyname, KDbField::Type keytype, const QVariant &keyval) //! @overload bool deleteRecords(KDbConnection*, const QString&, const QString&, KDbField::Type, const QVariant&); inline bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname, const QString &keyval) //! @overload bool deleteRecords(KDbConnection*, const QString&, const QString&, const QString&); inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString &keyname, const QString &keyval) //! @overload bool deleteRecords(KDbConnection*, const KDbTableSchema&, const QString&, const QString&); inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString& keyname, int keyval) //! @overload bool deleteRecords(KDbConnection*, const KDbTableSchema&, const QString&, int); inline bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString& keyname, int keyval) //! Deletes records with two generic criterias. KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2); //! Deletes records with three generic criterias. KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2, const QString &keyname3, KDbField::Type keytype3, const QVariant& keyval3); //! Deletes all records from table @a tableName. KDB_EXPORT bool deleteAllRecords(KDbConnection* conn, const QString &tableName); //! @overload bool deleteAllRecords(KDbConnection*, const QString&); inline bool deleteAllRecords(KDbConnection* conn, const KDbTableSchema &table) #endif void KDbTest::testFieldTypes() { QCOMPARE(KDbField::FirstType, KDbField::Byte); QCOMPARE(KDbField::LastType, KDbField::BLOB); QVERIFY(KDbField::FirstType < KDbField::LastType); } void KDbTest::testFieldTypesForGroup_data() { QTest::addColumn("typeGroup"); QTest::addColumn>("types"); int c = 0; ++c; QTest::newRow("invalid") << KDbField::InvalidGroup << (QList() << KDbField::InvalidType); ++c; QTest::newRow("text") << KDbField::TextGroup << (QList() << KDbField::Text << KDbField::LongText); ++c; QTest::newRow("integer") << KDbField::IntegerGroup << (QList() << KDbField::Byte << KDbField::ShortInteger << KDbField::Integer << KDbField::BigInteger); ++c; QTest::newRow("float") << KDbField::FloatGroup << (QList() << KDbField::Float << KDbField::Double); ++c; QTest::newRow("boolean") << KDbField::BooleanGroup << (QList() << KDbField::Boolean); ++c; QTest::newRow("datetime") << KDbField::DateTimeGroup << (QList() << KDbField::Date << KDbField::DateTime << KDbField::Time); ++c; QTest::newRow("blob") << KDbField::BLOBGroup << (QList() << KDbField::BLOB); QCOMPARE(c, KDbField::typeGroupsCount()); // make sure we're checking everything } void KDbTest::testFieldTypesForGroup() { QFETCH(KDbField::TypeGroup, typeGroup); QFETCH(QList, types); QCOMPARE(KDb::fieldTypesForGroup(typeGroup), types); } void KDbTest::testFieldTypeNamesAndStringsForGroup_data() { QTest::addColumn("typeGroup"); QTest::addColumn>("typeNames"); QTest::addColumn("typeStrings"); int c = 0; ++c; QTest::newRow("invalid") << KDbField::InvalidGroup << (QList() << "Invalid Type") << (QStringList() << "InvalidType"); ++c; QTest::newRow("text") << KDbField::TextGroup << (QList() << "Text" << "Long Text") << (QStringList() << "Text" << "LongText"); ++c; QTest::newRow("integer") << KDbField::IntegerGroup << (QList() << "Byte" << "Short Integer Number" << "Integer Number" << "Big Integer Number") << (QStringList() << "Byte" << "ShortInteger" << "Integer" << "BigInteger"); ++c; QTest::newRow("float") << KDbField::FloatGroup << (QList() << "Single Precision Number" << "Double Precision Number") << (QStringList() << "Float" << "Double"); ++c; QTest::newRow("boolean") << KDbField::BooleanGroup << (QList() << "Yes/No Value") << (QStringList() << "Boolean"); ++c; QTest::newRow("datetime") << KDbField::DateTimeGroup << (QList() << "Date" << "Date and Time" << "Time") << (QStringList() << "Date" << "DateTime" << "Time"); ++c; QTest::newRow("blob") << KDbField::BLOBGroup << (QList() << "Object") << (QStringList() << "BLOB"); QCOMPARE(c, KDbField::typeGroupsCount()); // make sure we're checking everything } void KDbTest::testFieldTypeNamesAndStringsForGroup() { QFETCH(KDbField::TypeGroup, typeGroup); QFETCH(QList, typeNames); QFETCH(QStringList, typeStrings); QStringList translatedNames; foreach(const QByteArray &name, typeNames) { translatedNames.append(KDbField::tr(name.constData())); } QCOMPARE(KDb::fieldTypeNamesForGroup(typeGroup), translatedNames); QCOMPARE(KDb::fieldTypeStringsForGroup(typeGroup), typeStrings); } void KDbTest::testDefaultFieldTypeForGroup() { int c = 0; ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::InvalidGroup), KDbField::InvalidType); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::TextGroup), KDbField::Text); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::IntegerGroup), KDbField::Integer); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::FloatGroup), KDbField::Double); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::BooleanGroup), KDbField::Boolean); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::DateTimeGroup), KDbField::Date); ++c; QCOMPARE(KDb::defaultFieldTypeForGroup(KDbField::BLOBGroup), KDbField::BLOB); QCOMPARE(c, KDbField::typeGroupsCount()); // make sure we're checking everything } void KDbTest::testSimplifiedFieldTypeName() { int c = 0; ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::InvalidType), KDbField::tr("Invalid Group")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Byte), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::ShortInteger), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Integer), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::BigInteger), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Boolean), KDbField::tr("Yes/No")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Date), KDbField::tr("Date/Time")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::DateTime), KDbField::tr("Date/Time")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Time), KDbField::tr("Date/Time")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Float), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Double), KDbField::tr("Number")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Text), KDbField::tr("Text")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::LongText), KDbField::tr("Text")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::BLOB), KDbField::tr("Image")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Null), KDbField::tr("Invalid Group")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Asterisk), KDbField::tr("Invalid Group")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Enum), KDbField::tr("Invalid Group")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Map), KDbField::tr("Invalid Group")); ++c; QCOMPARE(KDb::simplifiedFieldTypeName(KDbField::Tuple), KDbField::tr("Invalid Group")); QCOMPARE(c, KDbField::typesCount() + KDbField::specialTypesCount()); // make sure we're checking everything } void KDbTest::testIsEmptyValue_data() { QTest::addColumn("type"); QTest::addColumn("value"); QTest::addColumn("result"); QTest::addColumn("resultForNullValue"); QTest::addColumn("resultForEmptyString"); int c = 0; ++c; QTest::newRow("Invalid") << KDbField::InvalidType << QVariant("abc") << false << true << false; ++c; QTest::newRow("Byte") << KDbField::Byte << QVariant(17) << false << true << false; ++c; QTest::newRow("ShortInteger") << KDbField::ShortInteger << QVariant(1733) << false << true << false; ++c; QTest::newRow("Integer") << KDbField::Integer << QVariant(11733) << false << true << false; ++c; QTest::newRow("BigInteger") << KDbField::BigInteger << QVariant(0xffffff12) << false << true << false; ++c; QTest::newRow("Boolean") << KDbField::Boolean << QVariant(false) << false << true << false; ++c; QTest::newRow("Date") << KDbField::Date << QVariant(QDate(2015, 11, 07)) << false << true << false; ++c; QTest::newRow("DateTime") << KDbField::DateTime << QVariant(QDateTime(QDate(2015, 11, 07), QTime(12, 58, 17))) << false << true << false; ++c; QTest::newRow("Time") << KDbField::Time << QVariant(QTime(12, 58, 17)) << false << true << false; ++c; QTest::newRow("Float") << KDbField::Float << QVariant(3.14) << false << true << false; ++c; QTest::newRow("Double") << KDbField::Double << QVariant(3.1415) << false << true << false; ++c; QTest::newRow("Text") << KDbField::Text << QVariant(QLatin1String("abc")) << false << false << true; ++c; QTest::newRow("LongText") << KDbField::LongText << QVariant(QLatin1String("abc")) << false << false << true; ++c; QTest::newRow("BLOB") << KDbField::LongText << QVariant(QByteArray(5, 'X')) << false << false << true; ++c; QTest::newRow("Null") << KDbField::Null << QVariant(123) << false << true << false; ++c; QTest::newRow("Asterisk") << KDbField::Asterisk << QVariant(123) << false << true << false; ++c; QTest::newRow("Enum") << KDbField::Enum << QVariant(123) << false << true << false; ++c; QTest::newRow("Map") << KDbField::Map << QVariant(123) << false << true << false; ++c; QTest::newRow("Tuple") << KDbField::Tuple << QVariant(123) << false << true << false; QCOMPARE(c, KDbField::typesCount() + KDbField::specialTypesCount()); } void KDbTest::testIsEmptyValue() { QFETCH(KDbField::Type, type); QFETCH(QVariant, value); QFETCH(bool, result); QFETCH(bool, resultForNullValue); QFETCH(bool, resultForEmptyString); QCOMPARE(KDb::isEmptyValue(type, QVariant()), resultForNullValue); QCOMPARE(KDb::isEmptyValue(type, QVariant(QString(""))), resultForEmptyString); QCOMPARE(KDb::isEmptyValue(type, value), result); } //! @todo add tests #if 0 /*! Sets string pointed by @a msg to an error message retrieved from @a resultable, and string pointed by @a details to details of this error (server message and result number). Does nothing if @a result is empty. In this case @a msg and @a details strings are not overwritten. If the string pointed by @a msg is not empty, @a result message is appended to the string pointed by @a details. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, QString *msg, QString *details); /*! This methods works like above, but appends both a message and a description to string pointed by @a msg. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, QString *msg); /*! This methods works like above, but works on @a result's members instead. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, KDbResultInfo *info); /*! Function useful for building WHERE parts of SQL statements. Constructs an SQL string like "fielname = value" for specific @a drv driver, field type @a t, @a fieldName and @a value. If @a value is null, "fieldname is NULL" string is returned. */ KDB_EXPORT KDbEscapedString sqlWhere(KDbDriver *drv, KDbField::Type t, const QString& fieldName, const QVariant& value); /*! Find an identifier for object @a objName of type @a objType. On success true is returned and *id is set to the value of the identifier. On failure false is returned. If there is no such object, @c cancelled value is returned. */ KDB_EXPORT tristate idForObjectName(KDbConnection* conn, int *id, const QString& objName, int objType); //! @todo perhaps use quint64 here? /*! @return number of records that can be retrieved after executing @a sql statement within a connection @a conn. The statement should be of type SELECT. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occurred. */ KDB_EXPORT int recordCount(KDbConnection* conn, const KDbEscapedString& sql); //! @todo perhaps use quint64 here? /*! @return number of records that can be retrieved from @a tableSchema. The table must be created or retrieved using a KDbConnection object, i.e. tableSchema.connection() must not return 0. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occurred. */ KDB_EXPORT int recordCount(const KDbTableSchema& tableSchema); /*! @overload in rowCount(const KDbTableSchema& tableSchema) Operates on a query schema. @a params are optional values of parameters that will be inserted into places marked with [] before execution of the query. */ //! @todo perhaps use quint64 here? KDB_EXPORT int recordCount(KDbQuerySchema* querySchema, const QList& params = QList()); /*! @overload int rowCount(KDbQuerySchema& querySchema, const QList& params) Operates on a table or query schema. @a params are optional values of parameters that will be inserted into places marked with [] before execution of the query. */ //! @todo perhaps use quint64 here? KDB_EXPORT int recordCount(KDbTableOrQuerySchema* tableOrQuery, const QList& params = QList()); /*! @return a number of columns that can be retrieved from table or query schema. In case of query, expanded fields are counted. Can return -1 if @a tableOrQuery has neither table or query assigned. */ KDB_EXPORT int fieldCount(KDbTableOrQuerySchema* tableOrQuery); /*! shows connection test dialog with a progress bar indicating connection testing (within a second thread). @a data is used to perform a (temporary) test connection. @a msgHandler is used to display errors. On successful connecting, a message is displayed. After testing, temporary connection is closed. */ KDB_EXPORT void connectionTestDialog(QWidget* parent, const KDbConnectionData& data, KDbMessageHandler* msgHandler); //! Used in splitToTableAndFieldParts(). enum SplitToTableAndFieldPartsOptions { FailIfNoTableOrFieldName = 0, //!< default value for splitToTableAndFieldParts() SetFieldNameIfNoTableName = 1 //!< see splitToTableAndFieldParts() }; /*! Splits @a string like "table.field" into "table" and "field" parts. On success, a table name is passed to @a tableName and a field name is passed to @a fieldName. The function fails if either: - @a string is empty, or - @a string does not contain '.' character and @a option is FailIfNoTableOrFieldName (the default), or - '.' character is the first of last character of @a string (in this case table name or field name could become empty what is not allowed). If @a option is SetFieldNameIfNoTableName and @a string does not contain '.', @a string is passed to @a fieldName and @a tableName is set to QString() without failure. If function fails, @a tableName and @a fieldName remain unchanged. @return true on success. */ KDB_EXPORT bool splitToTableAndFieldParts(const QString& string, QString *tableName, QString *fieldName, SplitToTableAndFieldPartsOptions option = FailIfNoTableOrFieldName); /*! @return true if @a type supports "visibleDecimalPlaces" property. */ KDB_EXPORT bool supportsVisibleDecimalPlacesProperty(KDbField::Type type); //*! @return string constructed by converting @a value. * If @a decimalPlaces is < 0, all meaningful fractional digits are returned (up to 10). * If @a automatically is 0, just integer part is returned. * If @a automatically is > 0, fractional part should take exactly N digits: if the fractional part is shorter than N, additional zeros are appended. Examples: * numberToString(12.345, 6) == "12.345000" * numberToString(12.345, 0) == "12" * numberToString(12.345, -1) == "12.345" * numberToString(12.0, -1) == "12" * numberToString(0.0, -1) == "0" @note No rounding is performed @note No thousands group separator is used. Decimal symbol is '.'. @see KDb::numberToLocaleString() KDbField::visibleDecimalPlaces() */ KDB_EXPORT QString numberToString(double value, int decimalPlaces); /*! Like KDb::numberToString() but formats the string using locale.toString(). If @a locale if @c nullptr, desault QLocale is used. @see KDb::numberToString() KDbField::visibleDecimalPlaces() */ KDB_EXPORT QString numberToLocaleString(double value, int decimalPlaces, const QLocale *locale = nullptr); //! @return true if @a propertyName is a builtin field property. KDB_EXPORT bool isBuiltinTableFieldProperty(const QByteArray& propertyName); //! @return true if @a propertyName is an extended field property. KDB_EXPORT bool isExtendedTableFieldProperty(const QByteArray& propertyName); //! @return true if @a propertyName is belongs to lookup field's schema. KDB_EXPORT bool isLookupFieldSchemaProperty(const QByteArray& propertyName); /*! @return type of field for integer value @a type. If @a type cannot be casted to KDbField::Type, KDbField::InvalidType is returned. This can be used when type information is deserialized from a string or QVariant. */ KDB_EXPORT KDbField::Type intToFieldType(int type); /*! @return type group of field for integer value @a typeGroup. If @a typeGroup cannot be casted to KDbField::TypeGroup, KDbField::InvalidGroup is returned. This can be used when type information is deserialized from a string or QVariant. */ KDB_EXPORT KDbField::TypeGroup intToFieldTypeGroup(int typeGroup); /*! Gets property values for the lookup schema @a lookup. @a values is cleared before filling. This function is used e.g. for altering table design. */ KDB_EXPORT void getProperties(const KDbLookupFieldSchema *lookup, QMap *values); /*! Gets property values for @a field. Properties from extended schema are included. @a values is cleared before filling. The same number of properties in the same order is returned. This function is used e.g. for altering table design. */ KDB_EXPORT void getFieldProperties(const KDbField &field, QMap *values); /*! Sets property values for @a field. @return true if all the values are valid and allowed. On failure contents of @a field is undefined. Properties from extended schema are also supported. This function is used e.g. by KDbAlterTableHandler when property information comes in form of text. */ KDB_EXPORT bool setFieldProperties(KDbField *field, const QMap& values); /*! Sets property value for @a field. @return true if the property has been found and the value is valid for this property. On failure contents of @a field is undefined. Properties from extended schema are also supported as well as QVariant customProperty(const QString& propertyName) const; This function is used e.g. by KDbAlterTableHandler when property information comes in form of text. */ KDB_EXPORT bool setFieldProperty(KDbField *field, const QByteArray& propertyName, const QVariant& value); /*! @return property value loaded from a DOM @a node, written in a QtDesigner-like notation: <number>int</number> or <bool>bool</bool>, etc. Supported types are "string", "cstring", "bool", "number". For invalid values null QVariant is returned. You can check the validity of the returned value using QVariant::type(). */ KDB_EXPORT QVariant loadPropertyValueFromDom(const QDomNode& node, bool *ok); /*! Convenience version of loadPropertyValueFromDom(). @return int value. */ KDB_EXPORT int loadIntPropertyValueFromDom(const QDomNode& node, bool* ok); /*! Convenience version of loadPropertyValueFromDom(). @return QString value. */ KDB_EXPORT QString loadStringPropertyValueFromDom(const QDomNode& node, bool* ok); /*! Saves integer element for value @a value to @a doc document within parent element @a parentEl. The value will be enclosed in "number" element and "elementName" element. Example: saveNumberElementToDom(doc, parentEl, "height", 15) will create @code 15 @endcode @return the reference to element created with tag elementName. */ KDB_EXPORT QDomElement saveNumberElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, int value); /*! Saves boolean element for value @a value to @a doc document within parent element @a parentEl. Like saveNumberElementToDom() but creates "bool" tags. True/false values will be saved as "true"/"false" strings. @return the reference to element created with tag elementName. */ KDB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, bool value); //! @return equivalent of empty (default) value that can be set for a database field of type @a type /*! In particular returns: - empty string for text types, - 0 for integer and floating-point types, - false for boolean types, - a null byte array for BLOB type, - current date, time, date+time is returned (measured at client side) for date, time and date/time types respectively, - a null QVariant for unsupported values such as KDbField::InvalidType. */ KDB_EXPORT QVariant emptyValueForFieldType(KDbField::Type type); //! @return a value that can be set for a database field of type @a type having "notEmpty" property set. /*! It works in a similar way as @ref QVariant KDb::emptyValueForFieldType(KDbField::Type type) with the following differences: - " " string (a single space) is returned for Text and LongText types - a byte array with saved "filenew" PNG image (icon) for BLOB type Returns null QVariant for unsupported values like KDbField::InvalidType. */ KDB_EXPORT QVariant notEmptyValueForFieldType(KDbField::Type type); /*! @return true if the @a word is an reserved KDbSQL keyword See src/generated/sqlkeywords.cpp in the KDb source code. @todo add function returning list of keywords. */ KDB_EXPORT bool isKDbSQLKeyword(const QByteArray& word); //! @return @a string string with applied KDbSQL identifier escaping /*! This escaping can be used for field, table, database names, etc. Use it for user-visible backend-independent statements. @see KDb::escapeIdentifierAndAddQuotes() */ KDB_EXPORT QString escapeIdentifier(const QString& string); //! @overload QString escapeIdentifier(const QString&) KDB_EXPORT QByteArray escapeIdentifier(const QByteArray& string); //! @return @a string string with applied KDbSQL identifier escaping and enclosed in " quotes /*! This escaping can be used for field, table, database names, etc. Use it for user-visible backend-independent statements. @see KDb::escapeIdentifier */ KDB_EXPORT QString escapeIdentifierAndAddQuotes(const QString& string); //! @overload QString escapeIdentifierAndAddQuotes(const QString&) KDB_EXPORT QByteArray escapeIdentifierAndAddQuotes(const QByteArray& string); /*! @return escaped string @a string w using KDbSQL dialect, i.e. doubles single quotes ("'") and inserts the string into single quotes. Quotes "'" are prepended and appended. Also escapes \\n, \\r, \\t, \\\\, \\0. Use it for user-visible backend-independent statements. */ KDB_EXPORT QString escapeString(const QString& string); #endif void KDbTest::testUnescapeString_data() { QTest::addColumn("sequence"); QTest::addColumn("result"); QTest::addColumn("quote"); // can be ' or ", if 0 then both variants are checked QTest::addColumn("errorPosition"); QTest::addColumn("errorPositionWhenAppended"); // quote-independent cases, success #define T2(tag, sequence, result, quote) QTest::newRow(tag) << QString::fromUtf8(sequence) \ << QString::fromUtf8(result) << quote << -1 << -1 #define T(tag, sequence, result) T2(tag, sequence, result, '\0') QTest::newRow("null") << QString() << QString() << '\0' << -1 << -1; QTest::newRow("\\0") << QString("\\0") << QString(QLatin1Char('\0')) << '\0' << -1 << -1; const char *s = " String without escaping %_? 𝌆 ©"; T("without escaping", s, s); T("empty", "", ""); T("\\'", "\\'", "'"); T("\\\"", "\\\"", "\""); T("\\\\", "\\\\", "\\"); T("\\b", "\\b", "\b"); T("\\f", "\\f", "\f"); T("\\n", "\\n", "\n"); T("\\r", "\\r", "\r"); T("\\t", "\\t", "\t"); T("\\v", "\\v", "\v"); T("_\\_", "_\\_", "__"); T("?\\?", "?\\?", "??"); T("%\\%", "%\\%", "%%"); T("ignored \\ in \\a", "\\a", "a"); T("ignored \\ in \\♥", "\\♥ ", "♥ "); T("ignored \\ in 𝌆\\\\\\a", "𝌆\\\\\\a", "𝌆\\a"); T("unfinished \\", "\\", ""); T("unfinished \\ 2", "one two\\", "one two"); T("\\xA9", "\\xA9", "©"); T("\\xa9\\xa9", "\\xa9\\xa9", "©©"); QTest::newRow("\\x00") << QString("\\x00") << QString(QLatin1Char('\0')) << '\0' << -1 << -1; QTest::newRow("\\u0000") << QString("\\u0000") << QString(QChar(static_cast(0))) << '\0' << -1 << -1; T("\\u2665", "\\u2665", "♥"); #ifndef _MSC_VER // does not work with MSVC: "warning C4566: character represented // by universal-character-name cannot be represented in the current code page" T("\\xff", "\\xff", "\u00ff"); T("\\uffff", "\\uffff", "\uffff"); #endif QTest::newRow("\\u{0}") << QString("\\u{0}") << QString(QLatin1Char('\0')) << '\0' << -1 << -1; QTest::newRow("\\u{0000000000}") << QString("\\u{0000000000}") << QString(QLatin1Char('\0')) << '\0' << -1 << -1; T("\\u{A9}", "\\u{A9}", "©"); T("\\u{a9}", "\\u{a9}", "©"); T("\\u{0a9}", "\\u{0a9}", "©"); T("\\u{00a9}", "\\u{00a9}", "©"); T("\\u{2665}", "\\u{2665}", "♥"); T("\\u{02665}", "\\u{02665}", "♥"); QTest::newRow("\\u{1D306}") << QString("\\u{1D306}") << QString(QChar(0x1D306)) << '\0' << -1 << -1; QTest::newRow("\\u{1d306}") << QString("\\u{1d306}") << QString(QChar(0x1d306)) << '\0' << -1 << -1; QTest::newRow("\\u{01D306}") << QString("\\u{01D306}") << QString(QChar(0x1D306)) << '\0' << -1 << -1; QTest::newRow("\\u{01d306}") << QString("\\u{01d306}") << QString(QChar(0x1d306)) << '\0' << -1 << -1; QTest::newRow("\\u{00001D306}") << QString("\\u{00001D306}") << QString(QChar(0x1D306)) << '\0' << -1 << -1; QTest::newRow("\\u{10FFFF}") << QString("\\u{10FFFF}") << QString(QChar(0x10FFFF)) << '\0' << -1 << -1; // quote-dependent cases, success T2("2x ' for ' quote", "''", "'", '\''); T2("4x ' for ' quote", "''''", "''", '\''); T2("2x \" for ' quote", "\"\"", "\"\"", '\''); T2("3x \" for ' quote", "\"\"\"", "\"\"\"", '\''); T2("2x ' for \" quote", "''", "''", '"'); T2("3x ' for \" quote", "'''", "'''", '"'); T2("2x \" for \" quote", "\"\"", "\"", '"'); T2("4x \" for \" quote", "\"\"\"\"", "\"\"", '"'); #undef T #undef T2 // failures QTest::newRow("invalid quote") << QString::fromUtf8("abc") << QString() << 'x' << 0 << 0; #define T(tag, sequence, quote, errorPosition, errorPositionWhenAppended) \ QTest::newRow(tag) << QString::fromUtf8(sequence) << QString() << quote \ << errorPosition << errorPositionWhenAppended T("missing ' quote", "'", '\'', 0, 0); T("missing \" quote", "\"", '"', 0, 0); T("invalid \\x", "\\x", '\0', 1, 2); T("invalid \\xQ", "\\xQ", '\0', 2, 2); T("invalid \\xQt", "\\xQt", '\0', 2, 2); T("invalid \\xAQ", "\\xAQ", '\0', 3, 3); T("invalid \\u", "\\u", '\0', 1, 2); T("invalid \\ua", "\\ua", '\0', 2, 3); T("invalid \\u40", "\\u40", '\0', 3, 4); T("invalid \\u405", "\\u405", '\0', 4, 5); T("invalid \\uQ", "\\uQ", '\0', 2, 2); T("invalid \\uQt", "\\uQt", '\0', 2, 2); T("invalid \\uQt5", "\\uQt5", '\0', 2, 2); T("invalid \\uQt57", "\\uQt57", '\0', 2, 2); T("invalid \\uaQ", "\\uaQ", '\0', 3, 3); T("invalid \\uabQ", "\\uabQ", '\0', 4, 4); T("invalid \\uabcQ", "\\uabcQ", '\0', 5, 5); T("invalid \\u{", "\\u{", '\0', 2, 3); T("invalid \\u{26", "\\u{26", '\0', 4, 5); T("invalid \\u{266", "\\u{266", '\0', 5, 6); T("invalid \\u{2665", "\\u{2665", '\0', 6, 7); T("invalid \\u{2665a", "\\u{2665a", '\0', 7, 8); T("invalid \\u{}", "\\u{}", '\0', 3, 3); T("invalid \\u{Q}", "\\u{Q}", '\0', 3, 3); T("invalid \\u{Qt}", "\\u{Qt}", '\0', 3, 3); T("invalid \\u{Qt5}", "\\u{Qt5}", '\0', 3, 3); T("invalid \\u{Qt57}", "\\u{Qt57}", '\0', 3, 3); T("invalid \\u{Qt57", "\\u{Qt57", '\0', 3, 3); T("invalid \\u{aQ}", "\\u{aQ}", '\0', 4, 4); T("invalid \\u{abQ}", "\\u{abQ}", '\0', 5, 5); T("invalid \\u{abcQ}", "\\u{abcQ}", '\0', 6, 6); T("invalid \\u{abcdQ}", "\\u{abcdQ}", '\0', 7, 7); T("invalid \\u{abcdQ}", "\\u{abcdQ}", '\0', 7, 7); T("invalid \\u{abcdfQ}", "\\u{abcdfQ}", '\0', 8, 8); T("invalid too large \\u{110000}", "\\u{110000}", '\0', 8, 8); T("invalid too large \\u{1100000}", "\\u{1100000}", '\0', 8, 8); T("invalid too large \\u{00110000}", "\\u{00110000}", '\0', 10, 10); } void KDbTest::testUnescapeStringHelper(const QString &sequenceString, const QString &resultString_, char quote, int errorPosition, int offset) { int actualErrorPosition = -2; QString resultString(resultString_); if (errorPosition >= 0) { errorPosition += offset; resultString.clear(); } //qDebug() << KDb::unescapeString("\\0bar", '\'', &errorPosition); #define COMPARE(x, y) \ if (x != y) { \ qDebug() << "sequenceString:" << sequenceString << "resultString:" << resultString; \ } \ QCOMPARE(x, y) if (quote == 0) { // both cases COMPARE(KDb::unescapeString(sequenceString, '\'', &actualErrorPosition), resultString); COMPARE(actualErrorPosition, errorPosition); - COMPARE(KDb::unescapeString(sequenceString, '\'', 0), resultString); + COMPARE(KDb::unescapeString(sequenceString, '\'', nullptr), resultString); COMPARE(KDb::unescapeString(sequenceString, '"', &actualErrorPosition), resultString); COMPARE(actualErrorPosition, errorPosition); - COMPARE(KDb::unescapeString(sequenceString, '"', 0), resultString); + COMPARE(KDb::unescapeString(sequenceString, '"', nullptr), resultString); } else { if (quote != '\'' && quote != '"') { resultString.clear(); errorPosition = 0; } COMPARE(KDb::unescapeString(sequenceString, quote, &actualErrorPosition), resultString); COMPARE(actualErrorPosition, errorPosition); - COMPARE(KDb::unescapeString(sequenceString, quote, 0), resultString); + COMPARE(KDb::unescapeString(sequenceString, quote, nullptr), resultString); } #undef CHECK_POS } void KDbTest::testUnescapeString() { QFETCH(QString, sequence); QFETCH(QString, result); QFETCH(char, quote); QFETCH(int, errorPosition); QFETCH(int, errorPositionWhenAppended); testUnescapeStringHelper(sequence, result, quote, errorPosition, 0); testUnescapeStringHelper("foo" + sequence, "foo" + result, quote, errorPosition, 3); testUnescapeStringHelper(sequence + " bar", result + " bar", quote, errorPositionWhenAppended, 0); testUnescapeStringHelper("foo" + sequence + " bar", "foo" + result + " bar", quote, errorPositionWhenAppended, 3); } void KDbTest::testEscapeBLOB_data() { QTest::addColumn("blob"); QTest::addColumn("escapedX"); QTest::addColumn("escaped0x"); QTest::addColumn("escapedHex"); QTest::addColumn("escapedOctal"); QTest::addColumn("escapedBytea"); QTest::newRow("") << QByteArray() << QString("X''") << QString() << QString("") << QString("''") << QString("E'\\\\x'::bytea"); QTest::newRow("0,1,k") << QByteArray("\0\1k", 3) << QString("X'00016B'") << QString("0x00016B") << QString("00016B") << QString("'\\\\000\\\\001k'") << QString("E'\\\\x00016B'::bytea"); QTest::newRow("ABC\\\\0") << QByteArray("ABC\0", 4) << QString("X'41424300'") << QString("0x41424300") << QString("41424300") << QString("'ABC\\\\000'") << QString("E'\\\\x41424300'::bytea"); QTest::newRow("'") << QByteArray("'") << QString("X'27'") << QString("0x27") << QString("27") << QString("'\\\\047'") << QString("E'\\\\x27'::bytea"); QTest::newRow("\\") << QByteArray("\\") << QString("X'5C'") << QString("0x5C") << QString("5C") << QString("'\\\\134'") << QString("E'\\\\x5C'::bytea"); } void KDbTest::testEscapeBLOB() { QFETCH(QByteArray, blob); QFETCH(QString, escapedX); QFETCH(QString, escaped0x); QFETCH(QString, escapedHex); QFETCH(QString, escapedOctal); QFETCH(QString, escapedBytea); QCOMPARE(KDb::escapeBLOB(blob, KDb::BLOBEscapeXHex), escapedX); QCOMPARE(KDb::escapeBLOB(blob, KDb::BLOBEscape0xHex), escaped0x); QCOMPARE(KDb::escapeBLOB(blob, KDb::BLOBEscapeHex), escapedHex); QCOMPARE(KDb::escapeBLOB(blob, KDb::BLOBEscapeOctal), escapedOctal); QCOMPARE(KDb::escapeBLOB(blob, KDb::BLOBEscapeByteaHex), escapedBytea); } void KDbTest::testPgsqlByteaToByteArray() { - QCOMPARE(KDb::pgsqlByteaToByteArray(0, 0), QByteArray()); + QCOMPARE(KDb::pgsqlByteaToByteArray(nullptr, 0), QByteArray()); QCOMPARE(KDb::pgsqlByteaToByteArray("", 0), QByteArray()); QCOMPARE(KDb::pgsqlByteaToByteArray(" ", 0), QByteArray()); QCOMPARE(KDb::pgsqlByteaToByteArray("\\101"), QByteArray("A")); QCOMPARE(KDb::pgsqlByteaToByteArray("\\101", 4), QByteArray("A")); QCOMPARE(KDb::pgsqlByteaToByteArray("\\101B", 4), QByteArray("A")); // cut-off at #4 QCOMPARE(KDb::pgsqlByteaToByteArray("\\'\\\\\\'"), QByteArray("\'\\\'")); QCOMPARE(KDb::pgsqlByteaToByteArray("\\\\a\\377bc\\'d\"\n"), QByteArray("\\a\377bc\'d\"\n")); } void KDbTest::testXHexToByteArray_data() { QTest::addColumn("data"); QTest::addColumn("length"); // -2 means "compute length", other values: pass it as is QTest::addColumn("ok"); QTest::addColumn("result"); QTest::newRow("") << QByteArray() << 0 << false << QByteArray(); QTest::newRow("bad prefix") << QByteArray("bad") << -2 << false << QByteArray(); QTest::newRow("X") << QByteArray("X") << -2 << false << QByteArray(); QTest::newRow("X'") << QByteArray("X'") << -2 << false << QByteArray(); QTest::newRow("X''") << QByteArray("X''") << -2 << true << QByteArray(); QTest::newRow("X'1") << QByteArray("X'1") << -2 << false << QByteArray(); QTest::newRow("X'1' cut") << QByteArray("X'1'") << 3 << false << QByteArray(); QTest::newRow("X'1'") << QByteArray("X'1'") << -2 << true << QByteArray("\1"); QTest::newRow("X'0'") << QByteArray("X'0'") << -2 << true << QByteArray("\0", 1); QTest::newRow("X'000'") << QByteArray("X'000'") << -2 << true << QByteArray("\0\0", 2); QTest::newRow("X'01'") << QByteArray("X'01'") << -2 << true << QByteArray("\1"); QTest::newRow("X'FeAb2C'") << QByteArray("X'FeAb2C'") << -2 << true << QByteArray("\376\253\54"); } void KDbTest::testXHexToByteArray() { QFETCH(QByteArray, data); QFETCH(int, length); QFETCH(bool, ok); QFETCH(QByteArray, result); bool actualOk; QCOMPARE(KDb::xHexToByteArray(data.constData(), length == -1 ? data.length() : length, &actualOk), result); QCOMPARE(actualOk, ok); - QCOMPARE(KDb::xHexToByteArray(data.constData(), length, 0), result); + QCOMPARE(KDb::xHexToByteArray(data.constData(), length, nullptr), result); } void KDbTest::testZeroXHexToByteArray_data() { QTest::addColumn("data"); QTest::addColumn("length"); // -2 means "compute length", other values: pass it as is QTest::addColumn("ok"); QTest::addColumn("result"); QTest::newRow("") << QByteArray() << 0 << false << QByteArray(); QTest::newRow("0") << QByteArray("0") << -2 << false << QByteArray(); QTest::newRow("0x") << QByteArray("0x") << -2 << false << QByteArray(); QTest::newRow("0X22") << QByteArray("0X22") << -2 << false << QByteArray(); QTest::newRow("bad prefix") << QByteArray("bad") << -2 << false << QByteArray(); QTest::newRow("0x0") << QByteArray("0x0") << -2 << true << QByteArray("\0", 1); QTest::newRow("0x0 cut") << QByteArray("0x0") << 2 << false << QByteArray(); QTest::newRow("0X0") << QByteArray("0X0") << -2 << false << QByteArray(); QTest::newRow("0x0123") << QByteArray("0x0123") << -2 << true << QByteArray("\1\43"); QTest::newRow("0x0123 cut") << QByteArray("0x0123") << 4 << true << QByteArray("\1"); QTest::newRow("0x00000'") << QByteArray("0x00000") << -2 << true << QByteArray("\0\0\0", 3); QTest::newRow("0xFeAb2C") << QByteArray("0xFeAb2C") << -2 << true << QByteArray("\376\253\54"); } void KDbTest::testZeroXHexToByteArray() { QFETCH(QByteArray, data); QFETCH(int, length); QFETCH(bool, ok); QFETCH(QByteArray, result); bool actualOk; QCOMPARE(KDb::zeroXHexToByteArray(data.constData(), length == -1 ? data.length() : length, &actualOk), result); QCOMPARE(actualOk, ok); - QCOMPARE(KDb::zeroXHexToByteArray(data.constData(), length, 0), result); + QCOMPARE(KDb::zeroXHexToByteArray(data.constData(), length, nullptr), result); } //! @todo add tests #if 0 /*! @return int list converted from string list. If @a ok is not 0, *ok is set to result of the conversion. */ KDB_EXPORT QList stringListToIntList(const QStringList &list, bool *ok); /*! @return string converted from list @a list. Separators are ',' characters, "," and "\\" are escaped. @see KDb::deserializeList() */ KDB_EXPORT QString serializeList(const QStringList &list); /*! @return string list converted from @a data which was built using serializeList(). Separators are ',' characters, escaping is assumed as "\\,". */ KDB_EXPORT QStringList deserializeList(const QString &data); /*! @return int list converted from @a data which was built using serializeList(). Separators are ',' characters, escaping is assumed as "\\,". If @a ok is not 0, *ok is set to result of the conversion. @see KDb::stringListToIntList() */ KDB_EXPORT QList deserializeIntList(const QString &data, bool *ok); /*! @return string value serialized from a variant value @a v. This functions works like QVariant::toString() except the case when @a v is of type: - QByteArray - in this case KDb::escapeBLOB(v.toByteArray(), KDb::BLOBEscapeHex) is used. - QStringList - in this case KDb::serializeList(v.toStringList()) is used. This function is needed for handling values of random type, for example "defaultValue" property of table fields can contain value of any type. Note: the returned string is an unescaped string. */ KDB_EXPORT QString variantToString(const QVariant& v); /*! @return variant value of type @a type for a string @a s that was previously serialized using @ref variantToString( const QVariant& v ) function. @a ok is set to the result of the operation. With exception for types mentioned in documentation of variantToString(), QVariant::convert() is used for conversion. */ KDB_EXPORT QVariant stringToVariant(const QString& s, QVariant::Type type, bool* ok); /*! @return true if setting default value for @a field field is allowed. Fields with unique (and thus primary key) flags set do not accept default values. */ KDB_EXPORT bool isDefaultValueAllowed(const KDbField &field); //! Provides limits for values of type @a type /*! The result is put into integers pointed by @a minValue and @a maxValue. The limits are machine-independent,. what is useful for format and protocol compatibility. Supported types are Byte, ShortInteger, Integer and BigInteger. The value of @a signedness controls the values; they can be limited to unsigned or not. Results for BigInteger or non-integer types are the same as for Integer due to limitation of int type. Signed integers are assumed. @a minValue and @a maxValue must not be 0. */ KDB_EXPORT void getLimitsForFieldType(KDbField::Type type, qlonglong *minValue, qlonglong *maxValue, KDb::Signedness signedness = KDb::Signed); /*! @return type that's maximum of two integer types @a t1 and @a t2, e.g. Integer for (Byte, Integer). If one of the types is not of the integer group, KDbField::InvalidType is returned. Returned type may not fit to the result of evaluated expression that involves the arguments. For example, 100 is within Byte type, maximumForIntegerFieldTypes(Byte, Byte) is Byte but result of 100 * 100 exceeds the range of Byte. */ KDB_EXPORT KDbField::Type maximumForIntegerFieldTypes(KDbField::Type t1, KDbField::Type t2); #endif void KDbTest::testCstringToVariant_data() { QTest::addColumn("data"); // QString() -> 0, QString("") -> empty string "" QTest::addColumn("type"); QTest::addColumn("length"); QTest::addColumn("variant"); QTest::addColumn("signedness"); QTest::addColumn("okResult"); int c = 0; ++c; QTest::newRow("invalid1") << QString() << KDbField::InvalidType << -1 << QVariant() << KDb::Signed << false; QTest::newRow("invalid2") << "" << KDbField::InvalidType << -1 << QVariant() << KDb::Signed << false; QTest::newRow("invalid3") << "abc" << KDbField::InvalidType << 3 << QVariant() << KDb::Signed << false; ++c; QTest::newRow("byte1") << "0" << KDbField::Byte << 1 << QVariant(0) << KDb::Signed << true; QTest::newRow("ubyte1") << "0" << KDbField::Byte << 1 << QVariant(0) << KDb::Unsigned << true; QTest::newRow("byte2") << "42" << KDbField::Byte << -1 << QVariant(42) << KDb::Signed << true; QTest::newRow("ubyte2") << "42" << KDbField::Byte << -1 << QVariant(42) << KDb::Unsigned << true; QTest::newRow("byte3") << "129" << KDbField::Byte << -1 << QVariant() << KDb::Signed << false; QTest::newRow("ubyte3") << "129" << KDbField::Byte << -1 << QVariant(129) << KDb::Unsigned << true; QTest::newRow("byte4") << "-128" << KDbField::Byte << -1 << QVariant(-128) << KDb::Signed << true; QTest::newRow("ubyte4") << "-128" << KDbField::Byte << -1 << QVariant() << KDb::Unsigned << false; ++c; QTest::newRow("short1") << "-123" << KDbField::ShortInteger << -1 << QVariant(-123) << KDb::Signed << true; QTest::newRow("short2") << "942" << KDbField::ShortInteger << -1 << QVariant(942) << KDb::Signed << true; QTest::newRow("short3") << "32767" << KDbField::ShortInteger << -1 << QVariant(32767) << KDb::Signed << true; QTest::newRow("short4") << "32768" << KDbField::ShortInteger << -1 << QVariant() << KDb::Signed << false; QTest::newRow("ushort4") << "32768" << KDbField::ShortInteger << -1 << QVariant(32768) << KDb::Unsigned << true; QTest::newRow("short5") << "-32768" << KDbField::ShortInteger << -1 << QVariant(-32768) << KDb::Signed << true; QTest::newRow("ushort5") << "-32768" << KDbField::ShortInteger << -1 << QVariant() << KDb::Unsigned << false; ++c; QTest::newRow("int1") << QString::number(0x07FFFFFFF) << KDbField::Integer << -1 << QVariant(0x07FFFFFFF) << KDb::Signed << true; QTest::newRow("uint1") << QString::number(0x07FFFFFFF) << KDbField::Integer << -1 << QVariant(0x07FFFFFFF) << KDb::Unsigned << true; QTest::newRow("int2") << QString::number(-0x07FFFFFFF) << KDbField::Integer << -1 << QVariant(-0x07FFFFFFF) << KDb::Signed << true; QTest::newRow("uint2") << QString::number(-0x07FFFFFFF) << KDbField::Integer << -1 << QVariant() << KDb::Unsigned << false; QTest::newRow("int3") << QString::number(std::numeric_limits::min()) << KDbField::Integer << -1 << QVariant() << KDb::Signed << false; QTest::newRow("uint4") << "-1" << KDbField::Integer << -1 << QVariant() << KDb::Unsigned << false; QTest::newRow("int4") << "-1" << KDbField::Integer << -1 << QVariant(-1) << KDb::Signed << true; //!< @todo cannot be larger? ++c; QTest::newRow("bigint1") << QString::number(0x07FFFFFFF) << KDbField::BigInteger << -1 << QVariant(0x07FFFFFFF) << KDb::Signed << true; QTest::newRow("ubigint1") << QString::number(0x07FFFFFFF) << KDbField::BigInteger << -1 << QVariant(0x07FFFFFFF) << KDb::Unsigned << true; QTest::newRow("bigint2") << QString::number(-0x07FFFFFFF) << KDbField::BigInteger << -1 << QVariant(-0x07FFFFFFF) << KDb::Signed << true; QTest::newRow("ubigint2") << QString::number(-0x07FFFFFFF) << KDbField::BigInteger << -1 << QVariant() << KDb::Unsigned << false; QTest::newRow("bigint3") << QString::number(std::numeric_limits::min()) << KDbField::BigInteger << -1 << QVariant() << KDb::Signed << false; QTest::newRow("ubigint4") << "-1" << KDbField::BigInteger << -1 << QVariant() << KDb::Unsigned << false; QTest::newRow("bigint4") << "-1" << KDbField::BigInteger << -1 << QVariant(-1) << KDb::Signed << true; ++c; QTest::newRow("bool0") << "0" << KDbField::Boolean << -1 << QVariant(false) << KDb::Signed << true; QTest::newRow("bool1") << "1" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; QTest::newRow("bool-") << "-" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; QTest::newRow("bool5") << "5" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; QTest::newRow("bool false") << "false" << KDbField::Boolean << -1 << QVariant(false) << KDb::Signed << true; QTest::newRow("bool False") << "False" << KDbField::Boolean << -1 << QVariant(false) << KDb::Signed << true; QTest::newRow("bool TRUE") << "TRUE" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; QTest::newRow("bool true") << "true" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; QTest::newRow("bool no") << "no" << KDbField::Boolean << -1 << QVariant(true) << KDb::Signed << true; // surprised? See docs for QVariant::toBool(). ++c; //! @todo support Date ++c; //! @todo support DateTime ++c; //! @todo support Time ++c; //! @todo support Float ++c; //! @todo support Double ++c; //! @todo support Text ++c; //! @todo support LongText ++c; //! @todo support BLOB ++c; QTest::newRow("Null") << " " << KDbField::Null << -1 << QVariant() << KDb::Signed << false; ++c; QTest::newRow("Asterisk") << " " << KDbField::Asterisk << -1 << QVariant() << KDb::Signed << false; ++c; QTest::newRow("Enum") << " " << KDbField::Enum << -1 << QVariant() << KDb::Signed << false; ++c; QTest::newRow("Map") << " " << KDbField::Map << -1 << QVariant() << KDb::Signed << false; ++c; QTest::newRow("Tuple") << " " << KDbField::Tuple << -1 << QVariant() << KDb::Signed << false; QCOMPARE(c, KDbField::typesCount() + KDbField::specialTypesCount()); } void KDbTest::testCstringToVariant() { QFETCH(QString, data); QFETCH(KDbField::Type, type); QFETCH(int, length); QFETCH(QVariant, variant); QFETCH(KDb::Signedness, signedness); QFETCH(bool, okResult); bool ok; const QByteArray ba(data.toUtf8()); // to avoid pointer to temp. - const char *realData = ba.isNull() ? 0 : ba.constData(); + const char *realData = ba.isNull() ? nullptr : ba.constData(); QCOMPARE(KDb::cstringToVariant(realData, type, &ok, length, signedness), variant); QCOMPARE(ok, okResult); - QCOMPARE(KDb::cstringToVariant(realData, type, 0, length, signedness), variant); // a case where ok == 0 + QCOMPARE(KDb::cstringToVariant(realData, type, nullptr, length, signedness), variant); // a case where ok == 0 if (realData) { QCOMPARE(KDb::cstringToVariant(realData, type, &ok, data.length(), signedness), variant); // a case where length is set QCOMPARE(ok, okResult); } - QCOMPARE(KDb::cstringToVariant(0, type, &ok, length, signedness), QVariant()); // a case where data == 0 (NULL) + QCOMPARE(KDb::cstringToVariant(nullptr, type, &ok, length, signedness), QVariant()); // a case where data == 0 (NULL) QVERIFY(ok || type < KDbField::Byte || type > KDbField::LastType); // fails for NULL if this type isn't allowed if (type != KDbField::Boolean) { QCOMPARE(KDb::cstringToVariant(realData, type, &ok, 0, signedness), QVariant()); // a case where length == 0 QVERIFY(!ok); } if (KDbField::isTextType(type)) { // a case where data == "" QCOMPARE(KDb::cstringToVariant("", type, &ok, length, signedness), QVariant("")); QVERIFY(ok); } else if (type != KDbField::Boolean) { QCOMPARE(KDb::cstringToVariant("", type, &ok, length, signedness), QVariant()); QVERIFY(!ok); } } //! @todo add tests #if 0 /*! @return default file-based driver MIME type (typically something like "application/x-kexiproject-sqlite") */ KDB_EXPORT QString defaultFileBasedDriverMimeType(); /*! @return default file-based driver ID (currently, "org.kde.kdb.sqlite"). */ KDB_EXPORT QString defaultFileBasedDriverId(); /*! Escapes and converts value @a v (for type @a ftype) to string representation required by KDbSQL commands. For Date/Time type KDb::dateTimeToSQL() is used. For BLOB type KDb::escapeBlob() with BLOBEscape0xHex conversion type is used. */ KDB_EXPORT KDbEscapedString valueToSQL(KDbField::Type ftype, const QVariant& v); /*! Converts value @a v to string representation required by KDbSQL commands: ISO 8601 DateTime format - with "T" delimiter/ For specification see http://www.w3.org/TR/NOTE-datetime. Example: "1994-11-05T13:15:30" not "1994-11-05 13:15:30". @todo Add support for time zones */ KDB_EXPORT KDbEscapedString dateTimeToSQL(const QDateTime& v); #ifdef KDB_DEBUG_GUI //! A prototype of handler for GUI debugger typedef void(*DebugGUIHandler)(const QString&); //! Sets handler for GUI debugger KDB_EXPORT void setDebugGUIHandler(DebugGUIHandler handler); //! Outputs string @a text to the GUI debugger KDB_EXPORT void debugGUI(const QString& text); //! A prototype of handler for GUI debugger (specialized for the Alter Table feature) typedef void(*AlterTableActionDebugGUIHandler)(const QString&, int); //! Sets handler for GUI debugger (specialized for the Alter Table feature) KDB_EXPORT void setAlterTableActionDebugHandler(AlterTableActionDebugGUIHandler handler); //! Outputs string @a text to the GUI debugger (specialized for the Alter Table feature); //! @a nestingLevel can be provided for nested outputs. KDB_EXPORT void alterTableActionDebugGUI(const QString& text, int nestingLevel = 0); #endif //! @return @a string if it is not empty, else returns @a stringIfEmpty. /*! This function is an optimization in cases when @a string is a result of expensive functioncall because any evaluation will be performed once, not twice. Another advantage is simpified code through the functional approach. The function expects bool isEmpty() method to be present in type T, so T can typically be QString or QByteArray. */ template T iifNotEmpty(const T &string, const T &stringIfEmpty) { return string.isEmpty() ? stringIfEmpty : string; } //! @overload iifNotEmpty(const T &string, const T &stringIfEmpty) template T iifNotEmpty(const QByteArray &string, const T &stringIfEmpty) { return iifNotEmpty(QLatin1String(string), stringIfEmpty); } //! @overload iifNotEmpty(const T &string, const T &stringIfEmpty) template T iifNotEmpty(const T &string, const QByteArray &stringIfEmpty) { return iifNotEmpty(string, QLatin1String(stringIfEmpty)); } //! @return @a value if @a ok is true, else returns default value T(). template T iif(bool ok, const T &value) { if (ok) { return value; } return T(); } /*! @return a list of paths that KDb will search when dynamically loading libraries (plugins) This is basicaly list of directories returned QCoreApplication::libraryPaths() that have readable subdirectory "kdb". @see QCoreApplication::libraryPaths() */ KDB_EXPORT QStringList libraryPaths(); #endif void KDbTest::testTemporaryTableName() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); QString baseName = QLatin1String("foobar"); QString tempName1 = KDb::temporaryTableName(utils.connection.data(), baseName); QVERIFY(!tempName1.isEmpty()); QVERIFY(tempName1.contains(baseName)); QString tempName2 = KDb::temporaryTableName(utils.connection.data(), baseName); QVERIFY(!tempName2.isEmpty()); QVERIFY(tempName2.contains(baseName)); QVERIFY(tempName1 != tempName2); utils.connection->closeDatabase(); QString tempName = KDb::temporaryTableName(utils.connection.data(), baseName); QVERIFY2(tempName.isEmpty(), "Temporary name should not be created for closed connection"); utils.connection->disconnect(); tempName = KDb::temporaryTableName(utils.connection.data(), baseName); QVERIFY2(tempName.isEmpty(), "Temporary name should not be created for closed connection"); utils.connection->dropDatabase(utils.connection->data().databaseName()); } //! @todo add tests #if 0 /*! @return absolute path to "sqlite3" program. Empty string is returned if the program was not found. */ KDB_EXPORT QString sqlite3ProgramPath(); /*! Imports file in SQL format from @a inputFileName into @a outputFileName. Works for any SQLite 3 dump file. Requires access to executing the "sqlite3" command. File named @a outputFileName will be silently overwritten with a new SQLite 3 database file. @return true on success. */ KDB_EXPORT bool importSqliteFile(const QString &inputFileName, const QString &outputFileName); /*! @return true if @a s is a valid identifier, ie. starts with a letter or '_' character and contains only letters, numbers and '_' character. */ KDB_EXPORT bool isIdentifier(const QString& s); /*! @return valid identifier based on @a s. Non-alphanumeric characters (or spaces) are replaced with '_'. If a number is at the beginning, '_' is added at start. Empty strings are not changed. Case remains unchanged. */ KDB_EXPORT QString stringToIdentifier(const QString &s); /*! @return useful message "Value of "valueName" column must be an identifier. "v" is not a valid identifier.". It is also used by KDbIdentifierValidator. */ KDB_EXPORT QString identifierExpectedMessage(const QString &valueName, const QVariant& v); #endif void KDbTest::deleteRecordWithOneConstraintsTest() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "id", 2)); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "id", "3"), "Passing a valid Integer in String Format"); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "id", "Foo")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "name", "Jaroslaw")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "surname", "FooBar")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", 45)); // and empty data. KDbTableSchema *kdb_t = utils.connection.data()->tableSchema("persons"); QVERIFY(kdb_t); QVERIFY2(utils.connection.data()->insertRecord(kdb_t, 10, 20, QVariant(), "Bar"), "Inserting NULL data"); QVERIFY2(utils.connection.data()->insertRecord(kdb_t,15, 20, "", "Bar"), "Inserting empty data"); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "name", QString()), "Passing a null value instead of string"); // QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "name", ""), "Passing an empty string"); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", "Nitish")); QVERIFY(utils.testDisconnectAndDropDb()); } void KDbTest::deleteNonExistingRecordTest() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "id", 400)); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "name", "FooBar")); QVERIFY2(!KDb::deleteRecords(utils.connection.data(), "persons", "Foo", "FooBar"), "Passing a NonExisting Column - should fail because 'Foo' column does not exist, " "See also https://bugs.kde.org/376052"); QVERIFY(utils.testDisconnectAndDropDb()); } void KDbTest::deleteRecordWithTwoConstraintsTest() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "id", KDbField::Integer, 2, "age", KDbField::Integer, 60), "Both fields are INTEGER"); KDbTableSchema *kdb_t = utils.connection.data()->tableSchema("persons"); QVERIFY(kdb_t); utils.connection.data()->insertRecord(kdb_t, 10, QVariant(), "Foo", "Bar") ; QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "id", KDbField::Integer, 10, "age", KDbField::Integer, QVariant()), "Passing NULL value for integer field"); QVERIFY(utils.connection.data()->insertRecord(kdb_t, 20, QVariant(), QVariant(), "Bar")); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, QVariant(), "name", KDbField::Text, QVariant()), "Passing 2 NULL values"); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, 20, "name", KDbField::Text, "Jaroslaw"), "One argument is Integer and another is Text"); QVERIFY2(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, 20, "name", KDbField::Text, 56), "Two arguments, passing second integer instead of text but it is converted to text"); QVERIFY2(!KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, "TRAP", "name", KDbField::Text, 56), "Passing text instead of integer, conversion error expected"); QVERIFY(utils.testDisconnectAndDropDb()); } void KDbTest::deleteRecordWithThreeConstraintsTest() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); KDbTableSchema *kdb_t = utils.connection.data()->tableSchema("persons"); QVERIFY(kdb_t); //One null value. QVERIFY(utils.connection.data()->insertRecord(kdb_t, 10, QVariant(), "Foo", "Bar")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, QVariant(), "name", KDbField::Text, "Foo", "surname", KDbField::Text, "Bar")); //Mix of null and empty values QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, QVariant(), "name", KDbField::Text, "", "surname", KDbField::Text, "")); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer,27, "name", KDbField::Text, "Jaraslaw", "id", KDbField::Integer, 1)); QVERIFY(KDb::deleteRecords(utils.connection.data(), "persons", "age", KDbField::Integer, 60, "name", KDbField::Text, "Lech", "id", KDbField::Integer, 2)); QVERIFY(utils.testDisconnectAndDropDb()); } void KDbTest::deleteAllRecordsTest() { QVERIFY(utils.testCreateDbWithTables("KDbTest")); QVERIFY(KDb::deleteAllRecords(utils.connection.data(), "persons")); QRegularExpression deleteAllErrorRegExp( "KDbResult: .* MESSAGE=\\\"Error while executing SQL statement.\\\" ERR_SQL=KDbEscapedString:" "\\\"DELETE FROM \\[.*\\]\\\" SERVER_ERROR_CODE=0 SERVER_MESSAGE=\\\"no such table: "); QTest::ignoreMessage(QtWarningMsg, deleteAllErrorRegExp); QVERIFY2(!KDb::deleteAllRecords(utils.connection.data(), QString()), "Passing a null table name"); QTest::ignoreMessage(QtWarningMsg, deleteAllErrorRegExp); QVERIFY2(!KDb::deleteAllRecords(utils.connection.data(), ""), "Passing an empty table name"); QVERIFY(KDb::deleteAllRecords(utils.connection.data(), "cars")); QTest::ignoreMessage(QtWarningMsg, deleteAllErrorRegExp); QVERIFY2(!KDb::deleteAllRecords(utils.connection.data(), "NonExistingTable"), "Passing a nonexisting table name"); QVERIFY(utils.testDisconnectAndDropDb()); } void KDbTest::cleanupTestCase() { } diff --git a/autotests/KDbTestUtils.cpp b/autotests/KDbTestUtils.cpp index 576e8abe..49e293f0 100644 --- a/autotests/KDbTestUtils.cpp +++ b/autotests/KDbTestUtils.cpp @@ -1,243 +1,243 @@ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTestUtils.h" #include #include #include #include #include #include #include #include "KDbUtils_p.h" #include #include #include #include "../tests/features/tables_test_p.h" KDbTestUtils::KDbTestUtils() - : connection(0) + : connection(nullptr) { QCoreApplication::addLibraryPath(KDB_LOCAL_PLUGINS_DIR); // make plugins work without installing them } void KDbTestUtils::testDriverManagerInternal(bool forceEmpty) { DriverManagerInternal::self()->forceEmpty = forceEmpty; QStringList ids = manager.driverIds(); qDebug() << "DRIVERS:" << ids; QVERIFY2(forceEmpty == manager.result().isError(), "Error in driver manager"); qDebug() << manager.result().message(); QVERIFY2(forceEmpty == ids.isEmpty(), "No db drivers found"); if (forceEmpty) { // no drivers, so try to find one and expect failure ids << "org.kde.kdb.sqlite"; } for (const QString &id : qAsConst(ids)) { const KDbDriverMetaData* driverMetaData; if (forceEmpty) { KDB_EXPECT_FAIL(manager.resultable(), driverMetaData = manager.driverMetaData(id), ERR_DRIVERMANAGER, "Driver metadata not found"); // find driver for the metadata KDB_EXPECT_FAIL(manager.resultable(), driver = manager.driver(id), ERR_DRIVERMANAGER, "Driver not found"); } else { KDB_VERIFY(manager.resultable(), driverMetaData = manager.driverMetaData(id), "Driver metadata not found"); QCOMPARE(driverMetaData->id(), id); // find driver for the metadata KDB_VERIFY(manager.resultable(), driver = manager.driver(id), "Driver not found"); } } DriverManagerInternal::self()->forceEmpty = false; // default state } void KDbTestUtils::testDriverManagerInternal() { testDriverManagerInternal(true); testDriverManagerInternal(false); } void KDbTestUtils::testDriver(const QString &driverId, bool fileBased, const QStringList &mimeTypes) { // find the metadata const KDbDriverMetaData* driverMetaData; KDB_VERIFY(manager.resultable(), driverMetaData = manager.driverMetaData(driverId), "Driver metadata not found"); QCOMPARE(driverMetaData->id(), driverId); QCOMPARE(driverMetaData->isFileBased(), fileBased); // test the mimetypes QStringList foundMimeTypes(driverMetaData->mimeTypes()); foundMimeTypes.sort(); QStringList expectedMimeTypes(mimeTypes); expectedMimeTypes.sort(); qDebug() << "mimeTypes:" << mimeTypes; QCOMPARE(foundMimeTypes, expectedMimeTypes); QVERIFY(!KDb::defaultFileBasedDriverMimeType().isEmpty()); QMimeDatabase mimeDb; foreach(const QString &mimeName, expectedMimeTypes) { QVERIFY2(mimeDb.mimeTypeForName(mimeName).isValid(), qPrintable(QString("%1 MIME type not found in the MIME database").arg(mimeName))); } // find driver for the metadata KDB_VERIFY(manager.resultable(), driver = manager.driver(driverId), "Driver not found"); } void KDbTestUtils::testSqliteDriverInternal() { QStringList mimeTypes; mimeTypes << "application/x-kexiproject-sqlite3" << "application/x-sqlite3"; testDriver("org.kde.kdb.sqlite", true, // file-based mimeTypes); QVERIFY2(mimeTypes.contains(KDb::defaultFileBasedDriverMimeType()), "SQLite's MIME types should include the default file based one"); } void KDbTestUtils::testConnectInternal(const KDbConnectionData &cdata) { qDebug() << cdata; if (!driver) { //! @todo don't hardcode SQLite here KDB_VERIFY(manager.resultable(), driver = manager.driver("org.kde.kdb.sqlite"), "Driver not found"); } KDbConnectionOptions connOptions; QStringList extraSqliteExtensionPaths; extraSqliteExtensionPaths << SQLITE_LOCAL_ICU_EXTENSION_PATH; connOptions.insert("extraSqliteExtensionPaths", extraSqliteExtensionPaths); connection.reset(); // remove previous connection if present const int connCount = driver->connections().count(); connection.reset(driver->createConnection(cdata, connOptions)); KDB_VERIFY(driver, !connection.isNull(), "Failed to create connection"); QVERIFY2(cdata.driverId().isEmpty(), "Connection data has filled driver ID"); QCOMPARE(connection->data().driverId(), driver->metaData()->id()); QVERIFY2(driver->connections().contains(connection.data()), "Driver does not list created connection"); QCOMPARE(driver->connections().count(), connCount + 1); // one more const KDbUtils::Property extraSqliteExtensionPathsProperty = connection->options()->property("extraSqliteExtensionPaths"); QVERIFY2(!extraSqliteExtensionPathsProperty.isNull(), "extraSqliteExtensionPaths property not found"); QCOMPARE(extraSqliteExtensionPathsProperty.value().type(), QVariant::StringList); QCOMPARE(extraSqliteExtensionPathsProperty.value().toStringList(), extraSqliteExtensionPaths); const KDbUtils::Property readOnlyProperty = connection->options()->property("readOnly"); QVERIFY2(!readOnlyProperty.isNull(), "readOnly property not found"); QCOMPARE(readOnlyProperty.value().toBool(), connection->options()->isReadOnly()); //! @todo Add extensive test for a read-only connection KDB_VERIFY(connection, connection->connect(), "Failed to connect"); KDB_VERIFY(connection, connection->isConnected(), "Database not connected after call to connect()"); } void KDbTestUtils::testUseInternal() { KDB_VERIFY(connection, connection->databaseExists(connection->data().databaseName()), "Database does not exists"); KDB_VERIFY(connection, connection->useDatabase(), "Failed to use database"); KDB_VERIFY(connection, connection->isDatabaseUsed(), "Database not used after call to useDatabase()"); } void KDbTestUtils::testCreateDbInternal(const QString &dbName) { //open connection KDbConnectionData cdata; //! @todo don't hardcode SQLite (.kexi) extension here QString fullDbName(QDir::fromNativeSeparators(QFile::decodeName(FILES_OUTPUT_DIR "/") + dbName + ".kexi")); cdata.setDatabaseName(fullDbName); QVERIFY(testConnect(cdata)); QVERIFY(connection); //! @todo KDbDriver::metaData { QScopedPointer connGuard(connection.take()); if (connGuard->databaseExists(dbName)) { KDB_VERIFY(connGuard, connGuard->dropDatabase(fullDbName), "Failed to drop database"); } KDB_VERIFY(connGuard, !connGuard->databaseExists(fullDbName), "Database exists"); KDB_VERIFY(connGuard, connGuard->createDatabase(fullDbName), "Failed to create db"); KDB_VERIFY(connGuard, connGuard->databaseExists(fullDbName), "Database does not exists after creation"); connection.reset(connGuard.take()); } } void KDbTestUtils::testCreateDbWithTablesInternal(const QString &dbName) { QVERIFY(testCreateDb(dbName)); KDB_VERIFY(connection, connection->useDatabase(), "Failed to use database"); testCreateTablesInternal(); } void KDbTestUtils::testPropertiesInternal() { QStringList properties; properties << connection->databaseProperties().names(); QVERIFY(properties.contains("kexidb_major_ver")); bool ok; QVERIFY(connection->databaseProperties().value("kexidb_major_ver").toInt(&ok) >= 0); QVERIFY(ok); QVERIFY(properties.contains("kexidb_minor_ver")); QVERIFY(connection->databaseProperties().value("kexidb_minor_ver").toInt(&ok) >= 0); QVERIFY(ok); } void KDbTestUtils::testCreateTablesInternal() { QVERIFY2(tablesTest_createTables(connection.data()) == 0, "Failed to create test data"); } void KDbTestUtils::testDisconnectPrivate() { if (!connection) { return; } KDB_VERIFY(connection, connection->closeDatabase(), "Failed to close database"); KDB_VERIFY(connection, !connection->isDatabaseUsed(), "Database still used after closing"); KDB_VERIFY(connection, connection->closeDatabase(), "Second closeDatabase() call should not fail"); KDB_VERIFY(connection, connection->disconnect(), "Failed to disconnect database"); KDB_VERIFY(connection, !connection->isConnected(), "Database still connected after disconnecting"); KDB_VERIFY(connection, connection->disconnect(), "Second disconnect() call should not fail"); } void KDbTestUtils::testDisconnectInternal() { const int connCount = driver ? driver->connections().count() : 0; testDisconnectPrivate(); QVERIFY(!QTest::currentTestFailed()); connection.reset(); QCOMPARE(driver ? driver->connections().count() : -1, connCount - 1); // one less } void KDbTestUtils::testDropDbInternal() { QVERIFY(connection->dropDatabase(connection->data().databaseName())); } void KDbTestUtils::testDisconnectAndDropDbInternal() { QString dbName(connection.data()->data().databaseName()); testDisconnectPrivate(); QVERIFY(!QTest::currentTestFailed()); KDB_VERIFY(connection, connection->dropDatabase(dbName), "Failed to drop database"); connection.reset(); } diff --git a/autotests/tools/StaticSetOfStringsTest.cpp b/autotests/tools/StaticSetOfStringsTest.cpp index 4955a0fe..b2ac9b9e 100644 --- a/autotests/tools/StaticSetOfStringsTest.cpp +++ b/autotests/tools/StaticSetOfStringsTest.cpp @@ -1,65 +1,65 @@ /* This file is part of the KDE project Copyright (C) 2011 Adam Pigg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "StaticSetOfStringsTest.h" #include QTEST_GUILESS_MAIN(StaticSetOfStringsTest) const char* StaticSetOfStringsTest::keywords[] = { "ABORT", "ABSOLUTE", "ACCESS", "ACTION", "ADD", "AGGREGATE", "ALTER", "ANALYSE", "ANALYZE", "ANY", "ARRAY", "ASSERTION", "ASSIGNMENT", "AT", "AUTHORIZATION", "BACKWARD", "BIGINT", "BINARY", - 0 + nullptr }; void StaticSetOfStringsTest::initTestCase() { strings.setStrings(keywords); } void StaticSetOfStringsTest::testContains() { QVERIFY(strings.contains("ANY")); //test a random string QVERIFY(strings.contains(QString("backward").toUpper().toLocal8Bit())); QVERIFY(!strings.contains("BIGIN")); //test a sub-string QVERIFY(!strings.contains("XXXXXXXXXX")); //test some garbage QVERIFY(!strings.isEmpty()); QVERIFY(strings.contains("ABORT")); //test start of list QVERIFY(strings.contains("BINARY")); //test end of list } void StaticSetOfStringsTest::cleanupTestCase() { } diff --git a/src/KDb.cpp b/src/KDb.cpp index de949f94..fe850430 100644 --- a/src/KDb.cpp +++ b/src/KDb.cpp @@ -1,2226 +1,2226 @@ /* This file is part of the KDE project Copyright (C) 2004-2016 Jarosław Staniek Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDb.h" #include "KDb_p.h" #include "KDbConnectionData.h" #include "KDbConnection.h" #include "KDbCursor.h" #include "KDbDriverManager.h" #include "KDbDriverBehavior.h" #include "KDbLookupFieldSchema.h" #include "KDbMessageHandler.h" #include "KDbNativeStatementBuilder.h" #include "KDbQuerySchema.h" #include "KDbRecordData.h" #include "KDbSqlResult.h" #include "KDbTableOrQuerySchema.h" #include "KDbVersionInfo.h" #include "kdb_debug.h" #include "transliteration/transliteration_table.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDbField::Type) class ConnectionTestDialog; class ConnectionTestThread : public QThread { Q_OBJECT public: ConnectionTestThread(ConnectionTestDialog *dlg, const KDbConnectionData& connData); - virtual void run(); + void run() override; Q_SIGNALS: void error(const QString& msg, const QString& details); protected: void emitError(const KDbResultable& KDbResultable); ConnectionTestDialog* m_dlg; KDbConnectionData m_connData; KDbDriver *m_driver; private: Q_DISABLE_COPY(ConnectionTestThread) }; class ConnectionTestDialog : public QProgressDialog // krazy:exclude=qclasses { Q_OBJECT public: ConnectionTestDialog(const KDbConnectionData& data, KDbMessageHandler* msgHandler, QWidget* parent = nullptr); - virtual ~ConnectionTestDialog(); + ~ConnectionTestDialog() override; - int exec(); + int exec() override; public Q_SLOTS: void error(const QString& msg, const QString& details); protected Q_SLOTS: void slotTimeout(); - virtual void reject(); + void reject() override; protected: QPointer m_thread; KDbConnectionData m_connData; QTimer m_timer; KDbMessageHandler* m_msgHandler; int m_elapsedTime; bool m_error; QString m_msg; QString m_details; bool m_stopWaiting; private: Q_DISABLE_COPY(ConnectionTestDialog) }; ConnectionTestThread::ConnectionTestThread(ConnectionTestDialog* dlg, const KDbConnectionData& connData) : m_dlg(dlg), m_connData(connData) { connect(this, SIGNAL(error(QString,QString)), dlg, SLOT(error(QString,QString)), Qt::QueuedConnection); // try to load driver now because it's not supported in different thread KDbDriverManager manager; m_driver = manager.driver(m_connData.driverId()); if (manager.result().isError()) { emitError(*manager.resultable()); - m_driver = 0; + m_driver = nullptr; } } void ConnectionTestThread::emitError(const KDbResultable& KDbResultable) { QString msg; QString details; KDb::getHTMLErrorMesage(KDbResultable, &msg, &details); emit error(msg, details); } void ConnectionTestThread::run() { if (!m_driver) { return; } QScopedPointer conn(m_driver->createConnection(m_connData)); if (conn.isNull() || m_driver->result().isError()) { emitError(*m_driver); return; } if (!conn->connect() || conn->result().isError()) { emitError(*conn); return; } // SQL database backends like PostgreSQL require executing "USE database" // if we really want to know connection to the server succeeded. QString tmpDbName; if (!conn->useTemporaryDatabaseIfNeeded(&tmpDbName)) { emitError(*conn); return; } if (!tmpDbName.isEmpty()) { if (!conn->closeDatabase()) { emitError(*conn); } } emitError(KDbResultable()); } ConnectionTestDialog::ConnectionTestDialog(const KDbConnectionData& data, KDbMessageHandler* msgHandler, QWidget* parent) : QProgressDialog(parent) , m_thread(new ConnectionTestThread(this, data)) , m_connData(data) , m_msgHandler(msgHandler) , m_elapsedTime(0) , m_error(false) , m_stopWaiting(false) { setWindowTitle(tr("Test Connection", "Dialog's title: testing database connection")); setLabelText(tr("Testing connection to \"%1\" database server...") .arg(data.toUserVisibleString())); setModal(true); setRange(0, 0); //to show busy indicator connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); adjustSize(); resize(250, height()); } ConnectionTestDialog::~ConnectionTestDialog() { if (m_thread->isRunning()) { m_thread->terminate(); } m_thread->deleteLater(); } int ConnectionTestDialog::exec() { //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread(); m_timer.start(20); m_thread->start(); const int res = QProgressDialog::exec(); // krazy:exclude=qclasses m_thread->wait(); m_timer.stop(); return res; } void ConnectionTestDialog::slotTimeout() { //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread(); kdbDebug() << m_error; bool notResponding = false; if (m_elapsedTime >= 1000*5) {//5 seconds m_stopWaiting = true; notResponding = true; } kdbDebug() << m_elapsedTime << m_stopWaiting << notResponding; if (m_stopWaiting) { m_timer.disconnect(this); m_timer.stop(); reject(); kdbDebug() << "after reject"; QString message; QString details; KDbMessageHandler::MessageType type; if (m_error) { message = tr("Test connection to \"%1\" database server failed.") .arg(m_connData.toUserVisibleString()); details = m_msg; if (!m_details.isEmpty()) { details += QLatin1Char('\n') + m_details; } type = KDbMessageHandler::Sorry; m_error = false; } else if (notResponding) { message = tr("Test connection to \"%1\" database server failed. The server is not responding.") .arg(m_connData.toUserVisibleString()); type = KDbMessageHandler::Sorry; } else { message = tr("Test connection to \"%1\" database server established successfully.") .arg(m_connData.toUserVisibleString()), type = KDbMessageHandler::Information; } if (m_msgHandler) { m_msgHandler->showErrorMessage(type, message, details, tr("Test Connection")); } return; } m_elapsedTime += 20; setValue(m_elapsedTime); } void ConnectionTestDialog::error(const QString& msg, const QString& details) { //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread(); kdbDebug() << msg << details; m_stopWaiting = true; m_msg = msg; m_details = details; m_error = !msg.isEmpty() || !details.isEmpty(); if (m_error) { kdbDebug() << "ERR!"; } } void ConnectionTestDialog::reject() { if (m_thread->isRunning()) { m_thread->terminate(); } m_timer.disconnect(this); m_timer.stop(); QProgressDialog::reject(); // krazy:exclude=qclasses } // ---- //! @return hex digit converted to integer (0 to 15), 0xFF on failure inline static unsigned char hexDigitToInt(char digit) { if (digit >= '0' && digit <= '9') { return digit - '0'; } if (digit >= 'a' && digit <= 'f') { return digit - 'a' + 10; } if (digit >= 'A' && digit <= 'F') { return digit - 'A' + 10; } return 0xFF; } //! Converts textual representation @a data of a hex number (@a length digits) to a byte array @a array //! @return true on success and false if @a data contains characters that are not hex digits. //! true is returned for empty @a data as well. inline static bool hexToByteArrayInternal(const char* data, int length, QByteArray *array) { Q_ASSERT(length >= 0); Q_ASSERT(data || length == 0); array->resize(length / 2 + length % 2); for(int i = 0; length > 0; --length, ++data, ++i) { unsigned char d1 = hexDigitToInt(data[0]); unsigned char d2; if (i == 0 && (length % 2) == 1) { // odd number of digits; no leading 0 d2 = d1; d1 = 0; } else { --length; ++data; d2 = hexDigitToInt(data[0]); } if (d1 == 0xFF || d2 == 0xFF) { return false; } (*array)[i] = (d1 << 4) + d2; } return true; } KDbVersionInfo KDb::version() { return KDbVersionInfo( KDB_VERSION_MAJOR, KDB_VERSION_MINOR, KDB_VERSION_PATCH); } bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname, KDbField::Type keytype, const QVariant &keyval) { Q_ASSERT(conn); return conn->executeVoidSQL(KDbEscapedString("DELETE FROM %1 WHERE %2=%3") .arg(conn->escapeIdentifier(tableName)) .arg(conn->escapeIdentifier(keyname)) .arg(conn->driver()->valueToSQL(keytype, keyval))); } bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2) { Q_ASSERT(conn); return conn->executeVoidSQL(KDbEscapedString("DELETE FROM %1 WHERE %2=%3 AND %4=%5") .arg(conn->escapeIdentifier(tableName)) .arg(conn->escapeIdentifier(keyname1)) .arg(conn->driver()->valueToSQL(keytype1, keyval1)) .arg(conn->escapeIdentifier(keyname2)) .arg(conn->driver()->valueToSQL(keytype2, keyval2))); } bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2, const QString &keyname3, KDbField::Type keytype3, const QVariant& keyval3) { Q_ASSERT(conn); return conn->executeVoidSQL(KDbEscapedString("DELETE FROM %1 WHERE %2=%3 AND %4=%5 AND %6=%7") .arg(conn->escapeIdentifier(tableName)) .arg(conn->escapeIdentifier(keyname1)) .arg(conn->driver()->valueToSQL(keytype1, keyval1)) .arg(conn->escapeIdentifier(keyname2)) .arg(conn->driver()->valueToSQL(keytype2, keyval2)) .arg(conn->escapeIdentifier(keyname3)) .arg(conn->driver()->valueToSQL(keytype3, keyval3))); } bool KDb::deleteAllRecords(KDbConnection* conn, const QString &tableName) { Q_ASSERT(conn); return conn->executeVoidSQL(KDbEscapedString("DELETE FROM %1") .arg(conn->escapeIdentifier(tableName))); } KDB_EXPORT quint64 KDb::lastInsertedAutoIncValue(KDbSqlResult *result, const QString& autoIncrementFieldName, const QString& tableName, quint64* recordId) { Q_ASSERT(result); const quint64 foundRecordId = result->lastInsertRecordId(); if (recordId) { *recordId = foundRecordId; } return KDb::lastInsertedAutoIncValue(result->connection(), foundRecordId, autoIncrementFieldName, tableName); } KDB_EXPORT quint64 KDb::lastInsertedAutoIncValue(KDbConnection *conn, const quint64 recordId, const QString& autoIncrementFieldName, const QString& tableName) { const KDbDriverBehavior *behavior = KDbDriverBehavior::get(conn->driver()); if (behavior->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) { return recordId; } KDbRecordData rdata; if (recordId == std::numeric_limits::max() || true != conn->querySingleRecord( KDbEscapedString("SELECT ") + escapeIdentifier(tableName) + '.' + escapeIdentifier(autoIncrementFieldName) + " FROM " + escapeIdentifier(tableName) + " WHERE " + behavior->ROW_ID_FIELD_NAME + '=' + KDbEscapedString::number(recordId), &rdata)) { return std::numeric_limits::max(); } return rdata[0].toULongLong(); } bool KDb::isEmptyValue(KDbField::Type type, const QVariant &value) { if (KDbField::isTextType(type)) { return value.toString().isEmpty() && !value.toString().isNull(); } else if (type == KDbField::BLOB) { return value.toByteArray().isEmpty() && !value.toByteArray().isNull(); } return value.isNull(); } KDbEscapedString KDb::sqlWhere(KDbDriver *drv, KDbField::Type t, const QString& fieldName, const QVariant& value) { if (value.isNull()) return KDbEscapedString(fieldName) + " is NULL"; return KDbEscapedString(fieldName) + '=' + drv->valueToSQL(t, value); } //! Cache struct TypeCache { TypeCache() { for (KDbField::Type t = KDbField::InvalidType; t <= KDbField::LastType; t = KDbField::Type(int(t) + 1)) { const KDbField::TypeGroup tg = KDbField::typeGroup(t); QList list; QStringList name_list, str_list; if (tlist.contains(tg)) { list = tlist.value(tg); name_list = nlist.value(tg); str_list = slist.value(tg); } list += t; name_list += KDbField::typeName(t); str_list += KDbField::typeString(t); tlist[ tg ] = list; nlist[ tg ] = name_list; slist[ tg ] = str_list; } def_tlist[ KDbField::InvalidGroup ] = KDbField::InvalidType; def_tlist[ KDbField::TextGroup ] = KDbField::Text; def_tlist[ KDbField::IntegerGroup ] = KDbField::Integer; def_tlist[ KDbField::FloatGroup ] = KDbField::Double; def_tlist[ KDbField::BooleanGroup ] = KDbField::Boolean; def_tlist[ KDbField::DateTimeGroup ] = KDbField::Date; def_tlist[ KDbField::BLOBGroup ] = KDbField::BLOB; } QHash< KDbField::TypeGroup, QList > tlist; QHash< KDbField::TypeGroup, QStringList > nlist; QHash< KDbField::TypeGroup, QStringList > slist; QHash< KDbField::TypeGroup, KDbField::Type > def_tlist; }; Q_GLOBAL_STATIC(TypeCache, KDb_typeCache) const QList KDb::fieldTypesForGroup(KDbField::TypeGroup typeGroup) { return KDb_typeCache->tlist.value(typeGroup); } QStringList KDb::fieldTypeNamesForGroup(KDbField::TypeGroup typeGroup) { return KDb_typeCache->nlist.value(typeGroup); } QStringList KDb::fieldTypeStringsForGroup(KDbField::TypeGroup typeGroup) { return KDb_typeCache->slist.value(typeGroup); } KDbField::Type KDb::defaultFieldTypeForGroup(KDbField::TypeGroup typeGroup) { return (typeGroup <= KDbField::LastTypeGroup) ? KDb_typeCache->def_tlist.value(typeGroup) : KDbField::InvalidType; } void KDb::getHTMLErrorMesage(const KDbResultable& resultable, QString *msg, QString *details) { Q_ASSERT(msg); Q_ASSERT(details); const KDbResult result(resultable.result()); if (!result.isError()) return; //lower level message is added to the details, if there is alread message specified if (!result.messageTitle().isEmpty()) *msg += QLatin1String("

") + result.messageTitle(); if (msg->isEmpty()) *msg = QLatin1String("

") + result.message(); else *details += QLatin1String("

") + result.message(); if (!result.serverMessage().isEmpty()) *details += QLatin1String("

") + kdb::tr("Message from server:") + QLatin1String(" ") + result.serverMessage(); if (!result.recentSQLString().isEmpty()) *details += QLatin1String("

") + kdb::tr("SQL statement:") + QString::fromLatin1(" %1").arg(result.recentSQLString().toString()); int serverErrorCode = 0; QString serverResultName; if (result.isError()) { serverErrorCode = result.serverErrorCode(); serverResultName = resultable.serverResultName(); } if ( !details->isEmpty() && ( !result.serverMessage().isEmpty() || !result.recentSQLString().isEmpty() || !serverResultName.isEmpty() || serverErrorCode != 0) ) { *details += (QLatin1String("

") + kdb::tr("Server result code:") + QLatin1String(" ") + QString::number(serverErrorCode)); if (!serverResultName.isEmpty()) { *details += QString::fromLatin1(" (%1)").arg(serverResultName); } } else { if (!serverResultName.isEmpty()) { *details += (QLatin1String("

") + kdb::tr("Server result:") + QLatin1String(" ") + serverResultName); } } if (!details->isEmpty() && !details->startsWith(QLatin1String(""))) { if (!details->startsWith(QLatin1String("

"))) details->prepend(QLatin1String("

")); } } void KDb::getHTMLErrorMesage(const KDbResultable& resultable, QString *msg) { Q_ASSERT(msg); getHTMLErrorMesage(resultable, msg, msg); } void KDb::getHTMLErrorMesage(const KDbResultable& resultable, KDbResultInfo *info) { Q_ASSERT(info); getHTMLErrorMesage(resultable, &info->message, &info->description); } tristate KDb::idForObjectName(KDbConnection* conn, int *id, const QString& objName, int objType) { Q_ASSERT(conn); Q_ASSERT(id); return conn->querySingleNumber( KDbEscapedString("SELECT o_id FROM kexi__objects WHERE o_name=%1 AND o_type=%2") .arg(conn->escapeString(objName)).arg(objType), id); } //----------------------------------------- void KDb::connectionTestDialog(QWidget* parent, const KDbConnectionData& data, KDbMessageHandler* msgHandler) { ConnectionTestDialog dlg(data, msgHandler, parent); dlg.exec(); } int KDb::recordCount(KDbConnection* conn, const KDbEscapedString& sql) { int count = -1; //will be changed only on success of querySingleNumber() conn->querySingleNumber(KDbEscapedString("SELECT COUNT() FROM (") + sql + ") AS kdb__subquery", &count); return count; } int KDb::recordCount(const KDbTableSchema& tableSchema) { //! @todo does not work with non-SQL data sources if (!tableSchema.connection()) { kdbWarning() << "no tableSchema.connection()"; return -1; } int count = -1; //will be changed only on success of querySingleNumber() tableSchema.connection()->querySingleNumber( KDbEscapedString("SELECT COUNT(*) FROM ") + tableSchema.connection()->escapeIdentifier(tableSchema.name()), &count ); return count; } int KDb::recordCount(KDbQuerySchema* querySchema, const QList& params) { //! @todo does not work with non-SQL data sources if (!querySchema->connection()) { kdbWarning() << "no querySchema->connection()"; return -1; } int count = -1; //will be changed only on success of querySingleNumber() KDbNativeStatementBuilder builder(querySchema->connection()); KDbEscapedString subSql; if (!builder.generateSelectStatement(&subSql, querySchema, params)) { return -1; } tristate result = querySchema->connection()->querySingleNumber( KDbEscapedString("SELECT COUNT(*) FROM (") + subSql + ") AS kdb__subquery", &count ); return true == result ? count : -1; } int KDb::recordCount(KDbTableOrQuerySchema* tableOrQuery, const QList& params) { if (tableOrQuery->table()) return recordCount(*tableOrQuery->table()); if (tableOrQuery->query()) return recordCount(tableOrQuery->query(), params); return -1; } int KDb::fieldCount(KDbTableOrQuerySchema* tableOrQuery) { if (tableOrQuery->table()) return tableOrQuery->table()->fieldCount(); if (tableOrQuery->query()) return tableOrQuery->query()->fieldsExpanded().count(); return -1; } bool KDb::splitToTableAndFieldParts(const QString& string, QString *tableName, QString *fieldName, SplitToTableAndFieldPartsOptions option) { Q_ASSERT(tableName); Q_ASSERT(fieldName); const int id = string.indexOf(QLatin1Char('.')); if (option & SetFieldNameIfNoTableName && id == -1) { tableName->clear(); *fieldName = string; return !fieldName->isEmpty(); } if (id <= 0 || id == int(string.length() - 1)) return false; *tableName = string.left(id); *fieldName = string.mid(id + 1); return !tableName->isEmpty() && !fieldName->isEmpty(); } bool KDb::supportsVisibleDecimalPlacesProperty(KDbField::Type type) { //! @todo add check for decimal type as well return KDbField::isFPNumericType(type); } inline static QString numberToString(double value, int decimalPlaces, const QLocale *locale) { //! @todo round? QString result; if (decimalPlaces == 0) { result = locale ? locale->toString(qlonglong(value)) : QString::number(qlonglong(value)); } else { const int realDecimalPlaces = decimalPlaces < 0 ? 10 : decimalPlaces; result = locale ? locale->toString(value, 'f', realDecimalPlaces) : QString::number(value, 'f', realDecimalPlaces); if (decimalPlaces < 0) { // cut off zeros int i = result.length() - 1; while (i > 0 && result[i] == QLatin1Char('0')) { i--; } if (result[i].isDigit()) {// last digit ++i; } result.truncate(i); } } return result; } QString KDb::numberToString(double value, int decimalPlaces) { return ::numberToString(value, decimalPlaces, nullptr); } QString KDb::numberToLocaleString(double value, int decimalPlaces, const QLocale *locale) { if (locale) { return ::numberToString(value, decimalPlaces, locale); } QLocale defaultLocale; return ::numberToString(value, decimalPlaces, &defaultLocale); } KDbField::Type KDb::intToFieldType(int type) { if (type < int(KDbField::InvalidType) || type > int(KDbField::LastType)) { kdbWarning() << "invalid type" << type; return KDbField::InvalidType; } return static_cast(type); } KDbField::TypeGroup KDb::intToFieldTypeGroup(int typeGroup) { if (typeGroup < int(KDbField::InvalidGroup) || typeGroup > int(KDbField::LastTypeGroup)) { kdbWarning() << "invalid type group" << typeGroup; return KDbField::InvalidGroup; } return static_cast(typeGroup); } static bool setIntToFieldType(KDbField *field, const QVariant& value) { Q_ASSERT(field); bool ok; const int intType = value.toInt(&ok); if (!ok || KDbField::InvalidType == KDb::intToFieldType(intType)) {//for sanity kdbWarning() << "invalid type"; return false; } field->setType((KDbField::Type)intType); return true; } //! @internal for KDb::isBuiltinTableFieldProperty() struct KDb_BuiltinFieldProperties { KDb_BuiltinFieldProperties() { #define ADD(name) set.insert(name) ADD("type"); ADD("primaryKey"); ADD("indexed"); ADD("autoIncrement"); ADD("unique"); ADD("notNull"); ADD("allowEmpty"); ADD("unsigned"); ADD("name"); ADD("caption"); ADD("description"); ADD("maxLength"); ADD("maxLengthIsDefault"); ADD("precision"); ADD("defaultValue"); ADD("defaultWidth"); ADD("visibleDecimalPlaces"); //! @todo always update this when new builtins appear! #undef ADD } QSet set; }; //! for KDb::isBuiltinTableFieldProperty() Q_GLOBAL_STATIC(KDb_BuiltinFieldProperties, KDb_builtinFieldProperties) bool KDb::isBuiltinTableFieldProperty(const QByteArray& propertyName) { return KDb_builtinFieldProperties->set.contains(propertyName); } static QVariant visibleColumnValue(const KDbLookupFieldSchema *lookup) { if (!lookup || lookup->visibleColumns().count() == 1) { if (lookup) { const QList visibleColumns = lookup->visibleColumns(); if (!visibleColumns.isEmpty()) { return visibleColumns.first(); } } return QVariant(); } QList variantList; const QList visibleColumns(lookup->visibleColumns()); for(int column : visibleColumns) { variantList.append(column); } return variantList; } void KDb::getProperties(const KDbLookupFieldSchema *lookup, QMap *values) { Q_ASSERT(values); KDbLookupFieldSchemaRecordSource recordSource; if (lookup) { recordSource = lookup->recordSource(); } values->insert("rowSource", lookup ? recordSource.name() : QVariant()); values->insert("rowSourceType", lookup ? recordSource.typeName() : QVariant()); values->insert("rowSourceValues", (lookup && !recordSource.values().isEmpty()) ? recordSource.values() : QVariant()); values->insert("boundColumn", lookup ? lookup->boundColumn() : QVariant()); values->insert("visibleColumn", visibleColumnValue(lookup)); QList variantList; if (lookup) { const QList columnWidths = lookup->columnWidths(); for(const QVariant& variant : columnWidths) { variantList.append(variant); } } values->insert("columnWidths", lookup ? variantList : QVariant()); values->insert("showColumnHeaders", lookup ? lookup->columnHeadersVisible() : QVariant()); values->insert("listRows", lookup ? lookup->maxVisibleRecords() : QVariant()); values->insert("limitToList", lookup ? lookup->limitToList() : QVariant()); values->insert("displayWidget", lookup ? int(lookup->displayWidget()) : QVariant()); } void KDb::getFieldProperties(const KDbField &field, QMap *values) { Q_ASSERT(values); values->clear(); // normal values values->insert("type", field.type()); const KDbField::Constraints constraints = field.constraints(); values->insert("primaryKey", constraints.testFlag(KDbField::PrimaryKey)); values->insert("indexed", constraints.testFlag(KDbField::Indexed)); values->insert("autoIncrement", KDbField::isAutoIncrementAllowed(field.type()) && constraints.testFlag(KDbField::AutoInc)); values->insert("unique", constraints.testFlag(KDbField::Unique)); values->insert("notNull", constraints.testFlag(KDbField::NotNull)); values->insert("allowEmpty", !constraints.testFlag(KDbField::NotEmpty)); const KDbField::Options options = field.options(); values->insert("unsigned", options.testFlag(KDbField::Unsigned)); values->insert("name", field.name()); values->insert("caption", field.caption()); values->insert("description", field.description()); values->insert("maxLength", field.maxLength()); values->insert("maxLengthIsDefault", field.maxLengthStrategy() & KDbField::DefaultMaxLength); values->insert("precision", field.precision()); values->insert("defaultValue", field.defaultValue()); //! @todo IMPORTANT: values->insert("defaultWidth", field.defaultWidth()); if (KDb::supportsVisibleDecimalPlacesProperty(field.type())) { values->insert("visibleDecimalPlaces", field.defaultValue()); } // insert lookup-related values KDbLookupFieldSchema *lookup = field.table()->lookupFieldSchema(field); KDb::getProperties(lookup, values); } static bool containsLookupFieldSchemaProperties(const QMap& values) { for (QMap::ConstIterator it(values.constBegin()); it != values.constEnd(); ++it) { if (KDb::isLookupFieldSchemaProperty(it.key())) { return true; } } return false; } bool KDb::setFieldProperties(KDbField *field, const QMap& values) { Q_ASSERT(field); QMap::ConstIterator it; if ((it = values.find("type")) != values.constEnd()) { if (!setIntToFieldType(field, *it)) return false; } #define SET_BOOLEAN_FLAG(flag, value) { \ constraints |= KDbField::flag; \ if (!value) \ constraints ^= KDbField::flag; \ } KDbField::Constraints constraints = field->constraints(); bool ok = true; if ((it = values.find("primaryKey")) != values.constEnd()) SET_BOOLEAN_FLAG(PrimaryKey, (*it).toBool()); if ((it = values.find("indexed")) != values.constEnd()) SET_BOOLEAN_FLAG(Indexed, (*it).toBool()); if ((it = values.find("autoIncrement")) != values.constEnd() && KDbField::isAutoIncrementAllowed(field->type())) SET_BOOLEAN_FLAG(AutoInc, (*it).toBool()); if ((it = values.find("unique")) != values.constEnd()) SET_BOOLEAN_FLAG(Unique, (*it).toBool()); if ((it = values.find("notNull")) != values.constEnd()) SET_BOOLEAN_FLAG(NotNull, (*it).toBool()); if ((it = values.find("allowEmpty")) != values.constEnd()) SET_BOOLEAN_FLAG(NotEmpty, !(*it).toBool()); field->setConstraints(constraints); KDbField::Options options; if ((it = values.find("unsigned")) != values.constEnd()) { options |= KDbField::Unsigned; if (!(*it).toBool()) options ^= KDbField::Unsigned; } field->setOptions(options); if ((it = values.find("name")) != values.constEnd()) field->setName((*it).toString()); if ((it = values.find("caption")) != values.constEnd()) field->setCaption((*it).toString()); if ((it = values.find("description")) != values.constEnd()) field->setDescription((*it).toString()); if ((it = values.find("maxLength")) != values.constEnd()) field->setMaxLength((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok)); if (!ok) return false; if ((it = values.find("maxLengthIsDefault")) != values.constEnd() && (*it).toBool()) { field->setMaxLengthStrategy(KDbField::DefaultMaxLength); } if ((it = values.find("precision")) != values.constEnd()) field->setPrecision((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok)); if (!ok) return false; if ((it = values.find("defaultValue")) != values.constEnd()) field->setDefaultValue(*it); //! @todo IMPORTANT: defaultWidth #if 0 if ((it = values.find("defaultWidth")) != values.constEnd()) field.setDefaultWidth((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok)); if (!ok) return false; #endif // -- extended properties if ((it = values.find("visibleDecimalPlaces")) != values.constEnd() && KDb::supportsVisibleDecimalPlacesProperty(field->type())) field->setVisibleDecimalPlaces((*it).isNull() ? -1/*default*/ : (*it).toInt(&ok)); if (!ok) return false; if (field->table() && containsLookupFieldSchemaProperties(values)) { KDbLookupFieldSchema *lookup = field->table()->lookupFieldSchema(*field); QScopedPointer createdLookup; if (!lookup) { // create lookup if needed createdLookup.reset(lookup = new KDbLookupFieldSchema()); } if (lookup->setProperties(values)) { if (createdLookup) { if (field->table()->setLookupFieldSchema(field->name(), lookup)) { createdLookup.take(); // ownership passed - lookup = 0; + lookup = nullptr; } } } } return true; #undef SET_BOOLEAN_FLAG } //! @internal for isExtendedTableProperty() struct KDb_ExtendedProperties { KDb_ExtendedProperties() { #define ADD(name) set.insert( name ) ADD("visibledecimalplaces"); ADD("rowsource"); ADD("rowsourcetype"); ADD("rowsourcevalues"); ADD("boundcolumn"); ADD("visiblecolumn"); ADD("columnwidths"); ADD("showcolumnheaders"); ADD("listrows"); ADD("limittolist"); ADD("displaywidget"); #undef ADD } QSet set; }; //! for isExtendedTableProperty() Q_GLOBAL_STATIC(KDb_ExtendedProperties, KDb_extendedProperties) bool KDb::isExtendedTableFieldProperty(const QByteArray& propertyName) { return KDb_extendedProperties->set.contains(QByteArray(propertyName).toLower()); } //! @internal for isLookupFieldSchemaProperty() struct KDb_LookupFieldSchemaProperties { KDb_LookupFieldSchemaProperties() { QMap tmp; - KDb::getProperties(0, &tmp); + KDb::getProperties(nullptr, &tmp); for (QMap::ConstIterator it(tmp.constBegin()); it != tmp.constEnd(); ++it) { set.insert(it.key().toLower()); } } QSet set; }; //! for isLookupFieldSchemaProperty() Q_GLOBAL_STATIC(KDb_LookupFieldSchemaProperties, KDb_lookupFieldSchemaProperties) bool KDb::isLookupFieldSchemaProperty(const QByteArray& propertyName) { return KDb_lookupFieldSchemaProperties->set.contains(propertyName.toLower()); } bool KDb::setFieldProperty(KDbField *field, const QByteArray& propertyName, const QVariant& value) { Q_ASSERT(field); #define SET_BOOLEAN_FLAG(flag, value) { \ constraints |= KDbField::flag; \ if (!value) \ constraints ^= KDbField::flag; \ field->setConstraints( constraints ); \ return true; \ } #define GET_INT(method) { \ const int ival = value.toInt(&ok); \ if (!ok) \ return false; \ field->method( ival ); \ return true; \ } if (propertyName.isEmpty()) return false; bool ok; if (KDb::isExtendedTableFieldProperty(propertyName)) { //a little speedup: identify extended property in O(1) if ("visibleDecimalPlaces" == propertyName && KDb::supportsVisibleDecimalPlacesProperty(field->type())) { GET_INT(setVisibleDecimalPlaces); } else if (KDb::isLookupFieldSchemaProperty(propertyName)) { if (!field->table()) { kdbWarning() << "Could not set" << propertyName << "property - no table assigned for field"; } else { KDbLookupFieldSchema *lookup = field->table()->lookupFieldSchema(*field); const bool createLookup = !lookup; if (createLookup) // create lookup if needed lookup = new KDbLookupFieldSchema(); if (lookup->setProperty(propertyName, value)) { if (createLookup) field->table()->setLookupFieldSchema(field->name(), lookup); return true; } if (createLookup) delete lookup; // not set, delete } } } else {//non-extended if ("type" == propertyName) return setIntToFieldType(field, value); KDbField::Constraints constraints = field->constraints(); if ("primaryKey" == propertyName) SET_BOOLEAN_FLAG(PrimaryKey, value.toBool()); if ("indexed" == propertyName) SET_BOOLEAN_FLAG(Indexed, value.toBool()); if ("autoIncrement" == propertyName && KDbField::isAutoIncrementAllowed(field->type())) SET_BOOLEAN_FLAG(AutoInc, value.toBool()); if ("unique" == propertyName) SET_BOOLEAN_FLAG(Unique, value.toBool()); if ("notNull" == propertyName) SET_BOOLEAN_FLAG(NotNull, value.toBool()); if ("allowEmpty" == propertyName) SET_BOOLEAN_FLAG(NotEmpty, !value.toBool()); KDbField::Options options; if ("unsigned" == propertyName) { options |= KDbField::Unsigned; if (!value.toBool()) options ^= KDbField::Unsigned; field->setOptions(options); return true; } if ("name" == propertyName) { if (value.toString().isEmpty()) return false; field->setName(value.toString()); return true; } if ("caption" == propertyName) { field->setCaption(value.toString()); return true; } if ("description" == propertyName) { field->setDescription(value.toString()); return true; } if ("maxLength" == propertyName) GET_INT(setMaxLength); if ("maxLengthIsDefault" == propertyName) { field->setMaxLengthStrategy(KDbField::DefaultMaxLength); } if ("precision" == propertyName) GET_INT(setPrecision); if ("defaultValue" == propertyName) { field->setDefaultValue(value); return true; } //! @todo IMPORTANT: defaultWidth #if 0 if ("defaultWidth" == propertyName) GET_INT(setDefaultWidth); #endif // last chance that never fails: custom field property field->setCustomProperty(propertyName, value); } kdbWarning() << "property" << propertyName << "not found!"; return false; #undef SET_BOOLEAN_FLAG #undef GET_INT } int KDb::loadIntPropertyValueFromDom(const QDomNode& node, bool* ok) { QByteArray valueType = node.nodeName().toLatin1(); if (valueType.isEmpty() || valueType != "number") { if (ok) *ok = false; return 0; } const QString text(QDomNode(node).toElement().text()); int val = text.toInt(ok); return val; } QString KDb::loadStringPropertyValueFromDom(const QDomNode& node, bool* ok) { QByteArray valueType = node.nodeName().toLatin1(); if (valueType != "string") { if (ok) *ok = false; return QString(); } if (ok) *ok = true; return QDomNode(node).toElement().text(); } QVariant KDb::loadPropertyValueFromDom(const QDomNode& node, bool* ok) { QByteArray valueType = node.nodeName().toLatin1(); if (valueType.isEmpty()) { if (ok) *ok = false; return QVariant(); } if (ok) *ok = true; const QString text(QDomNode(node).toElement().text()); bool _ok; if (valueType == "string") { return text; } else if (valueType == "cstring") { return text.toLatin1(); } else if (valueType == "number") { // integer or double if (text.indexOf(QLatin1Char('.')) != -1) { double val = text.toDouble(&_ok); if (_ok) return val; } else { const int val = text.toInt(&_ok); if (_ok) return val; const qint64 valLong = text.toLongLong(&_ok); if (_ok) return valLong; } } else if (valueType == "bool") { return text.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0 || text == QLatin1String("1"); } else { //! @todo add more QVariant types kdbWarning() << "KDb::loadPropertyValueFromDom(): unknown type '" << valueType << "'"; } if (ok) *ok = false; return QVariant(); } QDomElement KDb::saveNumberElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, int value) { Q_ASSERT(doc); Q_ASSERT(parentEl); QDomElement el(doc->createElement(elementName)); parentEl->appendChild(el); QDomElement numberEl(doc->createElement(QLatin1String("number"))); el.appendChild(numberEl); numberEl.appendChild(doc->createTextNode(QString::number(value))); return el; } QDomElement KDb::saveBooleanElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, bool value) { Q_ASSERT(doc); Q_ASSERT(parentEl); QDomElement el(doc->createElement(elementName)); parentEl->appendChild(el); QDomElement numberEl(doc->createElement(QLatin1String("bool"))); el.appendChild(numberEl); numberEl.appendChild(doc->createTextNode( value ? QLatin1String("true") : QLatin1String("false"))); return el; } //! @internal Used in KDb::emptyValueForFieldType() struct KDb_EmptyValueForFieldTypeCache { KDb_EmptyValueForFieldTypeCache() : values(int(KDbField::LastType) + 1) { #define ADD(t, value) values.insert(t, value); ADD(KDbField::Byte, 0); ADD(KDbField::ShortInteger, 0); ADD(KDbField::Integer, 0); ADD(KDbField::BigInteger, 0); ADD(KDbField::Boolean, false); ADD(KDbField::Float, 0.0); ADD(KDbField::Double, 0.0); //! @todo ok? we have no better defaults ADD(KDbField::Text, QLatin1String(" ")); ADD(KDbField::LongText, QLatin1String(" ")); ADD(KDbField::BLOB, QByteArray()); #undef ADD } QVector values; }; //! Used in KDb::emptyValueForFieldType() Q_GLOBAL_STATIC(KDb_EmptyValueForFieldTypeCache, KDb_emptyValueForFieldTypeCache) QVariant KDb::emptyValueForFieldType(KDbField::Type type) { const QVariant val(KDb_emptyValueForFieldTypeCache->values.at( (type <= KDbField::LastType) ? type : KDbField::InvalidType)); if (!val.isNull()) return val; else { //special cases if (type == KDbField::Date) return QDate::currentDate(); if (type == KDbField::DateTime) return QDateTime::currentDateTime(); if (type == KDbField::Time) return QTime::currentTime(); } kdbWarning() << "no value for type" << KDbField::typeName(type); return QVariant(); } //! @internal Used in KDb::notEmptyValueForFieldType() struct KDb_NotEmptyValueForFieldTypeCache { KDb_NotEmptyValueForFieldTypeCache() : values(int(KDbField::LastType) + 1) { #define ADD(t, value) values.insert(t, value); // copy most of the values for (int i = int(KDbField::InvalidType) + 1; i <= KDbField::LastType; i++) { if (i == KDbField::Date || i == KDbField::DateTime || i == KDbField::Time) continue; //'current' value will be returned if (i == KDbField::Text || i == KDbField::LongText) { ADD(i, QVariant(QLatin1String(""))); continue; } if (i == KDbField::BLOB) { //! @todo blobs will contain other MIME types too QByteArray ba; //! @todo port to Qt4 #if 0 QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); QPixmap pm(SmallIcon("document-new")); pm.save(&buffer, "PNG"/*! @todo default? */); #endif ADD(i, ba); continue; } ADD(i, KDb::emptyValueForFieldType((KDbField::Type)i)); } #undef ADD } QVector values; }; //! Used in KDb::notEmptyValueForFieldType() Q_GLOBAL_STATIC(KDb_NotEmptyValueForFieldTypeCache, KDb_notEmptyValueForFieldTypeCache) QVariant KDb::notEmptyValueForFieldType(KDbField::Type type) { const QVariant val(KDb_notEmptyValueForFieldTypeCache->values.at( (type <= KDbField::LastType) ? type : KDbField::InvalidType)); if (!val.isNull()) return val; else { //special cases if (type == KDbField::Date) return QDate::currentDate(); if (type == KDbField::DateTime) return QDateTime::currentDateTime(); if (type == KDbField::Time) return QTime::currentTime(); } kdbWarning() << "no value for type" << KDbField::typeName(type); return QVariant(); } //! @internal @return nestimated new length after escaping of string @a string template inline static int estimatedNewLength(const T &string, bool addQuotes) { if (string.length() < 10) return string.length() * 2 + (addQuotes ? 2 : 0); return string.length() * 3 / 2; } //! @internal @return @a string string with applied KDbSQL identifier escaping. //! If @a addQuotes is true, '"' characer is prepended and appended. template inline static T escapeIdentifier(const T& string, bool addQuotes) { const Latin1CharType quote('"'); // create Latin1StringType escapedQuote("\"\""); T newString; newString.reserve(estimatedNewLength(string, addQuotes)); if (addQuotes) { newString.append(quote); } for (int i = 0; i < string.length(); i++) { const CharType c = string.at(i); if (c == quote) newString.append(escapedQuote); else newString.append(c); } if (addQuotes) { newString.append(quote); } newString.squeeze(); return newString; } QString KDb::escapeIdentifier(const QString& string) { return ::escapeIdentifier(string, false); } QByteArray KDb::escapeIdentifier(const QByteArray& string) { return ::escapeIdentifier(string, false); } QString KDb::escapeIdentifierAndAddQuotes(const QString& string) { return ::escapeIdentifier(string, true); } QByteArray KDb::escapeIdentifierAndAddQuotes(const QByteArray& string) { return ::escapeIdentifier(string, true); } QString KDb::escapeString(const QString& string) { const QLatin1Char quote('\''); // find out the length ot the destination string // create QString newString(quote); newString.reserve(estimatedNewLength(string, true)); for (int i = 0; i < string.length(); i++) { const QChar c = string.at(i); const ushort unicode = c.unicode(); if (unicode == quote) newString.append(QLatin1String("''")); else if (unicode == '\t') newString.append(QLatin1String("\\t")); else if (unicode == '\\') newString.append(QLatin1String("\\\\")); else if (unicode == '\n') newString.append(QLatin1String("\\n")); else if (unicode == '\r') newString.append(QLatin1String("\\r")); else if (unicode == '\0') newString.append(QLatin1String("\\0")); else newString.append(c); } newString.append(QLatin1Char(quote)); return newString; } //! @see handleHex() const int CODE_POINT_DIGITS = std::numeric_limits::max(); //! @see handleHex() const int MAX_CODE_POINT_VALUE = 0x10FFFF; //! @internal Decodes hex of length @a digits for handleXhh(), handleUxxxx() and handleUcodePoint() //! If @a digits is CODE_POINT_DIGITS, any number of hex digits is decoded until '}' character //! is found (error if not found), and the function succeeds when the resulting number //! is not larger than MAX_CODE_POINT_VALUE. //! If @a digits is smaller than CODE_POINT_DIGITS the function succeeds only if exactly @a digits //! number of digits has been found. //! @return -1 on error (when invalid character found or on missing character //! or if the resulting number is too large) //! @see KDb::unescapeString() static int handleHex(QString *result, int *from, int stringLen, int *errorPosition, int digits) { int digit = 0; for (int i=0; i= stringLen) { // unfinished if (errorPosition) { *errorPosition = *from; } return -1; } ++(*from); if (digits == CODE_POINT_DIGITS && (*result)[*from] == QLatin1Char('}')) { // special case: code point character decoded if (i == 0) { if (errorPosition) { *errorPosition = *from; } return -1; } return digit; } const unsigned char d = hexDigitToInt((*result)[*from].toLatin1()); if (d == 0xFF) { // unfinished or wrong character if (errorPosition) { *errorPosition = *from; } return -1; } digit = (digit << 4) + d; if (digits == CODE_POINT_DIGITS) { if (digit > MAX_CODE_POINT_VALUE) { // special case: exceeded limit of code point if (errorPosition) { *errorPosition = *from; } return -1; } } } return digit; } //! @internal Handles \xhh format for handleEscape() //! Assumption: the @a *from points to "x" in the "\x" //! @see KDb::unescapeString() static bool handleXhh(QString *result, int *from, int to, int stringLen, int *errorPosition) { const int intDigit = handleHex(result, from, stringLen, errorPosition, 2); if (intDigit == -1) { return false; } (*result)[to] = QChar(static_cast(intDigit), 0); return true; } //! @internal Handles \uxxxx format for handleEscape() //! Assumption: the @a *from points to the "u" in the "\u". //! @see KDb::unescapeString() static bool handleUxxxx(QString *result, int *from, int to, int stringLen, int *errorPosition) { const int intDigit = handleHex(result, from, stringLen, errorPosition, 4); if (intDigit == -1) { return false; } (*result)[to] = QChar(static_cast(intDigit)); return true; } //! @internal Handles \u{xxxxxx} format for handleEscape() //! Assumption: the @a *from points to the "{" in the "\u{". //! @see KDb::unescapeString() static bool handleUcodePoint(QString *result, int *from, int to, int stringLen, int *errorPosition) { const int intDigit = handleHex(result, from, stringLen, errorPosition, CODE_POINT_DIGITS); if (intDigit == -1) { return false; } (*result)[to] = QChar(intDigit); return true; } //! @internal Handles escaped character @a c2 for KDb::unescapeString() //! Updates @a result //! @return true on success static bool handleEscape(QString *result, int *from, int *to, int stringLen, int *errorPosition) { const QCharRef c2 = (*result)[*from]; if (c2 == QLatin1Char('x')) { // \xhh if (!handleXhh(result, from, *to, stringLen, errorPosition)) { return false; } } else if (c2 == QLatin1Char('u')) { // \u if ((*from + 1) >= stringLen) { // unfinished if (errorPosition) { *errorPosition = *from; } return false; } ++(*from); const QCharRef c3 = (*result)[*from]; if (c3 == QLatin1Char('{')) { // \u{ if (!handleUcodePoint(result, from, *to, stringLen, errorPosition)) { return false; } } else { --(*from); if (!handleUxxxx(result, from, *to, stringLen, errorPosition)) { return false; } } #define _RULE(in, out) \ } else if (c2 == QLatin1Char(in)) { \ (*result)[*to] = QLatin1Char(out); _RULE('0', '\0') _RULE('b', '\b') _RULE('f', '\f') _RULE('n', '\n') _RULE('r', '\r') _RULE('t', '\t') _RULE('v', '\v') #undef _RULE } else { // \ ' " ? % _ and any other without special meaning can be escaped: just skip "\" (*result)[*to] = c2; } return true; } QString KDb::unescapeString(const QString& string, char quote, int *errorPosition) { if (quote != '\'' && quote != '\"') { if (errorPosition) { *errorPosition = 0; } return QString(); } const QLatin1Char quoteChar(quote); if (string.isEmpty() || (!string.contains(QLatin1Char('\\')) && !string.contains(quoteChar))) { if (errorPosition) { *errorPosition = -1; } return string; // optimization: there are no escapes and quotes } QString result(string); const int stringLen = string.length(); int from = 0; int to = 0; bool doubleQuoteExpected = false; while (from < stringLen) { const QCharRef c = result[from]; if (doubleQuoteExpected) { if (c == quoteChar) { result[to] = c; doubleQuoteExpected = false; } else { // error: missing second quote if (errorPosition) { *errorPosition = from - 1; // -1 because error is at prev. char } return QString(); } } else if (c == quoteChar) { doubleQuoteExpected = true; ++from; continue; } else if (c == QLatin1Char('\\')) { // escaping if ((from + 1) >= stringLen) { // ignore unfinished '\' break; } ++from; if (!handleEscape(&result, &from, &to, stringLen, errorPosition)) { return QString(); } } else { // normal character: skip result[to] = result[from]; } ++from; ++to; } if (doubleQuoteExpected) { // error: string ends with a single quote if (errorPosition) { *errorPosition = from - 1; } return QString(); } if (errorPosition) { *errorPosition = -1; } result.truncate(to); return result; } //! @return hex digit '0'..'F' for integer number 0..15 inline static char intToHexDigit(unsigned char val) { return (val < 10) ? ('0' + val) : ('A' + (val - 10)); } QString KDb::escapeBLOB(const QByteArray& array, BLOBEscapingType type) { const int size = array.size(); if (size == 0 && type == BLOBEscape0xHex) return QString(); int escaped_length = size * 2; if (type == BLOBEscape0xHex || type == BLOBEscapeOctal) escaped_length += 2/*0x or X'*/; else if (type == BLOBEscapeXHex) escaped_length += 3; //X' + ' else if (type == BLOBEscapeByteaHex) escaped_length += (4 + 8); // E'\x + '::bytea QString str; str.reserve(escaped_length); if (str.capacity() < escaped_length) { kdbWarning() << "no enough memory (cannot allocate" << escaped_length << "chars)"; return QString(); } if (type == BLOBEscapeXHex) str = QString::fromLatin1("X'"); else if (type == BLOBEscape0xHex) str = QString::fromLatin1("0x"); else if (type == BLOBEscapeOctal) str = QString::fromLatin1("'"); else if (type == BLOBEscapeByteaHex) str = QString::fromLatin1("E'\\\\x"); int new_length = str.length(); //after X' or 0x, etc. if (type == BLOBEscapeOctal) { // only escape nonprintable characters as in Table 8-7: // http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html // i.e. escape for bytes: < 32, >= 127, 39 ('), 92(\). for (int i = 0; i < size; i++) { const unsigned char val = array[i]; if (val < 32 || val >= 127 || val == 39 || val == 92) { str[new_length++] = '\\'; str[new_length++] = '\\'; str[new_length++] = '0' + val / 64; str[new_length++] = '0' + (val % 64) / 8; str[new_length++] = '0' + val % 8; } else { str[new_length++] = val; } } } else { for (int i = 0; i < size; i++) { const unsigned char val = array[i]; str[new_length++] = intToHexDigit(val / 16); str[new_length++] = intToHexDigit(val % 16); } } if (type == BLOBEscapeXHex || type == BLOBEscapeOctal) { str[new_length++] = '\''; } else if (type == BLOBEscapeByteaHex) { str[new_length++] = '\''; str[new_length++] = ':'; str[new_length++] = ':'; str[new_length++] = 'b'; str[new_length++] = 'y'; str[new_length++] = 't'; str[new_length++] = 'e'; str[new_length++] = 'a'; } return str; } QByteArray KDb::pgsqlByteaToByteArray(const char* data, int length) { if (!data) { return QByteArray(); } QByteArray array; int output = 0; if (length < 0) { length = qstrlen(data); } for (int pass = 0; pass < 2; pass++) {//2 passes to avoid allocating buffer twice: // 0: count #of chars; 1: copy data const char* s = data; const char* end = s + length; if (pass == 1) { //kdbDebug() << "processBinaryData(): real size == " << output; array.resize(output); output = 0; } for (int input = 0; s < end; output++) { // kdbDebug()<<(int)s[0]<<" "<<(int)s[1]<<" "<<(int)s[2]<<" "<<(int)s[3]<<" "<<(int)s[4]; if (s[0] == '\\' && (s + 1) < end) { //special cases as in http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html if (s[1] == '\'') {// \' if (pass == 1) array[output] = '\''; s += 2; } else if (s[1] == '\\') { // 2 backslashes if (pass == 1) array[output] = '\\'; s += 2; } else if ((input + 3) < length) {// \\xyz where xyz are 3 octal digits if (pass == 1) array[output] = char((int(s[1] - '0') * 8 + int(s[2] - '0')) * 8 + int(s[3] - '0')); s += 4; } else { kdbWarning() << "no octal value after backslash"; s++; } } else { if (pass == 1) array[output] = s[0]; s++; } // kdbDebug()< KDb::stringListToIntList(const QStringList &list, bool *ok) { QList result; foreach (const QString &item, list) { int val = item.toInt(ok); if (ok && !*ok) { return QList(); } result.append(val); } if (ok) { *ok = true; } return result; } // Based on KConfigGroupPrivate::serializeList() from kconfiggroup.cpp (kdelibs 4) QString KDb::serializeList(const QStringList &list) { QString value; if (!list.isEmpty()) { QStringList::ConstIterator it = list.constBegin(); const QStringList::ConstIterator end = list.constEnd(); value = QString(*it).replace(QLatin1Char('\\'), QLatin1String("\\\\")) .replace(QLatin1Char(','), QLatin1String("\\,")); while (++it != end) { // In the loop, so it is not done when there is only one element. // Doing it repeatedly is a pretty cheap operation. value.reserve(4096); value += QLatin1Char(',') + QString(*it).replace(QLatin1Char('\\'), QLatin1String("\\\\")) .replace(QLatin1Char(','), QLatin1String("\\,")); } // To be able to distinguish an empty list from a list with one empty element. if (value.isEmpty()) value = QLatin1String("\\0"); } return value; } // Based on KConfigGroupPrivate::deserializeList() from kconfiggroup.cpp (kdelibs 4) QStringList KDb::deserializeList(const QString &data) { if (data.isEmpty()) return QStringList(); if (data == QLatin1String("\\0")) return QStringList(QString()); QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == QLatin1Char('\\')) { quoted = true; } else if (data[p].unicode() == QLatin1Char(',')) { val.squeeze(); // release any unused memory value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } QList KDb::deserializeIntList(const QString &data, bool *ok) { return KDb::stringListToIntList( KDb::deserializeList(data), ok); } QString KDb::variantToString(const QVariant& v) { if (v.type() == QVariant::ByteArray) { return KDb::escapeBLOB(v.toByteArray(), KDb::BLOBEscapeHex); } else if (v.type() == QVariant::StringList) { return serializeList(v.toStringList()); } return v.toString(); } QVariant KDb::stringToVariant(const QString& s, QVariant::Type type, bool* ok) { if (s.isNull()) { if (ok) *ok = true; return QVariant(); } switch (type) { case QVariant::Invalid: if (ok) *ok = false; return QVariant(); case QVariant::ByteArray: {//special case: hex string const int len = s.length(); QByteArray ba; ba.resize(len / 2 + len % 2); for (int i = 0; i < (len - 1); i += 2) { bool _ok; int c = s.midRef(i, 2).toInt(&_ok, 16); if (!_ok) { if (ok) *ok = _ok; kdbWarning() << "Error in digit" << i; return QVariant(); } ba[i/2] = (char)c; } if (ok) *ok = true; return ba; } case QVariant::StringList: *ok = true; return KDb::deserializeList(s); default:; } QVariant result(s); if (!result.convert(type)) { if (ok) *ok = false; return QVariant(); } if (ok) *ok = true; return result; } bool KDb::isDefaultValueAllowed(const KDbField &field) { return !field.isUniqueKey(); } void KDb::getLimitsForFieldType(KDbField::Type type, qlonglong *minValue, qlonglong *maxValue, Signedness signedness) { Q_ASSERT(minValue); Q_ASSERT(maxValue); switch (type) { case KDbField::Byte: //! @todo always ok? *minValue = signedness == KDb::Signed ? -0x80 : 0; *maxValue = signedness == KDb::Signed ? 0x7F : 0xFF; break; case KDbField::ShortInteger: *minValue = signedness == KDb::Signed ? -0x8000 : 0; *maxValue = signedness == KDb::Signed ? 0x7FFF : 0xFFFF; break; case KDbField::Integer: case KDbField::BigInteger: //!< @todo cannot return anything larger? default: *minValue = signedness == KDb::Signed ? qlonglong(-0x07FFFFFFF) : qlonglong(0); *maxValue = signedness == KDb::Signed ? qlonglong(0x07FFFFFFF) : qlonglong(0x0FFFFFFFF); } } KDbField::Type KDb::maximumForIntegerFieldTypes(KDbField::Type t1, KDbField::Type t2) { if (!KDbField::isIntegerType(t1) || !KDbField::isIntegerType(t2)) return KDbField::InvalidType; if (t1 == t2) return t2; if (t1 == KDbField::ShortInteger && t2 != KDbField::Integer && t2 != KDbField::BigInteger) return t1; if (t1 == KDbField::Integer && t2 != KDbField::BigInteger) return t1; if (t1 == KDbField::BigInteger) return t1; return KDb::maximumForIntegerFieldTypes(t2, t1); //swap } QString KDb::simplifiedFieldTypeName(KDbField::Type type) { if (KDbField::isNumericType(type)) return KDbField::tr("Number"); //simplify else if (type == KDbField::BLOB) //! @todo support names of other BLOB subtypes return KDbField::tr("Image"); //simplify return KDbField::typeGroupName(KDbField::typeGroup(type)); } QString KDb::defaultFileBasedDriverMimeType() { return QLatin1String("application/x-kexiproject-sqlite3"); } QString KDb::defaultFileBasedDriverId() { return QLatin1String("org.kde.kdb.sqlite"); } // Try to convert from string to type T template QVariant convert(T (QString::*ConvertToT)(bool*,int) const, const char *data, int size, qlonglong minValue, qlonglong maxValue, bool *ok) { T v = (QString::fromLatin1(data, size).*ConvertToT)(ok, 10); if (*ok) { *ok = minValue <= v && v <= maxValue; } return KDb::iif(*ok, QVariant(v)); } QVariant KDb::cstringToVariant(const char* data, KDbField::Type type, bool *ok, int length, KDb::Signedness signedness) { bool tempOk; bool *thisOk = ok ? ok : &tempOk; if (type < KDbField::Byte || type > KDbField::LastType) { *thisOk = false; return QVariant(); } if (!data) { // NULL value *thisOk = true; return QVariant(); } // from most to least frequently used types: if (KDbField::isTextType(type)) { *thisOk = true; //! @todo use KDbDriverBehavior::TEXT_TYPE_MAX_LENGTH for Text type? return QString::fromUtf8(data, length); } if (KDbField::isIntegerType(type)) { qlonglong minValue, maxValue; const bool isUnsigned = signedness == KDb::Unsigned; KDb::getLimitsForFieldType(type, &minValue, &maxValue, signedness); switch (type) { case KDbField::Byte: // Byte here too, minValue/maxValue will take care of limits case KDbField::ShortInteger: return isUnsigned ? convert(&QString::toUShort, data, length, minValue, maxValue, thisOk) : convert(&QString::toShort, data, length, minValue, maxValue, thisOk); case KDbField::Integer: return isUnsigned ? convert(&QString::toUInt, data, length, minValue, maxValue, thisOk) : convert(&QString::toInt, data, length, minValue, maxValue, thisOk); case KDbField::BigInteger: return convert(&QString::toLongLong, data, length, minValue, maxValue, thisOk); default: qFatal("Unsupported integer type %d", type); } } if (KDbField::isFPNumericType(type)) { const QVariant result(QString::fromLatin1(data, length).toDouble(thisOk)); return KDb::iif(*thisOk, result); } if (type == KDbField::BLOB) { *thisOk = length >= 0; return *thisOk ? QVariant(QByteArray(data, length)) : QVariant(); } // the default //! @todo date/time? QVariant result(QString::fromUtf8(data, length)); if (!result.convert(KDbField::variantType(type))) { *thisOk = false; return QVariant(); } *thisOk = true; return result; } QStringList KDb::libraryPaths() { QStringList result; foreach (const QString& path, qApp->libraryPaths()) { const QString dir(path + QLatin1Char('/') + QLatin1String(KDB_BASE_NAME_LOWER)); if (QDir(dir).exists() && QDir(dir).isReadable()) { result += dir; } } return result; } QString KDb::temporaryTableName(KDbConnection *conn, const QString &baseName) { Q_ASSERT(conn); if (!conn) { return QString(); } while (true) { QString name = QLatin1String("tmp__") + baseName; for (int i = 0; i < 10; ++i) { name += QString::number(int(double(qrand()) / RAND_MAX * 0x10), 16); } const tristate res = conn->containsTable(name); if (~res) { return QString(); } else if (res == false) { return name; } } } QString KDb::sqlite3ProgramPath() { QString path = KDbUtils::findExe(QLatin1String("sqlite3")); if (path.isEmpty()) { kdbWarning() << "Could not find program \"sqlite3\""; } return path; } bool KDb::importSqliteFile(const QString &inputFileName, const QString &outputFileName) { const QString sqlite_app = KDb::sqlite3ProgramPath(); if (sqlite_app.isEmpty()) { return false; } QFileInfo fi(inputFileName); if (!fi.isReadable()) { kdbWarning() << "No readable input file" << fi.absoluteFilePath(); return false; } QFileInfo fo(outputFileName); if (QFile(fo.absoluteFilePath()).exists()) { if (!QFile::remove(fo.absoluteFilePath())) { kdbWarning() << "Could not remove output file" << fo.absoluteFilePath(); return false; } } kdbDebug() << inputFileName << fi.absoluteDir().path() << fo.absoluteFilePath(); QProcess p; p.start(sqlite_app, QStringList() << fo.absoluteFilePath()); if (!p.waitForStarted()) { kdbWarning() << "Failed to start program" << sqlite_app; return false; } QByteArray line(".read " + QFile::encodeName(fi.absoluteFilePath())); if (p.write(line) != line.length() || !p.waitForBytesWritten()) { kdbWarning() << "Failed to send \".read\" command to program" << sqlite_app; return false; } p.closeWriteChannel(); if (!p.waitForFinished()) { kdbWarning() << "Failed to finish program" << sqlite_app; return false; } return true; } //--------- bool KDb::isIdentifier(const QString& s) { int i; const int sLength = s.length(); for (i = 0; i < sLength; i++) { const char c = s.at(i).toLower().toLatin1(); if (c == 0 || !(c == '_' || (c >= 'a' && c <= 'z') || (i > 0 && c >= '0' && c <= '9'))) break; } return i > 0 && i == sLength; } bool KDb::isIdentifier(const QByteArray& s) { int i; const int sLength = s.length(); for (i = 0; i < sLength; i++) { const char c = s.at(i); if (c == 0 || !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9'))) { break; } } return i > 0 && i == sLength; } static inline QString charToIdentifier(const QChar& c) { if (c.unicode() >= TRANSLITERATION_TABLE_SIZE) return QLatin1String("_"); const char *const s = transliteration_table[c.unicode()]; return s ? QString::fromLatin1(s) : QLatin1String("_"); } QString KDb::stringToIdentifier(const QString &s) { if (s.isEmpty()) return QString(); QString r, id = s.simplified(); if (id.isEmpty()) return QString(); r.reserve(id.length()); id.replace(QLatin1Char(' '), QLatin1String("_")); const QChar c = id[0]; const char ch = c.toLatin1(); QString add; bool wasUnderscore = false; if (ch >= '0' && ch <= '9') { r += QLatin1Char('_') + c; } else { add = charToIdentifier(c); r += add; wasUnderscore = add == QLatin1String("_"); } const int idLength = id.length(); for (int i = 1; i < idLength; i++) { add = charToIdentifier(id.at(i)); if (wasUnderscore && add == QLatin1String("_")) continue; wasUnderscore = add == QLatin1String("_"); r += add; } return r; } QString KDb::identifierExpectedMessage(const QString &valueName, const QVariant& v) { return QLatin1String("

") + kdb::tr("Value of \"%1\" field must be an identifier.") .arg(valueName) + QLatin1String("

") + kdb::tr("\"%1\" is not a valid identifier.").arg(v.toString()) + QLatin1String("

"); } //-------------------------------------------------------------------------------- #ifdef KDB_DEBUG_GUI static KDb::DebugGUIHandler s_debugGUIHandler = 0; void KDb::setDebugGUIHandler(KDb::DebugGUIHandler handler) { s_debugGUIHandler = handler; } void KDb::debugGUI(const QString& text) { if (s_debugGUIHandler) s_debugGUIHandler(text); } static KDb::AlterTableActionDebugGUIHandler s_alterTableActionDebugHandler = 0; void KDb::setAlterTableActionDebugHandler(KDb::AlterTableActionDebugGUIHandler handler) { s_alterTableActionDebugHandler = handler; } void KDb::alterTableActionDebugGUI(const QString& text, int nestingLevel) { if (s_alterTableActionDebugHandler) s_alterTableActionDebugHandler(text, nestingLevel); } #endif // KDB_DEBUG_GUI #include "KDb.moc" diff --git a/src/KDb.h b/src/KDb.h index 691d004c..a2de97f2 100644 --- a/src/KDb.h +++ b/src/KDb.h @@ -1,732 +1,732 @@ /* This file is part of the KDE project Copyright (C) 2004-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_H #define KDB_H #include #include #include #include "KDbGlobal.h" #include "KDbField.h" #include "KDbTableSchema.h" class QDomNode; class QDomElement; class QDomDocument; class KDbConnection; class KDbConnectionData; class KDbDriver; class KDbEscapedString; class KDbLookupFieldSchema; class KDbMessageHandler; class KDbQuerySchema; class KDbResultable; class KDbResultInfo; class KDbSqlResult; class KDbTableOrQuerySchema; class KDbVersionInfo; class tristate; namespace KDb { //! @return runtime information about version of the KDb library. //! @see KDbConnection::databaseVersion() KDbConnection::serverVersion() KDbDriverMetaData::version() KDB_EXPORT KDbVersionInfo version(); //! @overload bool deleteRecord(KDbConnection*, const KDbTableSchema&, const QString &, KDbField::Type, const QVariant &) KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname, KDbField::Type keytype, const QVariant &keyval); //! Deletes records using one generic criteria. inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString &keyname, KDbField::Type keytype, const QVariant &keyval) { return deleteRecords(conn, table.name(), keyname, keytype, keyval); } //! @overload bool deleteRecords(KDbConnection*, const QString&, const QString&, KDbField::Type, const QVariant&); inline bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname, const QString &keyval) { return deleteRecords(conn, tableName, keyname, KDbField::Text, keyval); } //! @overload bool deleteRecords(KDbConnection*, const QString&, const QString&, const QString&); inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString &keyname, const QString &keyval) { return deleteRecords(conn, table.name(), keyname, keyval); } //! @overload bool deleteRecords(KDbConnection*, const KDbTableSchema&, const QString&, const QString&); inline bool deleteRecords(KDbConnection* conn, const KDbTableSchema &table, const QString& keyname, int keyval) { return deleteRecords(conn, table, keyname, KDbField::Integer, keyval); } //! @overload bool deleteRecords(KDbConnection*, const KDbTableSchema&, const QString&, int); inline bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString& keyname, int keyval) { return deleteRecords(conn, tableName, keyname, KDbField::Integer, keyval); } //! Deletes records with two generic criterias. KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2); //! Deletes records with three generic criterias. KDB_EXPORT bool deleteRecords(KDbConnection* conn, const QString &tableName, const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1, const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2, const QString &keyname3, KDbField::Type keytype3, const QVariant& keyval3); //! Deletes all records from table @a tableName. KDB_EXPORT bool deleteAllRecords(KDbConnection* conn, const QString &tableName); //! @overload bool deleteAllRecords(KDbConnection*, const QString&); inline bool deleteAllRecords(KDbConnection* conn, const KDbTableSchema &table) { return KDb::deleteAllRecords(conn, table.name()); } /*! @return autoincrement field's @a autoIncrementFieldName value of last inserted record for result @a result. Simply, method internally fetches values of the last inserted record and returns selected field's value. The field belong to @a tableName table. Requirements: the field must be of integer type, there must be a record inserted using query for which the @a result is returned. On error std::numeric_limits::max() is returned. Last inserted record is identified by a magical record identifier, usually called ROWID (PostgreSQL has it as well as SQLite; see KDbDriverBehavior::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE). ROWID's value will be assigned back to @a recordId if this pointer is not null. */ KDB_EXPORT quint64 lastInsertedAutoIncValue(KDbSqlResult *result, - const QString& autoIncrementFieldName, const QString& tableName, quint64* recordId = 0); + const QString& autoIncrementFieldName, const QString& tableName, quint64* recordId = nullptr); /*! @overload int lastInsertedAutoIncValue(KDbSqlResult *, const QString&, const QString&, quint64*) * Accepts @a recordId that can be obtained from KDbPreparedStatement::lastInsertRecordId() * or KDbSqlResult::lastInsertRecordId(). */ KDB_EXPORT quint64 lastInsertedAutoIncValue(KDbConnection *conn, const quint64 recordId, const QString& autoIncrementFieldName, const QString& tableName); /*! @overload int lastInsertedAutoIncValue(KDbSqlResult *, const QString&, const QString&, quint64*) */ inline quint64 lastInsertedAutoIncValue(KDbSqlResult *result, - const QString& autoIncrementFieldName, const KDbTableSchema& table, quint64* recordId = 0) + const QString& autoIncrementFieldName, const KDbTableSchema& table, quint64* recordId = nullptr) { return lastInsertedAutoIncValue(result, autoIncrementFieldName, table.name(), recordId); } /*! @return list of field types for field type group @a typeGroup. */ KDB_EXPORT const QList fieldTypesForGroup(KDbField::TypeGroup typeGroup); /*! @return list of translated field type names for field type group @a typeGroup. */ KDB_EXPORT QStringList fieldTypeNamesForGroup(KDbField::TypeGroup typeGroup); /*! @return list of (nontranslated) field type names for field type group @a typeGroup. */ KDB_EXPORT QStringList fieldTypeStringsForGroup(KDbField::TypeGroup typeGroup); /*! @return default field type for field type group @a typeGroup, for example, KDbField::Integer for KDbField::IntegerGroup. It is used e.g. in KexiAlterTableDialog, to properly fill 'type' property when user selects type group for a field. */ KDB_EXPORT KDbField::Type defaultFieldTypeForGroup(KDbField::TypeGroup typeGroup); /*! @return a slightly simplified field type name type @a type. For KDbField::BLOB type it returns a translated "Image" string or other, depending on the mime type. For numbers (either floating-point or integer) it returns a translated "Number" string. For other types KDbField::typeGroupName() is returned. */ //! @todo support names of other BLOB subtypes KDB_EXPORT QString simplifiedFieldTypeName(KDbField::Type type); /*! @return true if value @a value represents an empty (but not null) value. - Case 1: If field type @a type is of any text type (KDbField::isTextType(type) == true) then the function returns true if @a value casted to a QString value is empty and not null. - Case 2: If field type @a type is KDbField::BLOB then the function returns if @a value casted to a QByteArray value is empty and not null. - Case 3: If field type @a type is of any other type then the function returns true if value.isNull(). @see KDbField::hasEmptyProperty() */ KDB_EXPORT bool isEmptyValue(KDbField::Type type, const QVariant &value); /*! Sets string pointed by @a msg to an error message retrieved from @a resultable, and string pointed by @a details to details of this error (server message and result number). Does nothing if @a result is empty. In this case @a msg and @a details strings are not overwritten. If the string pointed by @a msg is not empty, @a result message is appended to the string pointed by @a details. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, QString *msg, QString *details); /*! This methods works like above, but appends both a message and a description to string pointed by @a msg. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, QString *msg); /*! This methods works like above, but works on @a result's members instead. */ KDB_EXPORT void getHTMLErrorMesage(const KDbResultable& resultable, KDbResultInfo *info); /*! Function useful for building WHERE parts of SQL statements. Constructs an SQL string like "fielname = value" for specific @a drv driver, field type @a t, @a fieldName and @a value. If @a value is null, "fieldname is NULL" string is returned. */ KDB_EXPORT KDbEscapedString sqlWhere(KDbDriver *drv, KDbField::Type t, const QString& fieldName, const QVariant& value); /*! Find an identifier for object @a objName of type @a objType. On success true is returned and *id is set to the value of the identifier. On failure false is returned. If there is no such object, @c cancelled value is returned. */ KDB_EXPORT tristate idForObjectName(KDbConnection* conn, int *id, const QString& objName, int objType); //! @todo perhaps use quint64 here? /*! @return number of records that can be retrieved after executing @a sql statement within a connection @a conn. The statement should be of type SELECT. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occurred. */ KDB_EXPORT int recordCount(KDbConnection* conn, const KDbEscapedString& sql); //! @todo perhaps use quint64 here? /*! @return number of records that can be retrieved from @a tableSchema. The table must be created or retrieved using a KDbConnection object, i.e. tableSchema.connection() must not return 0. For SQL data sources it does not fetch any records, only "COUNT(*)" SQL aggregation is used at the backed. -1 is returned if error occurred. */ KDB_EXPORT int recordCount(const KDbTableSchema& tableSchema); /*! @overload in rowCount(const KDbTableSchema& tableSchema) Operates on a query schema. @a params are optional values of parameters that will be inserted into places marked with [] before execution of the query. */ //! @todo perhaps use quint64 here? KDB_EXPORT int recordCount(KDbQuerySchema* querySchema, const QList& params = QList()); /*! @overload int rowCount(KDbQuerySchema& querySchema, const QList& params) Operates on a table or query schema. @a params are optional values of parameters that will be inserted into places marked with [] before execution of the query. */ //! @todo perhaps use quint64 here? KDB_EXPORT int recordCount(KDbTableOrQuerySchema* tableOrQuery, const QList& params = QList()); /*! @return a number of columns that can be retrieved from table or query schema. In case of query, expanded fields are counted. Can return -1 if @a tableOrQuery has neither table or query assigned. */ KDB_EXPORT int fieldCount(KDbTableOrQuerySchema* tableOrQuery); /*! shows connection test dialog with a progress bar indicating connection testing (within a second thread). @a data is used to perform a (temporary) test connection. @a msgHandler is used to display errors. On successful connecting, a message is displayed. After testing, temporary connection is closed. */ KDB_EXPORT void connectionTestDialog(QWidget* parent, const KDbConnectionData& data, KDbMessageHandler* msgHandler); //! Used in splitToTableAndFieldParts(). enum SplitToTableAndFieldPartsOptions { FailIfNoTableOrFieldName = 0, //!< default value for splitToTableAndFieldParts() SetFieldNameIfNoTableName = 1 //!< see splitToTableAndFieldParts() }; /*! Splits @a string like "table.field" into "table" and "field" parts. On success, a table name is passed to @a tableName and a field name is passed to @a fieldName. The function fails if either: - @a string is empty, or - @a string does not contain '.' character and @a option is FailIfNoTableOrFieldName (the default), or - '.' character is the first of last character of @a string (in this case table name or field name could become empty what is not allowed). If @a option is SetFieldNameIfNoTableName and @a string does not contain '.', @a string is passed to @a fieldName and @a tableName is set to QString() without failure. If function fails, @a tableName and @a fieldName remain unchanged. @return true on success. */ KDB_EXPORT bool splitToTableAndFieldParts(const QString& string, QString *tableName, QString *fieldName, SplitToTableAndFieldPartsOptions option = FailIfNoTableOrFieldName); /*! @return true if @a type supports "visibleDecimalPlaces" property. */ KDB_EXPORT bool supportsVisibleDecimalPlacesProperty(KDbField::Type type); /*! @return string constructed by converting @a value. * If @a decimalPlaces is < 0, all meaningful fractional digits are returned (up to 10). * If @a automatically is 0, just integer part is returned. * If @a automatically is > 0, fractional part should take exactly N digits: if the fractional part is shorter than N, additional zeros are appended. Examples: * numberToString(12.345, 6) == "12.345000" * numberToString(12.345, 0) == "12" * numberToString(12.345, -1) == "12.345" * numberToString(12.0, -1) == "12" * numberToString(0.0, -1) == "0" @note No rounding is performed @note No thousands group separator is used. Decimal symbol is '.'. @see KDb::numberToLocaleString() KDbField::visibleDecimalPlaces() */ KDB_EXPORT QString numberToString(double value, int decimalPlaces); /*! Like KDb::numberToString() but formats the string using locale.toString(). If @a locale if @c nullptr, desault QLocale is used. @see KDb::numberToString() KDbField::visibleDecimalPlaces() */ KDB_EXPORT QString numberToLocaleString(double value, int decimalPlaces, const QLocale *locale = nullptr); //! @return true if @a propertyName is a builtin field property. KDB_EXPORT bool isBuiltinTableFieldProperty(const QByteArray& propertyName); //! @return true if @a propertyName is an extended field property. KDB_EXPORT bool isExtendedTableFieldProperty(const QByteArray& propertyName); //! @return true if @a propertyName is belongs to lookup field's schema. KDB_EXPORT bool isLookupFieldSchemaProperty(const QByteArray& propertyName); /*! @return type of field for integer value @a type. If @a type cannot be casted to KDbField::Type or is not normal type, i.e. @a type > KDbField::LastType (e.g. KDbField::Null), KDbField::InvalidType is returned. This can be used when type information is deserialized from a string or QVariant. @see KDbField::typesCount() KDbField::specialTypesCount() */ KDB_EXPORT KDbField::Type intToFieldType(int type); /*! @return type group of field for integer value @a typeGroup. If @a typeGroup cannot be casted to KDbField::TypeGroup, KDbField::InvalidGroup is returned. This can be used when type information is deserialized from a string or QVariant. @see KDbField::typeGroupsCount() */ KDB_EXPORT KDbField::TypeGroup intToFieldTypeGroup(int typeGroup); /*! Gets property values for the lookup schema @a lookup. @a values is cleared before filling. This function is used e.g. for altering table design. */ KDB_EXPORT void getProperties(const KDbLookupFieldSchema *lookup, QMap *values); /*! Gets property values for @a field. Properties from extended schema are included. @a values is cleared before filling. The same number of properties in the same order is returned. This function is used e.g. for altering table design. */ KDB_EXPORT void getFieldProperties(const KDbField &field, QMap *values); /*! Sets property values for @a field. @return true if all the values are valid and allowed. On failure contents of @a field is undefined. Properties from extended schema are also supported. This function is used e.g. by KDbAlterTableHandler when property information comes in form of text. */ KDB_EXPORT bool setFieldProperties(KDbField *field, const QMap& values); /*! Sets property value for @a field. @return true if the property has been found and the value is valid for this property. On failure contents of @a field is undefined. Properties from extended schema are also supported as well as QVariant customProperty(const QString& propertyName) const; This function is used e.g. by KDbAlterTableHandler when property information comes in form of text. */ KDB_EXPORT bool setFieldProperty(KDbField *field, const QByteArray& propertyName, const QVariant& value); /*! @return property value loaded from a DOM @a node, written in a QtDesigner-like notation: <number>int</number> or <bool>bool</bool>, etc. Supported types are "string", "cstring", "bool", "number". For invalid values null QVariant is returned. You can check the validity of the returned value using QVariant::type(). */ KDB_EXPORT QVariant loadPropertyValueFromDom(const QDomNode& node, bool *ok); /*! Convenience version of loadPropertyValueFromDom(). @return int value. */ KDB_EXPORT int loadIntPropertyValueFromDom(const QDomNode& node, bool* ok); /*! Convenience version of loadPropertyValueFromDom(). @return QString value. */ KDB_EXPORT QString loadStringPropertyValueFromDom(const QDomNode& node, bool* ok); /*! Saves integer element for value @a value to @a doc document within parent element @a parentEl. The value will be enclosed in "number" element and "elementName" element. Example: saveNumberElementToDom(doc, parentEl, "height", 15) will create @code 15 @endcode @return the reference to element created with tag elementName. */ KDB_EXPORT QDomElement saveNumberElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, int value); /*! Saves boolean element for value @a value to @a doc document within parent element @a parentEl. Like saveNumberElementToDom() but creates "bool" tags. True/false values will be saved as "true"/"false" strings. @return the reference to element created with tag elementName. */ KDB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString& elementName, bool value); //! @return equivalent of empty (default) value that can be set for a database field of type @a type /*! In particular returns: - empty string for text types, - 0 for integer and floating-point types, - false for boolean types, - a null byte array for BLOB type, - current date, time, date+time is returned (measured at client side) for date, time and date/time types respectively, - a null QVariant for unsupported values such as KDbField::InvalidType. */ KDB_EXPORT QVariant emptyValueForFieldType(KDbField::Type type); //! @return a value that can be set for a database field of type @a type having "notEmpty" property set. /*! It works in a similar way as @ref QVariant KDb::emptyValueForFieldType(KDbField::Type type) with the following differences: - " " string (a single space) is returned for Text and LongText types - a byte array with saved "filenew" PNG image (icon) for BLOB type Returns null QVariant for unsupported values like KDbField::InvalidType. */ KDB_EXPORT QVariant notEmptyValueForFieldType(KDbField::Type type); /*! @return true if the @a word is an reserved KDbSQL keyword See generated/sqlkeywords.cpp. @todo add function returning list of keywords. */ KDB_EXPORT bool isKDbSQLKeyword(const QByteArray& word); //! @return @a string string with applied KDbSQL identifier escaping /*! This escaping can be used for field, table, database names, etc. Use it for user-visible backend-independent statements. @see KDb::escapeIdentifierAndAddQuotes() */ KDB_EXPORT QString escapeIdentifier(const QString& string); //! @overload QString escapeIdentifier(const QString&) KDB_EXPORT QByteArray escapeIdentifier(const QByteArray& string); //! @return @a string string with applied KDbSQL identifier escaping and enclosed in " quotes /*! This escaping can be used for field, table, database names, etc. Use it for user-visible backend-independent statements. @see KDb::escapeIdentifier */ KDB_EXPORT QString escapeIdentifierAndAddQuotes(const QString& string); //! @overload QString escapeIdentifierAndAddQuotes(const QString&) KDB_EXPORT QByteArray escapeIdentifierAndAddQuotes(const QByteArray& string); /*! @return escaped string @a string for the KDbSQL dialect, i.e. doubles single quotes ("'") and inserts the string into single quotes. Quotes "'" are prepended and appended. Also escapes \\n, \\r, \\t, \\\\, \\0. Use it for user-visible backend-independent statements. @see unescapeString() */ KDB_EXPORT QString escapeString(const QString& string); //! Unescapes characters in string @a string for the KDbSQL dialect. /** The operation depends on @a quote character, which can be be ' or ". * @a string is assumed to be properly constructed. This is assured by the lexer's grammar. * Used by lexer to recognize the CHARACTER_STRING_LITERAL token. * @return unescaped string and sets value pointed by @a errorPosition (if any) to -1 on success; * and to index of problematic character on failure. * The function fails when unsupported @a quote character is passed or for unsupported sequences. * * Example sequences for ' character quote: * - \' -> ' (escaping) * - \" -> " (escaping) * - '' -> ' (repeated quote escapes too) * - "" -> "" (repeated but this is not the quote) * - ' -> (disallowed, escaping needed) * - " -> " * Example sequences for " character quote: * - \' -> ' (escaping) * - \" -> " (escaping) * - " -> " (disallowed, escaping needed) * - "" -> " (repeated quote escapes too) * - '' -> '' (repeated but this is not the quote) * - ' -> ' * * Following sequences are always unescaped (selection based on a mix of MySQL and C/JavaScript): * - \0 -> NULL (QChar()) * - \b -> backspace 0x8 * - \f -> form feed 0xc * - \n -> new line 0xa * - \r -> carriage return 0xd * - \t -> horizontal tab 0x9 * - \v -> vertical tab 0xb * - \\ -> backslash * - \? -> ? (useful when '?' placeholders are used) * - \% -> (useful when '%' wildcards are used e.g. for the LIKE operator) * - \_ -> (useful when '_' pattern is used e.g. for the LIKE operator) * - \xhh -> a character for which hh (exactly 2 digits) is interpreted as an hexadecimal * number, 00 <= hh <= FF. Widely supported by programming languages. * Can be also 00 <= hh <= ff. * Example: \xA9 translates to "©". * - \uxxxx -> 16-bit unicode character, exactly 4 digits, each x is a hexadecimal digit, * case insensitive; known from JavaScript, Java, C/C++. 0000 <= xxxxxx <= FFFF * Example: \u2665 translates to "♥". * - \u{xxxxxx} -> 24-bit unicode "code point" character, each x is a hexadecimal digit, * case insensitive; known from JavaScript (ECMAScript 6). 0 <= xxxxxx <= 10FFFF * Example: \u{1D306} translates to "𝌆" * * @note Characters without special meaning can be escaped, but then the "\" character * is skipped, e.g. "\a" == "a". * @note Trailing "\" character in @a string is ignored. * @note \nnn octal notation is not supported, it may be confusing and conflicting * when combined with other characters (\0012 is not the same as \012). * The industry is moving away from it and EcmaScript 5 deprecates it. * * See also: * - http://dev.mysql.com/doc/refman/5.7/en/string-literals.html * - https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences * - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Using_special_characters_in_strings * - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals */ KDB_EXPORT QString unescapeString(const QString& string, char quote, int *errorPosition = nullptr); //! Escaping types for BLOBS. Used in escapeBLOB(). enum BLOBEscapingType { BLOBEscapeXHex = 1, //!< Escaping like X'1FAD', used by sqlite (hex numbers) BLOBEscape0xHex, //!< Escaping like 0x1FAD, used by mysql (hex numbers) BLOBEscapeHex, //!< Escaping like 1FAD without quotes or prefixes BLOBEscapeOctal, //!< Escaping like 'zk\\000$x', used by PostgreSQL //!< (only non-printable characters are escaped using octal numbers); //!< see http://www.postgresql.org/docs/9.5/interactive/datatype-binary.html BLOBEscapeByteaHex //!< "bytea hex" escaping, e.g. E'\xDEADBEEF'::bytea used by PostgreSQL //!< (only non-printable characters are escaped using octal numbers); //!< see http://www.postgresql.org/docs/9.5/interactive/datatype-binary.html }; /*! @return a string containing escaped, printable representation of @a array. Escaping is controlled by @a type. For empty array, QString() is returned, so if you want to use this function in an SQL statement, empty arrays should be detected and "NULL" string should be put instead. This is helper, used in KDbDriver::escapeBLOB() and KDb::variantToString(). */ KDB_EXPORT QString escapeBLOB(const QByteArray& array, BLOBEscapingType type); /*! @return byte array converted from @a data of length @a length. If @a length is negative, the data is assumed to point to a null-terminated string and its length is determined dynamically. @a data is escaped in format used by PostgreSQL's bytea datatype described at http://www.postgresql.org/docs/8.1/interactive/datatype-binary.html This function is used by PostgreSQL KDb and migration drivers. */ KDB_EXPORT QByteArray pgsqlByteaToByteArray(const char* data, int length = -1); /*! @return byte array converted from @a data of length @a length. If @a length is negative, the data is assumed to point to a null-terminated string and its length is determined dynamically. @a data is escaped in format X'*', where * is one or more hexadecimal digits. Both A-F and a-f letters are supported. Even and odd number of digits are supported. If @a ok is not 0, *ok is set to result of the conversion. See BLOBEscapeXHex. */ -KDB_EXPORT QByteArray xHexToByteArray(const char* data, int length = -1, bool *ok = 0); +KDB_EXPORT QByteArray xHexToByteArray(const char* data, int length = -1, bool *ok = nullptr); /*! @return byte array converted from @a data of length @a length. If @a length is negative, the data is assumed to point to a null-terminated string and its length is determined dynamically. @a data is escaped in format 0x*, where * is one or more hexadecimal digits. Both A-F and a-f letters are supported. Even and odd number of digits are supported. If @a ok is not 0, *ok is set to result of the conversion. See BLOBEscape0xHex. */ -KDB_EXPORT QByteArray zeroXHexToByteArray(const char* data, int length = -1, bool *ok = 0); +KDB_EXPORT QByteArray zeroXHexToByteArray(const char* data, int length = -1, bool *ok = nullptr); /*! @return int list converted from string list. If @a ok is not 0, *ok is set to result of the conversion. */ -KDB_EXPORT QList stringListToIntList(const QStringList &list, bool *ok = 0); +KDB_EXPORT QList stringListToIntList(const QStringList &list, bool *ok = nullptr); /*! @return string converted from list @a list. Separators are ',' characters, "," and "\\" are escaped. @see KDb::deserializeList() */ KDB_EXPORT QString serializeList(const QStringList &list); /*! @return string list converted from @a data which was built using serializeList(). Separators are ',' characters, escaping is assumed as "\\,". */ KDB_EXPORT QStringList deserializeList(const QString &data); /*! @return int list converted from @a data which was built using serializeList(). Separators are ',' characters, escaping is assumed as "\\,". If @a ok is not 0, *ok is set to result of the conversion. @see KDb::stringListToIntList() */ KDB_EXPORT QList deserializeIntList(const QString &data, bool *ok); /*! @return string value serialized from a variant value @a v. This functions works like QVariant::toString() except the case when @a v is of type: - QByteArray - in this case KDb::escapeBLOB(v.toByteArray(), KDb::BLOBEscapeHex) is used. - QStringList - in this case KDb::serializeList(v.toStringList()) is used. This function is needed for handling values of random type, for example "defaultValue" property of table fields can contain value of any type. Note: the returned string is an unescaped string. */ KDB_EXPORT QString variantToString(const QVariant& v); /*! @return variant value of type @a type for a string @a s that was previously serialized using @ref variantToString( const QVariant& v ) function. @a ok is set to the result of the operation. With exception for types mentioned in documentation of variantToString(), QVariant::convert() is used for conversion. */ KDB_EXPORT QVariant stringToVariant(const QString& s, QVariant::Type type, bool* ok); /*! @return true if setting default value for @a field field is allowed. Fields with unique (and thus primary key) flags set do not accept default values. */ KDB_EXPORT bool isDefaultValueAllowed(const KDbField &field); //! Provides limits for values of type @a type /*! The result is put into integers pointed by @a minValue and @a maxValue. The limits are machine-independent,. what is useful for format and protocol compatibility. Supported types are Byte, ShortInteger, Integer and BigInteger. The value of @a signedness controls the values; they can be limited to unsigned or not. Results for BigInteger or non-integer types are the same as for Integer due to limitation of int type. Signed integers are assumed. @a minValue and @a maxValue must not be 0. */ KDB_EXPORT void getLimitsForFieldType(KDbField::Type type, qlonglong *minValue, qlonglong *maxValue, KDb::Signedness signedness = KDb::Signed); /*! @return type that's maximum of two integer types @a t1 and @a t2, e.g. Integer for (Byte, Integer). If one of the types is not of the integer group, KDbField::InvalidType is returned. Returned type may not fit to the result of evaluated expression that involves the arguments. For example, 100 is within Byte type, maximumForIntegerFieldTypes(Byte, Byte) is Byte but result of 100 * 100 exceeds the range of Byte. */ KDB_EXPORT KDbField::Type maximumForIntegerFieldTypes(KDbField::Type t1, KDbField::Type t2); //! @return QVariant value converted from a @a data string /*! Conversion is based on the information about type @a type. @a type has to be an element from KDbField::Type, not greater than KDbField::LastType. For unsupported type this function fails. @a length value controls number of characters used in the conversion. It is optional value for all cases but for the BLOB type because for it @a data is not null-terminated so the length cannot be measured. The value of @a signedness controls the conversion in case of integer types; numbers can be limited to unsigned or not. If @a ok is not 0 *ok is set to false on failure and to true on success. On failure a null QVariant is returned. The function fails if @a data is 0. For rules of conversion to the boolean type see the documentation of @ref QVariant::toBool(), QVariant::toDate() for date type, QVariant::toDateTime() for date+time type, QVariant::toTime() for time type. */ KDB_EXPORT QVariant cstringToVariant(const char* data, KDbField::Type type, bool *ok, int length = -1, KDb::Signedness signedness = KDb::Signed); /*! @return default file-based driver MIME type (typically something like "application/x-kexiproject-sqlite") */ KDB_EXPORT QString defaultFileBasedDriverMimeType(); /*! @return default file-based driver ID (currently, "org.kde.kdb.sqlite"). */ KDB_EXPORT QString defaultFileBasedDriverId(); /*! Escapes and converts value @a v (for type @a ftype) to string representation required by KDbSQL commands. For Date/Time type KDb::dateTimeToSQL() is used. For BLOB type KDb::escapeBlob() with BLOBEscape0xHex conversion type is used. */ KDB_EXPORT KDbEscapedString valueToSQL(KDbField::Type ftype, const QVariant& v); /*! Converts value @a v to string representation required by KDbSQL commands: ISO 8601 DateTime format - with "T" delimiter/ For specification see http://www.w3.org/TR/NOTE-datetime. Example: "1994-11-05T13:15:30" not "1994-11-05 13:15:30". @todo Add support for time zones */ KDB_EXPORT KDbEscapedString dateTimeToSQL(const QDateTime& v); #ifdef KDB_DEBUG_GUI //! A prototype of handler for GUI debugger typedef void(*DebugGUIHandler)(const QString&); //! Sets handler for GUI debugger KDB_EXPORT void setDebugGUIHandler(DebugGUIHandler handler); //! Outputs string @a text to the GUI debugger KDB_EXPORT void debugGUI(const QString& text); //! A prototype of handler for GUI debugger (specialized for the Alter Table feature) typedef void(*AlterTableActionDebugGUIHandler)(const QString&, int); //! Sets handler for GUI debugger (specialized for the Alter Table feature) KDB_EXPORT void setAlterTableActionDebugHandler(AlterTableActionDebugGUIHandler handler); //! Outputs string @a text to the GUI debugger (specialized for the Alter Table feature); //! @a nestingLevel can be provided for nested outputs. KDB_EXPORT void alterTableActionDebugGUI(const QString& text, int nestingLevel = 0); #endif //! @return @a string if it is not empty, else returns @a stringIfEmpty. /*! This function is an optimization in cases when @a string is a result of expensive functioncall because any evaluation will be performed once, not twice. Another advantage is simpified code through the functional approach. The function expects bool isEmpty() method to be present in type T, so T can typically be QString or QByteArray. */ template T iifNotEmpty(const T &string, const T &stringIfEmpty) { return string.isEmpty() ? stringIfEmpty : string; } //! @overload iifNotEmpty(const T &string, const T &stringIfEmpty) template T iifNotEmpty(const QByteArray &string, const T &stringIfEmpty) { return iifNotEmpty(QLatin1String(string), stringIfEmpty); } //! @overload iifNotEmpty(const T &string, const T &stringIfEmpty) template T iifNotEmpty(const T &string, const QByteArray &stringIfEmpty) { return iifNotEmpty(string, QLatin1String(stringIfEmpty)); } //! @return @a value if @a ok is true, else returns default value T(). template T iif(bool ok, const T &value) { if (ok) { return value; } return T(); } /*! @return a list of paths that KDb will search when dynamically loading libraries (plugins) This is basicaly list of directories returned QCoreApplication::libraryPaths() that have readable subdirectory "kdb". @see QCoreApplication::libraryPaths() */ KDB_EXPORT QStringList libraryPaths(); /*! @return new temporary name suitable for creating new table. The name has mask tmp__{baseName}{rand} where baseName is passed as argument and {rand} is a 10 digits long hexadecimal number. @a baseName can be empty. It is adviced to use the returned name as quickly as possible for creating new physical table. It is not 100% guaranteed that table with this name will not exist at an attempt of creation but it is very unlikely. The function checks for existence of a table with temporary name for connection @a conn. Empty string is returned if @a conn is not present or is not open or if checking for existence of table withg temporary name failed. */ KDB_EXPORT QString temporaryTableName(KDbConnection *conn, const QString &baseName); /*! @return absolute path to "sqlite3" program. Empty string is returned if the program was not found. */ KDB_EXPORT QString sqlite3ProgramPath(); /*! Imports file in SQL format from @a inputFileName into @a outputFileName. Works for any SQLite 3 dump file. Requires access to executing the "sqlite3" command. File named @a outputFileName will be silently overwritten with a new SQLite 3 database file. @return true on success. */ KDB_EXPORT bool importSqliteFile(const QString &inputFileName, const QString &outputFileName); /*! @return true if @a s is a valid identifier, ie. starts with a letter or '_' character and contains only letters, numbers and '_' character. */ KDB_EXPORT bool isIdentifier(const QString& s); //! @overload isIdentifier(const QString& s) //! @since 3.1 KDB_EXPORT bool isIdentifier(const QByteArray& s); /*! @return valid identifier based on @a s. Non-alphanumeric characters (or spaces) are replaced with '_'. If a number is at the beginning, '_' is added at start. Empty strings are not changed. Case remains unchanged. */ KDB_EXPORT QString stringToIdentifier(const QString &s); /*! @return useful message "Value of "valueName" field must be an identifier. "v" is not a valid identifier.". It is also used by KDbIdentifierValidator. */ KDB_EXPORT QString identifierExpectedMessage(const QString &valueName, const QVariant& v); } // namespace KDb #endif diff --git a/src/KDbAdmin.h b/src/KDbAdmin.h index 50ee33ed..9428c682 100644 --- a/src/KDbAdmin.h +++ b/src/KDbAdmin.h @@ -1,52 +1,52 @@ /* This file is part of the KDE project Copyright (C) 2006 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_ADMIN_H #define KDB_ADMIN_H #include "KDbResult.h" class KDbConnectionData; //! @short An interface containing a set of tools for database administration /*! Can be implemented in database drivers. @see KDbDriver::adminTools */ class KDB_EXPORT KDbAdminTools : public KDbResultable { public: KDbAdminTools(); - virtual ~KDbAdminTools(); + ~KDbAdminTools() override; /*! Performs vacuum (compacting) for connection @a data. Can be implemented for your driver. Note: in most cases the database should not be opened. Currently it is implemented for SQLite drivers. @return true on success, false on failure (then you can get error status from the KDbAdminTools object). */ virtual bool vacuum(const KDbConnectionData& data, const QString& databaseName); private: Q_DISABLE_COPY(KDbAdminTools) class Private; Private * const d; }; #endif diff --git a/src/KDbAlter.cpp b/src/KDbAlter.cpp index 6ea20e08..bc3344fd 100644 --- a/src/KDbAlter.cpp +++ b/src/KDbAlter.cpp @@ -1,1135 +1,1135 @@ /* This file is part of the KDE project Copyright (C) 2006-2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbAlter.h" #include "KDb.h" #include "KDbConnection.h" #include "KDbConnectionOptions.h" #include "kdb_debug.h" #include #include class Q_DECL_HIDDEN KDbAlterTableHandler::Private { public: Private() {} ~Private() { qDeleteAll(actions); } ActionList actions; //! @todo IMPORTANT: replace QPointer conn; KDbConnection* conn; private: Q_DISABLE_COPY(Private) }; //! Define a global instance used to when returning null is needed #define DEFINE_NULL_OBJECT(name) \ class Null ## name : public KDbAlterTableHandler::name \ { \ public: \ Null ## name() : KDbAlterTableHandler::name(true) {} \ }; \ Null ## name null ## name DEFINE_NULL_OBJECT(ChangeFieldPropertyAction); DEFINE_NULL_OBJECT(RemoveFieldAction); DEFINE_NULL_OBJECT(InsertFieldAction); DEFINE_NULL_OBJECT(MoveFieldPositionAction); //-------------------------------------------------------- KDbAlterTableHandler::ActionBase::ActionBase(bool null) : m_alteringRequirements(0) , m_order(-1) , m_null(null) { } KDbAlterTableHandler::ActionBase::~ActionBase() { } KDbAlterTableHandler::ChangeFieldPropertyAction& KDbAlterTableHandler::ActionBase::toChangeFieldPropertyAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullChangeFieldPropertyAction; } KDbAlterTableHandler::RemoveFieldAction& KDbAlterTableHandler::ActionBase::toRemoveFieldAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullRemoveFieldAction; } KDbAlterTableHandler::InsertFieldAction& KDbAlterTableHandler::ActionBase::toInsertFieldAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullInsertFieldAction; } KDbAlterTableHandler::MoveFieldPositionAction& KDbAlterTableHandler::ActionBase::toMoveFieldPositionAction() { if (dynamic_cast(this)) return *dynamic_cast(this); return nullMoveFieldPositionAction; } void KDbAlterTableHandler::ActionBase::debug(const DebugOptions& debugOptions) { kdbDebug() << debugString(debugOptions) << " (req = " << alteringRequirements() << ")"; } //-------------------------------------------------------- KDbAlterTableHandler::FieldActionBase::FieldActionBase(const QString& fieldName, int uid) : ActionBase(false) , m_fieldUID(uid) , m_fieldName(fieldName) { } KDbAlterTableHandler::FieldActionBase::FieldActionBase(bool) : ActionBase(true) , m_fieldUID(-1) { } KDbAlterTableHandler::FieldActionBase::~FieldActionBase() { } //-------------------------------------------------------- //! @internal struct KDb_AlterTableHandlerStatic { KDb_AlterTableHandlerStatic() { #define I(name, type) \ types.insert(QByteArray(name).toLower(), int(KDbAlterTableHandler::type)) #define I2(name, type1, type2) \ flag = int(KDbAlterTableHandler::type1)|int(KDbAlterTableHandler::type2); \ if (flag & KDbAlterTableHandler::PhysicalAlteringRequired) \ flag |= KDbAlterTableHandler::MainSchemaAlteringRequired; \ types.insert(QByteArray(name).toLower(), flag) /* useful links: http://dev.mysql.com/doc/refman/5.0/en/create-table.html */ // ExtendedSchemaAlteringRequired is here because when the field is renamed, // we need to do the same rename in extended table schema: int flag; I2("name", PhysicalAlteringRequired, MainSchemaAlteringRequired); I2("type", PhysicalAlteringRequired, DataConversionRequired); I("caption", MainSchemaAlteringRequired); I("description", MainSchemaAlteringRequired); I2("unsigned", PhysicalAlteringRequired, DataConversionRequired); // always? I2("maxLength", PhysicalAlteringRequired, DataConversionRequired); // always? I2("precision", PhysicalAlteringRequired, DataConversionRequired); // always? I("defaultWidth", ExtendedSchemaAlteringRequired); // defaultValue: depends on backend, for mysql it can only by a constant or now()... // -- should we look at KDbDriver here? #ifdef KDB_UNFINISHED I2("defaultValue", PhysicalAlteringRequired, MainSchemaAlteringRequired); #else //! @todo reenable I("defaultValue", MainSchemaAlteringRequired); #endif I2("primaryKey", PhysicalAlteringRequired, DataConversionRequired); I2("unique", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here I2("notNull", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here // allowEmpty: only support it just at kexi level? maybe there is a backend that supports this? I2("allowEmpty", PhysicalAlteringRequired, MainSchemaAlteringRequired); I2("autoIncrement", PhysicalAlteringRequired, DataConversionRequired); // data conversion may be hard here I2("indexed", PhysicalAlteringRequired, DataConversionRequired); // we may want to add an Index here // easier cases follow... I("visibleDecimalPlaces", ExtendedSchemaAlteringRequired); //more to come... #undef I #undef I2 } QHash types; }; Q_GLOBAL_STATIC(KDb_AlterTableHandlerStatic, KDb_alteringTypeForProperty) //! @internal int KDbAlterTableHandler::alteringTypeForProperty(const QByteArray& propertyName) { const int res = KDb_alteringTypeForProperty->types[propertyName.toLower()]; if (res == 0) { if (KDb::isExtendedTableFieldProperty(propertyName)) return int(ExtendedSchemaAlteringRequired); kdbWarning() << "property" << propertyName << "not found!"; } return res; } //--- KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction( const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid) : FieldActionBase(fieldName, uid) , m_propertyName(propertyName) , m_newValue(newValue) { } KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction() : ChangeFieldPropertyAction(true) { } KDbAlterTableHandler::ChangeFieldPropertyAction::ChangeFieldPropertyAction(bool null) : FieldActionBase(null) { } KDbAlterTableHandler::ChangeFieldPropertyAction::~ChangeFieldPropertyAction() { } void KDbAlterTableHandler::ChangeFieldPropertyAction::updateAlteringRequirements() { setAlteringRequirements(alteringTypeForProperty(m_propertyName.toLatin1())); } QString KDbAlterTableHandler::ChangeFieldPropertyAction::debugString(const DebugOptions& debugOptions) { QString s = QString::fromLatin1("Set \"%1\" property for table field \"%2\" to \"%3\"") .arg(m_propertyName, fieldName(), m_newValue.toString()); if (debugOptions.showUID) { s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID)); } return s; } static KDbAlterTableHandler::ActionDict* createActionDict( KDbAlterTableHandler::ActionDictDict *fieldActions, int forFieldUID) { KDbAlterTableHandler::ActionDict* dict = new KDbAlterTableHandler::ActionDict(); fieldActions->insert(forFieldUID, dict); return dict; } static void debugAction(KDbAlterTableHandler::ActionBase *action, int nestingLevel, - bool simulate, const QString& prependString = QString(), QString * debugTarget = 0) + bool simulate, const QString& prependString = QString(), QString * debugTarget = nullptr) { Q_UNUSED(simulate); Q_UNUSED(nestingLevel); QString debugString; if (!debugTarget) debugString = prependString; if (action) { KDbAlterTableHandler::ActionBase::DebugOptions debugOptions; - debugOptions.showUID = debugTarget == 0; - debugOptions.showFieldDebug = debugTarget != 0; + debugOptions.showUID = debugTarget == nullptr; + debugOptions.showFieldDebug = debugTarget != nullptr; debugString += action->debugString(debugOptions); } else { if (!debugTarget) { debugString += QLatin1String("[No action]"); //hmm } } if (debugTarget) { if (!debugString.isEmpty()) { *debugTarget += debugString + QLatin1Char('\n'); } } else { kdbDebug() << debugString; #ifdef KDB_DEBUG_GUI if (simulate) KDb::alterTableActionDebugGUI(debugString, nestingLevel); #endif } } static void debugActionDict(KDbAlterTableHandler::ActionDict *dict, int fieldUID, bool simulate) { QString fieldName; KDbAlterTableHandler::ActionDictConstIterator it(dict->constBegin()); if (it != dict->constEnd() && dynamic_cast(it.value())) { //retrieve field name from the 1st related action fieldName = dynamic_cast(it.value())->fieldName(); } else { fieldName = QLatin1String("??"); } QString dbg(QString::fromLatin1("Action dict for field \"%1\" (%2, UID=%3):") .arg(fieldName).arg(dict->count()).arg(fieldUID)); kdbDebug() << dbg; #ifdef KDB_DEBUG_GUI if (simulate) KDb::alterTableActionDebugGUI(dbg, 1); #endif for (;it != dict->constEnd(); ++it) { debugAction(it.value(), 2, simulate); } } static void debugFieldActions(const KDbAlterTableHandler::ActionDictDict &fieldActions, bool simulate) { #ifdef KDB_DEBUG_GUI if (simulate) KDb::alterTableActionDebugGUI(QLatin1String("** Simplified Field Actions:")); #endif for (KDbAlterTableHandler::ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) { debugActionDict(it.value(), it.key(), simulate); } } /*! Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Case 1. (special) when new action=[rename A to B] and exists=[rename B to C] => remove [rename B to C] and set result to new [rename A to C] and go to 1b. Case 1b. when new action=[rename A to B] and actions exist like [set property P to C in field B] or like [delete field B] or like [move field B] => change B to A for all these actions Case 2. when new action=[change property in field A] (property != name) and exists=[remove A] or exists=[change property in field A] => do not add [change property in field A] because it will be removed anyway or the property will change */ void KDbAlterTableHandler::ChangeFieldPropertyAction::simplifyActions(ActionDictDict *fieldActions) { ActionDict *actionsLikeThis = fieldActions->value(uid()); if (m_propertyName == QLatin1String("name")) { // Case 1. special: name1 -> name2, i.e. rename action QByteArray newName(newValue().toString().toLatin1()); // try to find rename(newName, otherName) action - ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->value(newName) : 0; + ActionBase *renameActionLikeThis = actionsLikeThis ? actionsLikeThis->value(newName) : nullptr; if (dynamic_cast(renameActionLikeThis)) { // 1. instead of having rename(fieldName(), newValue()) action, // let's have rename(fieldName(), otherName) action m_newValue = dynamic_cast(renameActionLikeThis)->m_newValue; /* KDbAlterTableHandler::ChangeFieldPropertyAction* newRenameAction = new KDbAlterTableHandler::ChangeFieldPropertyAction( *this ); newRenameAction->m_newValue = dynamic_cast(renameActionLikeThis)->m_newValue; // (m_order is the same as in newAction) // replace prev. rename action (if any) actionsLikeThis->remove( "name" ); ActionDict *adict = (*fieldActions)[ fieldName().toLatin1() ]; if (!adict) adict = createActionDict( fieldActions, fieldName() ); adict->insert(m_propertyName.toLatin1(), newRenameAction);*/ } else { - ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : 0; + ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr; if (removeActionForThisField) { //if this field is going to be removed, just change the action's field name // and do not add a new action } else { //just insert a copy of the rename action if (!actionsLikeThis) actionsLikeThis = createActionDict(fieldActions, uid()); KDbAlterTableHandler::ChangeFieldPropertyAction* newRenameAction = new KDbAlterTableHandler::ChangeFieldPropertyAction(*this); kdbDebug() << "insert into" << fieldName() << "dict:" << newRenameAction->debugString(); actionsLikeThis->insert(m_propertyName.toLatin1(), newRenameAction); return; } } if (actionsLikeThis) { // Case 1b. change "field name" information to fieldName() in any action that // is related to newName // e.g. if there is setCaption("B", "captionA") action after rename("A","B"), // replace setCaption action with setCaption("A", "captionA") foreach(ActionBase* action, *actionsLikeThis) { dynamic_cast(action)->setFieldName(fieldName()); } } return; } - ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : 0; + ActionBase *removeActionForThisField = actionsLikeThis ? actionsLikeThis->value(":remove:") : nullptr; if (removeActionForThisField) { //if this field is going to be removed, do not add a new action return; } // Case 2. other cases: just give up with adding this "intermediate" action // so, e.g. [ setCaption(A, "captionA"), setCaption(A, "captionB") ] // becomes: [ setCaption(A, "captionB") ] // because adding this action does nothing ActionDict *nextActionsLikeThis = fieldActions->value(uid()); if (!nextActionsLikeThis || !nextActionsLikeThis->value(m_propertyName.toLatin1())) { //no such action, add this KDbAlterTableHandler::ChangeFieldPropertyAction* newAction = new KDbAlterTableHandler::ChangeFieldPropertyAction(*this); if (!nextActionsLikeThis) nextActionsLikeThis = createActionDict(fieldActions, uid()); nextActionsLikeThis->insert(m_propertyName.toLatin1(), newAction); } } bool KDbAlterTableHandler::ChangeFieldPropertyAction::shouldBeRemoved(ActionDictDict *fieldActions) { Q_UNUSED(fieldActions); return 0 == fieldName().compare(m_newValue.toString(), Qt::CaseInsensitive); } tristate KDbAlterTableHandler::ChangeFieldPropertyAction::updateTableSchema(KDbTableSchema* table, KDbField* field, QHash* fieldHash) { //1. Simpler cases first: changes that do not affect table schema at all // "caption", "description", "defaultWidth", "visibleDecimalPlaces" if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) { bool result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue()); return result; } if (m_propertyName == QLatin1String("name")) { if (fieldHash->value(field->name()) == field->name()) fieldHash->remove(field->name()); fieldHash->insert(newValue().toString(), field->name()); table->renameField(field, newValue().toString()); return true; } return cancelled; } /*! Many of the properties must be applied using a separate algorithm. */ tristate KDbAlterTableHandler::ChangeFieldPropertyAction::execute(KDbConnection* conn, KDbTableSchema* table) { Q_UNUSED(conn); KDbField *field = table->field(fieldName()); if (!field) { //! @todo errmsg return false; } bool result; //1. Simpler cases first: changes that do not affect table schema at all // "caption", "description", "defaultWidth", "visibleDecimalPlaces" if (SchemaAlteringRequired & alteringTypeForProperty(m_propertyName.toLatin1())) { result = KDb::setFieldProperty(field, m_propertyName.toLatin1(), newValue()); return result; } //! @todo #if 1 return true; #else //2. Harder cases, that often require special care if (m_propertyName == QLatin1String("name")) { /*mysql: A. Get real field type (it's safer): let be the 2nd "Type" column from result of "DESCRIBE tablename oldfieldname" ( http://dev.mysql.com/doc/refman/5.0/en/describe.html ) B. Run "ALTER TABLE tablename CHANGE oldfieldname newfieldname "; ( http://dev.mysql.com/doc/refman/5.0/en/alter-table.html ) */ } if (m_propertyName == QLatin1String("type")) { /*mysql: A. Like A. for "name" property above B. Construct string, eg. "varchar(50)" using the driver C. Like B. for "name" property above (mysql then truncate the values for changes like varchar -> integer, and properly convert the values for changes like integer -> varchar) */ //! @todo more cases to check } if (m_propertyName == QLatin1String("maxLength")) { //! @todo use "select max( length(o_name) ) from kexi__objects" } if (m_propertyName == QLatin1String("primaryKey")) { //! @todo } /* "name", "unsigned", "precision", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", "autoIncrement", "indexed", bool result = KDb::setFieldProperty(*field, m_propertyName.toLatin1(), newValue()); */ return result; #endif } //-------------------------------------------------------- KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(const QString& fieldName, int uid) : FieldActionBase(fieldName, uid) { } KDbAlterTableHandler::RemoveFieldAction::RemoveFieldAction(bool null) : FieldActionBase(null) { } KDbAlterTableHandler::RemoveFieldAction::~RemoveFieldAction() { } void KDbAlterTableHandler::RemoveFieldAction::updateAlteringRequirements() { //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? setAlteringRequirements(PhysicalAlteringRequired); //! @todo } QString KDbAlterTableHandler::RemoveFieldAction::debugString(const DebugOptions& debugOptions) { QString s = QString::fromLatin1("Delete table field \"%1\"").arg(fieldName()); if (debugOptions.showUID) { s.append(QString::fromLatin1(" (UID=%1)").arg(uid())); } return s; } /*! Legend: A,B==objects, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Preconditions: we assume there cannot be such case encountered: ([remove A], [do something related on A]) (except for [remove A], [insert A]) General Case: it's safe to always insert a [remove A] action. */ void KDbAlterTableHandler::RemoveFieldAction::simplifyActions(ActionDictDict *fieldActions) { //! @todo not checked KDbAlterTableHandler::RemoveFieldAction* newAction = new KDbAlterTableHandler::RemoveFieldAction(*this); ActionDict *actionsLikeThis = fieldActions->value(uid()); if (!actionsLikeThis) actionsLikeThis = createActionDict(fieldActions, uid()); actionsLikeThis->insert(":remove:", newAction); //special } tristate KDbAlterTableHandler::RemoveFieldAction::updateTableSchema(KDbTableSchema* table, KDbField* field, QHash* fieldHash) { fieldHash->remove(field->name()); table->removeField(field); return true; } tristate KDbAlterTableHandler::RemoveFieldAction::execute(KDbConnection* conn, KDbTableSchema* table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(int fieldIndex, KDbField *field, int uid) : FieldActionBase(field->name(), uid) , m_index(fieldIndex) - , m_field(0) + , m_field(nullptr) { Q_ASSERT(field); setField(field); } KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(const InsertFieldAction& action) : FieldActionBase(action) //action.fieldName(), action.uid()) , m_index(action.index()) { m_field = new KDbField(*action.field()); } KDbAlterTableHandler::InsertFieldAction::InsertFieldAction() : InsertFieldAction(true) { } KDbAlterTableHandler::InsertFieldAction::InsertFieldAction(bool null) : FieldActionBase(null) , m_index(0) - , m_field(0) + , m_field(nullptr) { } KDbAlterTableHandler::InsertFieldAction::~InsertFieldAction() { delete m_field; } void KDbAlterTableHandler::InsertFieldAction::setField(KDbField* field) { if (m_field) delete m_field; m_field = field; setFieldName(m_field ? m_field->name() : QString()); } void KDbAlterTableHandler::InsertFieldAction::updateAlteringRequirements() { //! @todo sometimes add DataConversionRequired (e.g. when relationships require removing orphaned records) ? setAlteringRequirements(PhysicalAlteringRequired); //! @todo } QString KDbAlterTableHandler::InsertFieldAction::debugString(const DebugOptions& debugOptions) { QString s = QString::fromLatin1("Insert table field \"%1\" at position %2") .arg(m_field->name()).arg(m_index); if (debugOptions.showUID) { s.append(QString::fromLatin1(" (UID=%1)").arg(m_fieldUID)); } if (debugOptions.showFieldDebug) { s.append(QString::fromLatin1(" (%1)").arg(KDbUtils::debugString(*m_field))); } return s; } /*! Legend: A,B==fields, P==property, [....]==action, (..,..,..) group of actions, <...> internal operation. Case 1: there are "change property" actions after the Insert action. -> change the properties in the Insert action itself and remove the "change property" actions. Examples: [Insert A] && [rename A to B] => [Insert B] [Insert A] && [change property P in field A] => [Insert A with P altered] Comment: we need to do this reduction because otherwise we'd need to do psyhical altering right after [Insert A] if [rename A to B] follows. */ void KDbAlterTableHandler::InsertFieldAction::simplifyActions(ActionDictDict *fieldActions) { // Try to find actions related to this action ActionDict *actionsForThisField = fieldActions->value(uid()); - ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->value(":remove:") : 0; + ActionBase *removeActionForThisField = actionsForThisField ? actionsForThisField->value(":remove:") : nullptr; if (removeActionForThisField) { //if this field is going to be removed, do not add a new action //and remove the "Remove" action actionsForThisField->remove(":remove:"); return; } if (actionsForThisField) { //collect property values that have to be changed in this field QMap values; ActionDict *newActionsForThisField = new ActionDict(); // this will replace actionsForThisField after the loop QSet actionsToDelete; // used to collect actions taht we soon delete but cannot delete in the loop below for (ActionDictConstIterator it(actionsForThisField->constBegin()); it != actionsForThisField->constEnd();++it) { ChangeFieldPropertyAction* changePropertyAction = dynamic_cast(it.value()); if (changePropertyAction) { //if this field is going to be renamed, also update fieldName() if (changePropertyAction->propertyName() == QLatin1String("name")) { setFieldName(changePropertyAction->newValue().toString()); } values.insert(changePropertyAction->propertyName().toLatin1(), changePropertyAction->newValue()); //the subsequent "change property" action is no longer needed actionsToDelete.insert(it.value()); } else { //keep newActionsForThisField->insert(it.key(), it.value()); } } qDeleteAll(actionsToDelete); actionsForThisField->setAutoDelete(false); delete actionsForThisField; actionsForThisField = newActionsForThisField; fieldActions->take(uid()); fieldActions->insert(uid(), actionsForThisField); if (!values.isEmpty()) { //update field, so it will be created as one step KDbField *f = new KDbField(*field()); if (KDb::setFieldProperties(f, values)) { setField(f); kdbDebug() << field(); #ifdef KDB_DEBUG_GUI KDb::alterTableActionDebugGUI( QLatin1String("** Property-set actions moved to field definition itself:\n") + KDbUtils::debugString(*field()), 0); #endif } else { #ifdef KDB_DEBUG_GUI KDb::alterTableActionDebugGUI( QLatin1String("** Failed to set properties for field ") + KDbUtils::debugString(*field()), 0); #endif kdbWarning() << "setFieldProperties() failed!"; delete f; } } } //ok, insert this action //! @todo not checked KDbAlterTableHandler::InsertFieldAction* newAction = new KDbAlterTableHandler::InsertFieldAction(*this); if (!actionsForThisField) actionsForThisField = createActionDict(fieldActions, uid()); actionsForThisField->insert(":insert:", newAction); //special } tristate KDbAlterTableHandler::InsertFieldAction::updateTableSchema(KDbTableSchema* table, KDbField* field, QHash* fieldMap) { //in most cases we won't add the field to fieldMap Q_UNUSED(field); //! @todo add it only when there should be fixed value (e.g. default) set for this new field... fieldMap->remove(this->field()->name()); table->insertField(index(), new KDbField(*this->field())); return true; } tristate KDbAlterTableHandler::InsertFieldAction::execute(KDbConnection* conn, KDbTableSchema* table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction( int fieldIndex, const QString& fieldName, int uid) : FieldActionBase(fieldName, uid) , m_index(fieldIndex) { } KDbAlterTableHandler::MoveFieldPositionAction::MoveFieldPositionAction(bool null) : FieldActionBase(null) , m_index(-1) { } KDbAlterTableHandler::MoveFieldPositionAction::~MoveFieldPositionAction() { } void KDbAlterTableHandler::MoveFieldPositionAction::updateAlteringRequirements() { setAlteringRequirements(MainSchemaAlteringRequired); //! @todo } QString KDbAlterTableHandler::MoveFieldPositionAction::debugString(const DebugOptions& debugOptions) { QString s = QString::fromLatin1("Move table field \"%1\" to position %2") .arg(fieldName()).arg(m_index); if (debugOptions.showUID) { s.append(QString::fromLatin1(" (UID=%1)").arg(uid())); } return s; } void KDbAlterTableHandler::MoveFieldPositionAction::simplifyActions(ActionDictDict *fieldActions) { Q_UNUSED(fieldActions); //! @todo } tristate KDbAlterTableHandler::MoveFieldPositionAction::execute(KDbConnection* conn, KDbTableSchema* table) { Q_UNUSED(conn); Q_UNUSED(table); //! @todo return true; } //-------------------------------------------------------- KDbAlterTableHandler::KDbAlterTableHandler(KDbConnection* conn) : d(new Private()) { d->conn = conn; } KDbAlterTableHandler::~KDbAlterTableHandler() { delete d; } void KDbAlterTableHandler::addAction(ActionBase* action) { d->actions.append(action); } KDbAlterTableHandler& KDbAlterTableHandler::operator<< (ActionBase* action) { d->actions.append(action); return *this; } const KDbAlterTableHandler::ActionList& KDbAlterTableHandler::actions() const { return d->actions; } void KDbAlterTableHandler::removeAction(int index) { d->actions.removeAt(index); } void KDbAlterTableHandler::clear() { d->actions.clear(); } void KDbAlterTableHandler::setActions(const ActionList& actions) { qDeleteAll(d->actions); d->actions = actions; } void KDbAlterTableHandler::debug() { kdbDebug() << "KDbAlterTableHandler's actions:"; foreach(ActionBase* action, d->actions) { action->debug(); } } KDbTableSchema* KDbAlterTableHandler::execute(const QString& tableName, ExecutionArguments* args) { args->result = false; if (!d->conn) { //! @todo err msg? - return 0; + return nullptr; } if (d->conn->options()->isReadOnly()) { //! @todo err msg? - return 0; + return nullptr; } if (!d->conn->isDatabaseUsed()) { //! @todo err msg? - return 0; + return nullptr; } KDbTableSchema *oldTable = d->conn->tableSchema(tableName); if (!oldTable) { //! @todo err msg? - return 0; + return nullptr; } if (!args->debugString) debug(); // Find a sum of requirements... int allActionsCount = 0; foreach(ActionBase* action, d->actions) { action->updateAlteringRequirements(); action->m_order = allActionsCount++; } /* Simplify actions list if possible and check for errors How to do it? - track property changes/deletions in reversed order - reduce intermediate actions Trivial example 1: *action1: "rename field a to b" *action2: "rename field b to c" *action3: "rename field c to d" After reduction: *action1: "rename field a to d" Summing up: we have tracked what happens to field curently named "d" and eventually discovered that it was originally named "a". Trivial example 2: *action1: "rename field a to b" *action2: "rename field b to c" *action3: "remove field b" After reduction: *action3: "remove field b" Summing up: we have noticed that field "b" has beed eventually removed so we needed to find all actions related to this field and remove them. This is good optimization, as some of the eventually removed actions would be difficult to perform and/or costly, what would be a waste of resources and a source of unwanted questions sent to the user. */ // Fields-related actions. ActionDictDict fieldActions; ActionBase* action; for (int i = d->actions.count() - 1; i >= 0; i--) { d->actions[i]->simplifyActions(&fieldActions); } if (!args->debugString) debugFieldActions(fieldActions, args->simulate); // Prepare actions for execution ---- // - Sort actions by order ActionsVector actionsVector(allActionsCount); int currentActionsCount = 0; //some actions may be removed args->requirements = 0; QSet fieldsWithChangedMainSchema; // Used to collect fields with changed main schema. // This will be used when recreateTable is false to update kexi__fields for (ActionDictDictConstIterator it(fieldActions.constBegin()); it != fieldActions.constEnd(); ++it) { for (KDbAlterTableHandler::ActionDictConstIterator it2(it.value()->constBegin()); it2 != it.value()->constEnd(); ++it2, currentActionsCount++) { if (it2.value()->shouldBeRemoved(&fieldActions)) continue; actionsVector[ it2.value()->m_order ] = it2.value(); // a sum of requirements... const int r = it2.value()->alteringRequirements(); args->requirements |= r; if (r & MainSchemaAlteringRequired && dynamic_cast(it2.value())) { // Remember, this will be used when recreateTable is false to update kexi__fields, below. fieldsWithChangedMainSchema.insert( dynamic_cast(it2.value())->fieldName()); } } } // - Debug QString dbg = QString::fromLatin1("** Overall altering requirements: %1").arg(args->requirements); kdbDebug() << dbg; if (args->onlyComputeRequirements) { args->result = true; - return 0; + return nullptr; } const bool recreateTable = (args->requirements & PhysicalAlteringRequired); #ifdef KDB_DEBUG_GUI if (args->simulate) KDb::alterTableActionDebugGUI(dbg, 0); #endif dbg = QString::fromLatin1("** Ordered, simplified actions (%1, was %2):") .arg(currentActionsCount).arg(allActionsCount); kdbDebug() << dbg; #ifdef KDB_DEBUG_GUI if (args->simulate) KDb::alterTableActionDebugGUI(dbg, 0); #endif for (int i = 0; i < allActionsCount; i++) { debugAction(actionsVector.at(i), 1, args->simulate, QString::fromLatin1("%1: ").arg(i + 1), args->debugString); } if (args->requirements == 0) {//nothing to do args->result = true; return oldTable; } if (args->simulate) {//do not execute args->result = true; return oldTable; } //! @todo transaction! // Create a new KDbTableSchema KDbTableSchema *newTable = recreateTable ? new KDbTableSchema(*oldTable, false/*!copy id*/) : oldTable; // find nonexisting temp name for new table schema if (recreateTable) { QString tempDestTableName = KDb::temporaryTableName(d->conn, newTable->name()); newTable->setName(tempDestTableName); } kdbDebug() << *oldTable; if (recreateTable && !args->debugString) { kdbDebug() << *newTable; } // Update table schema in memory ---- int lastUID = -1; - KDbField *currentField = 0; + KDbField *currentField = nullptr; QHash fieldHash; // a map from new value to old value foreach(KDbField* f, *newTable->fields()) { fieldHash.insert(f->name(), f->name()); } for (int i = 0; i < allActionsCount; i++) { action = actionsVector.at(i); if (!action) continue; //remember the current KDbField object because soon we may be unable to find it by name: FieldActionBase *fieldAction = dynamic_cast(action); if (!fieldAction) { - currentField = 0; + currentField = nullptr; } else { if (lastUID != fieldAction->uid()) { currentField = newTable->field(fieldAction->fieldName()); lastUID = currentField ? fieldAction->uid() : -1; } InsertFieldAction *insertFieldAction = dynamic_cast(action); if (insertFieldAction && insertFieldAction->index() > newTable->fieldCount()) { //update index: there can be empty rows insertFieldAction->setIndex(newTable->fieldCount()); } } args->result = action->updateTableSchema(newTable, currentField, &fieldHash); if (args->result != true) { if (recreateTable) delete newTable; - return 0; + return nullptr; } } if (recreateTable) { // Create the destination table with temporary name if (!d->conn->createTable(newTable, false)) { m_result = d->conn->result(); delete newTable; args->result = false; - return 0; + return nullptr; } } #if 0 //! @todo // Execute actions ---- for (int i = 0; i < allActionsCount; i++) { action = actionsVector.at(i); if (!action) continue; args.result = action->execute(*d->conn, *newTable); if (!args.result || ~args.result) { //! @todo delete newTable... args.result = false; return 0; } } #endif // update extended table schema after executing the actions if (!d->conn->storeExtendedTableSchemaData(newTable)) { //! @todo better errmsg? m_result = d->conn->result(); //! @todo delete newTable... args->result = false; - return 0; + return nullptr; } if (recreateTable) { // Copy the data: // Build "INSERT INTO ... SELECT FROM ..." SQL statement // The order is based on the order of the source table fields. // Notes: // -Some source fields can be skipped in case when there are deleted fields. // -Some destination fields can be skipped in case when there // are new empty fields without fixed/default value. KDbEscapedString sql = KDbEscapedString("INSERT INTO %1 (").arg(d->conn->escapeIdentifier(newTable->name())); //insert list of dest. fields bool first = true; KDbEscapedString sourceFields; foreach(KDbField* f, *newTable->fields()) { QString renamedFieldName(fieldHash.value(f->name())); KDbEscapedString sourceSQLString; const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive if (!renamedFieldName.isEmpty()) { //this field should be renamed sourceSQLString = KDbEscapedString(d->conn->escapeIdentifier(renamedFieldName)); } else if (!f->defaultValue().isNull()) { //this field has a default value defined //! @todo support expressions (eg. TODAY()) as a default value //! @todo this field can be notNull or notEmpty - check whether the default is ok //! (or do this checking also in the Table Designer?) sourceSQLString = d->conn->driver()->valueToSQL(type, f->defaultValue()); } else if (f->isNotNull()) { //this field cannot be null sourceSQLString = d->conn->driver()->valueToSQL( type, KDb::emptyValueForFieldType(type)); } else if (f->isNotEmpty()) { //this field cannot be empty - use any nonempty value..., e.g. " " for text or 0 for number sourceSQLString = d->conn->driver()->valueToSQL( type, KDb::notEmptyValueForFieldType(type)); } //! @todo support unique, validatationRule, unsigned flags... //! @todo check for foreignKey values... if (!sourceSQLString.isEmpty()) { if (first) { first = false; } else { sql.append(", "); sourceFields.append(", "); } sql += d->conn->escapeIdentifier(f->name()); sourceFields.append(sourceSQLString); } } sql += (") SELECT " + sourceFields + " FROM " + oldTable->name()); kdbDebug() << " ** " << sql; if (!d->conn->executeVoidSQL(sql)) { m_result = d->conn->result(); //! @todo delete newTable... args->result = false; - return 0; + return nullptr; } const QString oldTableName = oldTable->name(); /* args.result = d->conn->dropTable( oldTable ); if (!args.result || ~args.result) { setError(d->conn); //! @todo delete newTable... return 0; } oldTable = 0;*/ // Replace the old table with the new one (oldTable will be destroyed) if (!d->conn->alterTableName(newTable, oldTableName, true /*replace*/)) { m_result = d->conn->result(); //! @todo delete newTable... args->result = false; - return 0; + return nullptr; } - oldTable = 0; + oldTable = nullptr; } if (!recreateTable) { if ((MainSchemaAlteringRequired & args->requirements) && !fieldsWithChangedMainSchema.isEmpty()) { //update main schema (kexi__fields) for changed fields foreach(const QString& changeFieldPropertyActionName, fieldsWithChangedMainSchema) { KDbField *f = newTable->field(changeFieldPropertyActionName); if (f) { if (!d->conn->storeMainFieldSchema(f)) { m_result = d->conn->result(); //! @todo delete newTable... args->result = false; - return 0; + return nullptr; } } } } } args->result = true; return newTable; } /*KDbTableSchema* KDbAlterTableHandler::execute(const QString& tableName, tristate &result, bool simulate) { return executeInternal( tableName, result, simulate, 0 ); } tristate KDbAlterTableHandler::simulateExecution(const QString& tableName, QString& debugString) { tristate result; (void)executeInternal( tableName, result, true//simulate , &debugString ); return result; } */ diff --git a/src/KDbAlter.h b/src/KDbAlter.h index 8658200d..c2a729b4 100644 --- a/src/KDbAlter.h +++ b/src/KDbAlter.h @@ -1,525 +1,525 @@ /* This file is part of the KDE project Copyright (C) 2006-2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_ALTER_H #define KDB_ALTER_H #include "KDbUtils.h" #include "KDbResult.h" #include "KDbTristate.h" #include "KDbTableSchema.h" #include #include class KDbConnection; //! @short A tool for handling altering database table schema. /*! In relational (and other) databases, table schema altering is not an easy task. It may be considered as easy if there is no data that user wants to keep while the table schema is altered. Otherwise, if the table is alredy filled with data, there could be no easy algorithm like: 1. Drop existing table 2. Create new one with altered schema. Instead, more complex algorithm is needed. To perform the table schema alteration, a list of well defined atomic operations is used as a "recipe". 1. Look at the current data, and: 1.1. analyze what values will be removed (in case of impossible conversion or table field removal); 1.2. analyze what values can be converted (e.g. from numeric types to text), and so on. 2. Optimize the atomic actions knowing that sometimes a compilation of one action and another that's opposite to the first means "do nothing". The optimization is a simulating of actions' execution. For example, when both action A="change field name from 'city' to 'town'" and action B="change field name from 'town' to 'city'" is specified, the compilation of the actions means "change field name from 'city' to 'city'", what is a NULL action. On the other hand, we need to execute all the actions on the destination table in proper order, and not just drop them. For the mentioned example, between actions A and B there can be an action like C="change the type of field 'city' to LongText". If A and B were simply removed, C would become invalid (there is no 'city' field). 3. Ask user whether she agrees with the results of analysis mentioned in 1. 3.2. Additionally, it may be possible to get some hints from the user, as humans usually know more about logic behind the altered table schema than any machine. If the user provided hints about the altering, apply them to the actions list. 4. Create (empty) destination table schema with temporary name, using the information collected so far. 5. Copy the data from the source to destionation table. Convert values, move them between fields, using the information collected. 6. Remove the source table. 7. Rename the destination table to the name previously assigned for the source table. Notes: * The actions 4 to 7 should be performed within a database transaction. * [todo] We want to take care about database relationships as well. For example, is a table field is removed, relationships related to this field should be also removed (similar rules as in the Query Designer). * Especially, care about primary keys and uniquess (indices). Recreate them when needed. The problem could be if such analysis may require to fetch the entire table data to the client side. Use "SELECT INTO" statements if possible to avoid such a treat. The KDbAlterTableHandler is used in Kexi's Table Designer. Already opened KDbConnection object is needed. Use case: @code KDbConnection *conn = ... // add some actions (in reality this is performed by tracking user's actions) // Actions 1, 2 will require physical table altering PhysicalAltering // Action 3 will only require changes in kexi__fields // Action 4 will only require changes in extended table schema written in kexi__objectdata AlterTable::ActionList list; // 1. rename the "city" field to "town" list << new ChangeFieldPropertyAction("city", "name", "town") // 2. change type of "town" field to "LongText" << new ChangeFieldPropertyAction("town", "type", "LongText") // 3. set caption of "town" field to "Town" << new ChangeFieldPropertyAction("town", "caption", "Town") // 4. set visible decimal places to 4 for "cost" field << new ChangeFieldPropertyAction("cost", "visibleDecimalPlaces", 4) KDbAlterTableHandler::execute( *conn ); @endcode Actions for Alter */ class KDB_EXPORT KDbAlterTableHandler : public KDbResultable { public: class ChangeFieldPropertyAction; class RemoveFieldAction; class InsertFieldAction; class MoveFieldPositionAction; //! Defines flags for possible altering requirements; can be combined. enum AlteringRequirements { /*! Physical table altering is required; e.g. ALTER TABLE ADD COLUMN. */ PhysicalAlteringRequired = 1, /*! Data conversion is required; e.g. converting integer values to string after changing column type from integer to text. */ DataConversionRequired = 2, /*! Changes to the main table schema (in kexi__fields) required, this does not require physical changes for the table; e.g. changing value of the "caption" or "description" property. */ MainSchemaAlteringRequired = 4, /*! Only changes to extended table schema required, this does not require physical changes for the table; e.g. changing value of the "visibleDecimalPlaces" property or any of the custom properties. */ ExtendedSchemaAlteringRequired = 8, /*! Convenience flag, changes to the main or extended schema is required. */ SchemaAlteringRequired = ExtendedSchemaAlteringRequired | MainSchemaAlteringRequired }; class ActionBase; //! For collecting actions related to a single field typedef KDbUtils::AutodeletedHash ActionDict; typedef KDbUtils::AutodeletedHash ActionDictDict; //!< for collecting groups of actions by field UID typedef QHash::Iterator ActionDictIterator; typedef QHash::ConstIterator ActionDictConstIterator; typedef QHash::Iterator ActionDictDictIterator; typedef QHash::ConstIterator ActionDictDictConstIterator; typedef QVector ActionsVector; //!< for collecting actions related to a single field //! Defines a type for action list. typedef QList ActionList; //! Defines a type for action list's iterator. typedef QList::ConstIterator ActionListIterator; //! Abstract base class used for implementing all the AlterTable actions. class KDB_EXPORT ActionBase { public: virtual ~ActionBase(); ChangeFieldPropertyAction& toChangeFieldPropertyAction(); RemoveFieldAction& toRemoveFieldAction(); InsertFieldAction& toInsertFieldAction(); MoveFieldPositionAction& toMoveFieldPositionAction(); //! @return true if the action is NULL; used in the Table Designer //! for temporarily collecting actions that have no effect at all. inline bool isNull() const { return m_null; } //! Controls debug options for actions. Used in debugString() and debug(). class DebugOptions { public: inline DebugOptions() : showUID(true), showFieldDebug(false) {} //! true if UID should be added to the action debug string (the default) bool showUID; //! true if the field associated with the action (if exists) should //! be appended to the debug string (default is false) bool showFieldDebug; }; inline virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()) { Q_UNUSED(debugOptions); return QLatin1String("ActionBase"); } //! @todo add QDebug operator << void debug(const DebugOptions& debugOptions = DebugOptions()); protected: //! @internal, used for constructing null action explicit ActionBase(bool null); //! Sets requirements for altering; used internally by KDbAlterTableHandler object inline void setAlteringRequirements(int alteringRequirements) { m_alteringRequirements = alteringRequirements; } inline int alteringRequirements() const { return m_alteringRequirements; } inline virtual void updateAlteringRequirements() {} /*! Simplifies @a fieldActions dictionary. If this action has to be inserted Into the dictionary, an ActionDict is created first and then a copy of this action is inserted into it. */ inline virtual void simplifyActions(ActionDictDict *fieldActions) { Q_UNUSED(fieldActions); } /*! After calling simplifyActions() for each action, shouldBeRemoved() is called for them as an additional step. This is used for ChangeFieldPropertyAction items so actions that do not change property values are removed. */ inline virtual bool shouldBeRemoved(ActionDictDict *fieldActions) { Q_UNUSED(fieldActions); return false; } inline virtual tristate updateTableSchema(KDbTableSchema* table, KDbField* field, QHash* fieldHash) { Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(fieldHash); return true; } private: //! Performs physical execution of this action. inline virtual tristate execute(KDbConnection* /*conn*/, KDbTableSchema* /*table*/) { return true; } //! requirements for altering; used internally by KDbAlterTableHandler object int m_alteringRequirements; //! @internal used for "simplify" algorithm int m_order; const bool m_null; friend class KDbAlterTableHandler; }; //! Abstract base class used for implementing table field-related actions. class KDB_EXPORT FieldActionBase : public ActionBase { public: FieldActionBase(const QString& fieldName, int uid); - virtual ~FieldActionBase(); + ~FieldActionBase() override; //! @return field name for this action inline QString fieldName() const { return m_fieldName; } /*! @return field's unique identifier This id is needed because in the meantime there can be more than one field sharing the same name, so we need to identify them unambiguously. After the (valid) altering is completed all the names will be unique. Example scenario when user exchanged the field names: 1. At the beginning: [field A], [field B] 2. Rename the 1st field to B: [field B], [field B] 3. Rename the 2nd field to A: [field B], [field A] */ inline int uid() const { return m_fieldUID; } //! Sets field name for this action inline void setFieldName(const QString& fieldName) { m_fieldName = fieldName; } protected: //! @internal, used for constructing null action explicit FieldActionBase(bool null); //! field's unique identifier, @see uid() int m_fieldUID; private: QString m_fieldName; }; /*! Defines an action for changing a single property value of a table field. Supported properties are currently: "name", "type", "caption", "description", "unsigned", "maxLength", "precision", "defaultWidth", "defaultValue", "primaryKey", "unique", "notNull", "allowEmpty", "autoIncrement", "indexed", "visibleDecimalPlaces" More to come. */ class KDB_EXPORT ChangeFieldPropertyAction : public FieldActionBase { public: ChangeFieldPropertyAction(const QString& fieldName, const QString& propertyName, const QVariant& newValue, int uid); //! Creates null action ChangeFieldPropertyAction(); - virtual ~ChangeFieldPropertyAction(); + ~ChangeFieldPropertyAction() override; inline QString propertyName() const { return m_propertyName; } inline QVariant newValue() const { return m_newValue; } - virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + QString debugString(const DebugOptions& debugOptions = DebugOptions()) override; - virtual void simplifyActions(ActionDictDict *fieldActions); + void simplifyActions(ActionDictDict *fieldActions) override; - virtual bool shouldBeRemoved(ActionDictDict *fieldActions); + bool shouldBeRemoved(ActionDictDict *fieldActions) override; - virtual tristate updateTableSchema(KDbTableSchema* table, KDbField* field, - QHash* fieldHash); + tristate updateTableSchema(KDbTableSchema *table, KDbField *field, + QHash *fieldHash) override; protected: //! @internal, used for constructing null action explicit ChangeFieldPropertyAction(bool null); - virtual void updateAlteringRequirements(); + void updateAlteringRequirements() override; //! Performs physical execution of this action. - virtual tristate execute(KDbConnection* conn, KDbTableSchema* table); + tristate execute(KDbConnection* conn, KDbTableSchema* table) override; QString m_propertyName; QVariant m_newValue; }; //! Defines an action for removing a single table field. class KDB_EXPORT RemoveFieldAction : public FieldActionBase { public: RemoveFieldAction(const QString& fieldName, int uid); - virtual ~RemoveFieldAction(); + ~RemoveFieldAction() override; - virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + QString debugString(const DebugOptions& debugOptions = DebugOptions()) override; - virtual void simplifyActions(ActionDictDict *fieldActions); + void simplifyActions(ActionDictDict *fieldActions) override; - virtual tristate updateTableSchema(KDbTableSchema* table, KDbField* field, - QHash* fieldHash); + tristate updateTableSchema(KDbTableSchema *table, KDbField *field, + QHash *fieldHash) override; protected: //! @internal, used for constructing null action explicit RemoveFieldAction(bool null); - virtual void updateAlteringRequirements(); + void updateAlteringRequirements() override; //! Performs physical execution of this action. - virtual tristate execute(KDbConnection* conn, KDbTableSchema* table); + tristate execute(KDbConnection* conn, KDbTableSchema* table) override; }; //! Defines an action for inserting a single table field. class KDB_EXPORT InsertFieldAction : public FieldActionBase { public: InsertFieldAction(int fieldIndex, KDbField *newField, int uid); //! copy ctor InsertFieldAction(const InsertFieldAction& action); //! Creates null action InsertFieldAction(); - virtual ~InsertFieldAction(); + ~InsertFieldAction() override; inline int index() const { return m_index; } inline void setIndex(int index) { m_index = index; } inline const KDbField* field() const { return m_field; } void setField(KDbField* field); - virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + QString debugString(const DebugOptions& debugOptions = DebugOptions()) override; - virtual void simplifyActions(ActionDictDict *fieldActions); + void simplifyActions(ActionDictDict *fieldActions) override; - virtual tristate updateTableSchema(KDbTableSchema* table, KDbField* field, - QHash* fieldHash); + tristate updateTableSchema(KDbTableSchema *table, KDbField *field, + QHash *fieldHash) override; protected: //! @internal, used for constructing null action explicit InsertFieldAction(bool null); - virtual void updateAlteringRequirements(); + void updateAlteringRequirements() override; //! Performs physical execution of this action. - virtual tristate execute(KDbConnection* conn, KDbTableSchema* table); + tristate execute(KDbConnection* conn, KDbTableSchema* table) override; int m_index; private: KDbField *m_field; }; /*! Defines an action for moving a single table field to a different position within table schema. */ class KDB_EXPORT MoveFieldPositionAction : public FieldActionBase { public: MoveFieldPositionAction(int fieldIndex, const QString& fieldName, int uid); - virtual ~MoveFieldPositionAction(); + ~MoveFieldPositionAction() override; inline int index() const { return m_index; } - virtual QString debugString(const DebugOptions& debugOptions = DebugOptions()); + QString debugString(const DebugOptions& debugOptions = DebugOptions()) override; - virtual void simplifyActions(ActionDictDict *fieldActions); + void simplifyActions(ActionDictDict *fieldActions) override; protected: //! @internal, used for constructing null action explicit MoveFieldPositionAction(bool null); - virtual void updateAlteringRequirements(); + void updateAlteringRequirements() override; //! Performs physical execution of this action. - virtual tristate execute(KDbConnection* conn, KDbTableSchema* table); + tristate execute(KDbConnection* conn, KDbTableSchema* table) override; int m_index; }; explicit KDbAlterTableHandler(KDbConnection* conn); - virtual ~KDbAlterTableHandler(); + ~KDbAlterTableHandler() override; /*! Appends @a action for the alter table tool. */ void addAction(ActionBase* action); /*! Provided for convenience, @see addAction(const ActionBase& action). */ KDbAlterTableHandler& operator<< (ActionBase* action); /*! Removes an action from the alter table tool at index @a index. */ void removeAction(int index); /*! Removes all actions from the alter table tool. */ void clear(); /*! Sets @a actions for the alter table tool. Previous actions are cleared. @a actions will be owned by the KDbAlterTableHandler object. */ void setActions(const ActionList& actions); /*! @return a list of actions for this AlterTable object. Use ActionBase::ListIterator to iterate over the list items. */ const ActionList& actions() const; //! Arguments for KDbAlterTableHandler::execute(). class ExecutionArguments { public: inline ExecutionArguments() - : debugString(0) + : debugString(nullptr) , requirements(0) , result(false) , simulate(false) , onlyComputeRequirements(false) { } /*! If not 0, debug is directed here. Used only in the alter table test suite. */ QString* debugString; /*! Requrements computed, a combination of AlteringRequirements values. */ int requirements; /*! Set to true on success, to false on failure. */ tristate result; /*! Used only in the alter table test suite. */ bool simulate; /*! Set to true if requirements should be computed and the execute() method should return afterwards. */ bool onlyComputeRequirements; private: Q_DISABLE_COPY(ExecutionArguments) }; /*! Performs table alteration using predefined actions for table named @a tableName, assuming it already exists. The KDbConnection object passed to the constructor must exist, must be connected and a database must be used. The connection must not be read-only. If args.simulate is true, the execution is only simulated, i.e. al lactions are processed like for regular execution but no changes are performed physically. This mode is used only for debugging purposes. @todo For some cases, table schema can completely change, so it will be needed to refresh all objects depending on it. Implement this! Sets args.result to true on success, to false on failure or when the above requirements are not met (then, you can get a detailed error message from KDbObject). When the action has been cancelled (stopped), args.result is set to cancelled value. If args.debugString is not 0, it will be filled with debugging output. @return the new table schema object created as a result of schema altering. The old table is returned if recreating table schema was not necessary or args.simulate is true. 0 is returned if args.result is not true. */ KDbTableSchema* execute(const QString& tableName, ExecutionArguments* args); //! Displays debug information about all actions collected by the handler. void debug(); /*! Like execute() with simulate set to true, but debug is directed to debugString. This function is used only in the alter table test suite. */ // tristate simulateExecution(const QString& tableName, QString& debugString); /*! Helper. @return a combination of AlteringRequirements values decribing altering type required when a given property field's @a propertyName is altered. Used internally KDbAlterTableHandler. Moreover it can be also used in the Table Designer's code as a temporary replacement before KDbAlterTableHandler is fully implemented. Thus, it is possible to identify properties that have no PhysicalAlteringRequired flag set (e.g. caption or extended properties like visibleDecimalPlaces. */ static int alteringTypeForProperty(const QByteArray& propertyName); private: Q_DISABLE_COPY(KDbAlterTableHandler) class Private; Private * const d; }; #endif diff --git a/src/KDbConnection.cpp b/src/KDbConnection.cpp index 390a3f64..f37ec9e5 100644 --- a/src/KDbConnection.cpp +++ b/src/KDbConnection.cpp @@ -1,3425 +1,3425 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbConnection.h" #include "KDbConnection_p.h" #include "KDbCursor.h" #include "kdb_debug.h" #include "KDbDriverBehavior.h" #include "KDbDriverMetaData.h" #include "KDbDriver_p.h" #include "KDbLookupFieldSchema.h" #include "KDbNativeStatementBuilder.h" #include "KDbQuerySchema.h" #include "KDbRecordData.h" #include "KDbRecordEditBuffer.h" #include "KDbRelationship.h" #include "KDbSqlRecord.h" #include "KDbSqlResult.h" #include "KDbTableSchemaChangeListener.h" #include #include #include /*! Version number of extended table schema. List of changes: * 2: (Kexi 2.5.0) Added maxLengthIsDefault property (type: bool, if true, KDbField::maxLengthStrategy() == KDbField::DefaultMaxLength) * 1: (Kexi 1.x) Initial version */ #define KDB_EXTENDED_TABLE_SCHEMA_VERSION 2 KDbConnectionInternal::KDbConnectionInternal(KDbConnection *conn) : connection(conn) { } class CursorDeleter { public: explicit CursorDeleter(KDbCursor *cursor) { delete cursor; } }; //================================================ class Q_DECL_HIDDEN KDbConnectionOptions::Private { public: Private() : connection(nullptr) {} Private(const Private &other) { copy(other); } #define KDbConnectionOptionsPrivateArgs(o) std::tie(o.connection) void copy(const Private &other) { KDbConnectionOptionsPrivateArgs((*this)) = KDbConnectionOptionsPrivateArgs(other); } bool operator==(const Private &other) const { return KDbConnectionOptionsPrivateArgs((*this)) == KDbConnectionOptionsPrivateArgs(other); } KDbConnection *connection; }; KDbConnectionOptions::KDbConnectionOptions() : d(new Private) { KDbUtils::PropertySet::insert("readOnly", false, tr("Read only", "Read only connection")); } KDbConnectionOptions::KDbConnectionOptions(const KDbConnectionOptions &other) : KDbUtils::PropertySet(other) , d(new Private(*other.d)) { } KDbConnectionOptions::~KDbConnectionOptions() { delete d; } KDbConnectionOptions& KDbConnectionOptions::operator=(const KDbConnectionOptions &other) { if (this != &other) { KDbUtils::PropertySet::operator=(other); d->copy(*other.d); } return *this; } bool KDbConnectionOptions::operator==(const KDbConnectionOptions &other) const { return KDbUtils::PropertySet::operator==(other) && *d == *other.d; } bool KDbConnectionOptions::isReadOnly() const { return property("readOnly").value().toBool(); } void KDbConnectionOptions::insert(const QByteArray &name, const QVariant &value, const QString &caption) { if (name == "readOnly") { setReadOnly(value.toBool()); return; } QString realCaption; if (property(name).caption().isEmpty()) { // don't allow to change the caption realCaption = caption; } KDbUtils::PropertySet::insert(name, value, realCaption); } void KDbConnectionOptions::setCaption(const QByteArray &name, const QString &caption) { if (name == "readOnly") { return; } KDbUtils::PropertySet::setCaption(name, caption); } void KDbConnectionOptions::setValue(const QByteArray &name, const QVariant &value) { if (name == "readOnly") { setReadOnly(value.toBool()); return; } KDbUtils::PropertySet::setValue(name, value); } void KDbConnectionOptions::remove(const QByteArray &name) { if (name == "readOnly") { return; } KDbUtils::PropertySet::remove(name); } void KDbConnectionOptions::setReadOnly(bool set) { if (d->connection && d->connection->isConnected()) { return; //sanity } KDbUtils::PropertySet::setValue("readOnly", set); } void KDbConnectionOptions::setConnection(KDbConnection *connection) { d->connection = connection; } //================================================ KDbConnectionPrivate::KDbConnectionPrivate(KDbConnection* const conn, KDbDriver *drv, const KDbConnectionData& _connData, const KDbConnectionOptions &_options) : conn(conn) , connData(_connData) , options(_options) , driver(drv) , dbProperties(conn) { options.setConnection(conn); } KDbConnectionPrivate::~KDbConnectionPrivate() { options.setConnection(nullptr); deleteAllCursors(); delete m_parser; qDeleteAll(tableSchemaChangeListeners); qDeleteAll(obsoleteQueries); } void KDbConnectionPrivate::deleteAllCursors() { QSet cursorsToDelete(cursors); cursors.clear(); for(KDbCursor* c : cursorsToDelete) { CursorDeleter deleter(c); } } void KDbConnectionPrivate::errorInvalidDBContents(const QString& details) { conn->m_result = KDbResult(ERR_INVALID_DATABASE_CONTENTS, KDbConnection::tr("Invalid database contents. %1").arg(details)); } QString KDbConnectionPrivate::strItIsASystemObject() const { return KDbConnection::tr("It is a system object."); } void KDbConnectionPrivate::setupKDbSystemSchema() { if (!m_internalKDbTables.isEmpty()) { return; //already set up } { KDbInternalTableSchema *t_objects = new KDbInternalTableSchema(QLatin1String("kexi__objects")); t_objects->addField(new KDbField(QLatin1String("o_id"), KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); - t_objects->addField(new KDbField(QLatin1String("o_type"), KDbField::Byte, 0, KDbField::Unsigned)); + t_objects->addField(new KDbField(QLatin1String("o_type"), KDbField::Byte, nullptr, KDbField::Unsigned)); t_objects->addField(new KDbField(QLatin1String("o_name"), KDbField::Text)); t_objects->addField(new KDbField(QLatin1String("o_caption"), KDbField::Text)); t_objects->addField(new KDbField(QLatin1String("o_desc"), KDbField::LongText)); //kdbDebug() << *t_objects; insertTable(t_objects); } { KDbInternalTableSchema *t_objectdata = new KDbInternalTableSchema(QLatin1String("kexi__objectdata")); t_objectdata->addField(new KDbField(QLatin1String("o_id"), KDbField::Integer, KDbField::NotNull, KDbField::Unsigned)); t_objectdata->addField(new KDbField(QLatin1String("o_data"), KDbField::LongText)); t_objectdata->addField(new KDbField(QLatin1String("o_sub_id"), KDbField::Text)); insertTable(t_objectdata); } { KDbInternalTableSchema *t_fields = new KDbInternalTableSchema(QLatin1String("kexi__fields")); - t_fields->addField(new KDbField(QLatin1String("t_id"), KDbField::Integer, 0, KDbField::Unsigned)); - t_fields->addField(new KDbField(QLatin1String("f_type"), KDbField::Byte, 0, KDbField::Unsigned)); + t_fields->addField(new KDbField(QLatin1String("t_id"), KDbField::Integer, nullptr, KDbField::Unsigned)); + t_fields->addField(new KDbField(QLatin1String("f_type"), KDbField::Byte, nullptr, KDbField::Unsigned)); t_fields->addField(new KDbField(QLatin1String("f_name"), KDbField::Text)); t_fields->addField(new KDbField(QLatin1String("f_length"), KDbField::Integer)); t_fields->addField(new KDbField(QLatin1String("f_precision"), KDbField::Integer)); t_fields->addField(new KDbField(QLatin1String("f_constraints"), KDbField::Integer)); t_fields->addField(new KDbField(QLatin1String("f_options"), KDbField::Integer)); t_fields->addField(new KDbField(QLatin1String("f_default"), KDbField::Text)); //these are additional properties: t_fields->addField(new KDbField(QLatin1String("f_order"), KDbField::Integer)); t_fields->addField(new KDbField(QLatin1String("f_caption"), KDbField::Text)); t_fields->addField(new KDbField(QLatin1String("f_help"), KDbField::LongText)); insertTable(t_fields); } { KDbInternalTableSchema *t_db = new KDbInternalTableSchema(QLatin1String("kexi__db")); t_db->addField(new KDbField(QLatin1String("db_property"), KDbField::Text, KDbField::NoConstraints, KDbField::NoOptions, 32)); t_db->addField(new KDbField(QLatin1String("db_value"), KDbField::LongText)); insertTable(t_db); } } void KDbConnectionPrivate::insertTable(KDbTableSchema* tableSchema) { KDbInternalTableSchema* internalTable = dynamic_cast(tableSchema); if (internalTable) { m_internalKDbTables.insert(internalTable); } else { m_tables.insert(tableSchema->id(), tableSchema); } m_tablesByName.insert(tableSchema->name(), tableSchema); } void KDbConnectionPrivate::removeTable(const KDbTableSchema& tableSchema) { m_tablesByName.remove(tableSchema.name()); KDbTableSchema *toDelete = m_tables.take(tableSchema.id()); delete toDelete; } void KDbConnectionPrivate::takeTable(KDbTableSchema* tableSchema) { if (m_tables.isEmpty()) { return; } m_tables.take(tableSchema->id()); m_tablesByName.take(tableSchema->name()); } void KDbConnectionPrivate::renameTable(KDbTableSchema* tableSchema, const QString& newName) { m_tablesByName.take(tableSchema->name()); tableSchema->setName(newName); m_tablesByName.insert(tableSchema->name(), tableSchema); } void KDbConnectionPrivate::changeTableId(KDbTableSchema* tableSchema, int newId) { m_tables.take(tableSchema->id()); m_tables.insert(newId, tableSchema); } void KDbConnectionPrivate::clearTables() { m_tablesByName.clear(); qDeleteAll(m_internalKDbTables); m_internalKDbTables.clear(); QHash tablesToDelete(m_tables); m_tables.clear(); qDeleteAll(tablesToDelete); } void KDbConnectionPrivate::insertQuery(KDbQuerySchema* query) { m_queries.insert(query->id(), query); m_queriesByName.insert(query->name(), query); } void KDbConnectionPrivate::removeQuery(KDbQuerySchema* querySchema) { m_queriesByName.remove(querySchema->name()); m_queries.remove(querySchema->id()); delete querySchema; } void KDbConnectionPrivate::setQueryObsolete(KDbQuerySchema* query) { obsoleteQueries.insert(query); m_queriesByName.take(query->name()); m_queries.take(query->id()); } void KDbConnectionPrivate::clearQueries() { qDeleteAll(m_queries); m_queries.clear(); } //================================================ namespace { //! @internal static: list of internal KDb system table names class SystemTables : public QStringList { public: SystemTables() : QStringList({ QLatin1String("kexi__objects"), QLatin1String("kexi__objectdata"), QLatin1String("kexi__fields"), QLatin1String("kexi__db")}) {} }; } Q_GLOBAL_STATIC(SystemTables, g_kdbSystemTableNames) KDbConnection::KDbConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options) : d(new KDbConnectionPrivate(this, driver, connData, options)) { if (d->connData.driverId().isEmpty()) { d->connData.setDriverId(d->driver->metaData()->id()); } } void KDbConnection::destroy() { disconnect(); //do not allow the driver to touch me: I will kill myself. d->driver->d->connections.remove(this); } KDbConnection::~KDbConnection() { KDbConnectionPrivate *thisD = d; d = nullptr; // make sure d is nullptr before destructing delete thisD; } KDbConnectionData KDbConnection::data() const { return d->connData; } KDbDriver* KDbConnection::driver() const { return d->driver; } bool KDbConnection::connect() { clearResult(); if (d->isConnected) { m_result = KDbResult(ERR_ALREADY_CONNECTED, tr("Connection already established.")); return false; } d->serverVersion.clear(); if (!(d->isConnected = drv_connect())) { if (m_result.code() == ERR_NONE) { m_result.setCode(ERR_OTHER); } m_result.setMessage(d->driver->metaData()->isFileBased() ? tr("Could not open \"%1\" project file.") .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName())) : tr("Could not connect to \"%1\" database server.") .arg(d->connData.toUserVisibleString())); } if (d->isConnected && !d->driver->beh->USING_DATABASE_REQUIRED_TO_CONNECT) { if (!drv_getServerVersion(&d->serverVersion)) return false; } return d->isConnected; } bool KDbConnection::isDatabaseUsed() const { return !d->usedDatabase.isEmpty() && d->isConnected && drv_isDatabaseUsed(); } void KDbConnection::clearResult() { KDbResultable::clearResult(); } bool KDbConnection::disconnect() { clearResult(); if (!d->isConnected) return true; if (!closeDatabase()) return false; bool ok = drv_disconnect(); if (ok) d->isConnected = false; return ok; } bool KDbConnection::isConnected() const { return d->isConnected; } bool KDbConnection::checkConnected() { if (d->isConnected) { clearResult(); return true; } m_result = KDbResult(ERR_NO_CONNECTION, tr("Not connected to the database server.")); return false; } bool KDbConnection::checkIsDatabaseUsed() { if (isDatabaseUsed()) { clearResult(); return true; } m_result = KDbResult(ERR_NO_DB_USED, tr("Currently no database is used.")); return false; } QStringList KDbConnection::databaseNames(bool also_system_db) { //kdbDebug() << also_system_db; if (!checkConnected()) return QStringList(); QString tmpdbName; //some engines need to have opened any database before executing "create database" if (!useTemporaryDatabaseIfNeeded(&tmpdbName)) return QStringList(); QStringList list; bool ret = drv_getDatabasesList(&list); if (!tmpdbName.isEmpty()) { //whatever result is - now we have to close temporary opened database: if (!closeDatabase()) return QStringList(); } if (!ret) return QStringList(); if (also_system_db) return list; //filter system databases: for (QMutableListIterator it(list); it.hasNext();) { if (d->driver->isSystemDatabaseName(it.next())) { it.remove(); } } return list; } bool KDbConnection::drv_getDatabasesList(QStringList* list) { list->clear(); return true; } bool KDbConnection::drv_databaseExists(const QString &dbName, bool ignoreErrors) { QStringList list = databaseNames(true);//also system if (m_result.isError()) { return false; } if (list.indexOf(dbName) == -1) { if (!ignoreErrors) m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("The database \"%1\" does not exist.").arg(dbName)); return false; } return true; } bool KDbConnection::databaseExists(const QString &dbName, bool ignoreErrors) { // kdbDebug() << dbName << ignoreErrors; if (d->driver->beh->CONNECTION_REQUIRED_TO_CHECK_DB_EXISTENCE && !checkConnected()) return false; clearResult(); if (d->driver->metaData()->isFileBased()) { //for file-based db: file must exists and be accessible QFileInfo file(d->connData.databaseName()); if (!file.exists() || (!file.isFile() && !file.isSymLink())) { if (!ignoreErrors) m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("The database file \"%1\" does not exist.") .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName()))); return false; } if (!file.isReadable()) { if (!ignoreErrors) m_result = KDbResult(ERR_ACCESS_RIGHTS, tr("Database file \"%1\" is not readable.") .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName()))); return false; } if (!d->options.isReadOnly() && !file.isWritable()) { if (!ignoreErrors) m_result = KDbResult(ERR_ACCESS_RIGHTS, tr("Database file \"%1\" is not writable.") .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName()))); return false; } return true; } QString tmpdbName; //some engines need to have opened any database before executing "create database" const bool orig_skipDatabaseExistsCheckInUseDatabase = d->skipDatabaseExistsCheckInUseDatabase; d->skipDatabaseExistsCheckInUseDatabase = true; bool ret = useTemporaryDatabaseIfNeeded(&tmpdbName); d->skipDatabaseExistsCheckInUseDatabase = orig_skipDatabaseExistsCheckInUseDatabase; if (!ret) return false; ret = drv_databaseExists(dbName, ignoreErrors); if (!tmpdbName.isEmpty()) { //whatever result is - now we have to close temporary opened database: if (!closeDatabase()) return false; } return ret; } #define createDatabase_CLOSE \ { if (!closeDatabase()) { \ m_result = KDbResult(KDbConnection::tr("Database \"%1\" has been created but " \ "could not be closed after creation.").arg(dbName)); \ return false; \ } } #define createDatabase_ERROR \ { createDatabase_CLOSE; return false; } bool KDbConnection::createDatabase(const QString &dbName) { if (d->driver->beh->CONNECTION_REQUIRED_TO_CREATE_DB && !checkConnected()) return false; if (databaseExists(dbName)) { m_result = KDbResult(ERR_OBJECT_EXISTS, tr("Database \"%1\" already exists.").arg(dbName)); return false; } if (d->driver->isSystemDatabaseName(dbName)) { m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED, tr("Could not create database \"%1\". This name is reserved for system database.").arg(dbName)); return false; } if (d->driver->metaData()->isFileBased()) { //update connection data if filename differs if (QFileInfo(dbName).isAbsolute()) { d->connData.setDatabaseName(dbName); } else { d->connData.setDatabaseName( QFileInfo(d->connData.databaseName()).absolutePath() + QDir::separator() + QFileInfo(dbName).fileName()); } } QString tmpdbName; //some engines need to have opened any database before executing "create database" if (!useTemporaryDatabaseIfNeeded(&tmpdbName)) return false; //low-level create if (!drv_createDatabase(dbName)) { m_result.prependMessage(tr("Error creating database \"%1\" on the server.").arg(dbName)); (void)closeDatabase();//sanity return false; } if (!tmpdbName.isEmpty()) { //whatever result is - now we have to close temporary opened database: if (!closeDatabase()) return false; } if (!tmpdbName.isEmpty() || !d->driver->beh->IS_DB_OPEN_AFTER_CREATE) { //db need to be opened if (!useDatabase(dbName, false/*not yet kexi compatible!*/)) { m_result = KDbResult(tr("Database \"%1\" has been created but could not be opened.").arg(dbName)); return false; } } else { //just for the rule d->usedDatabase = dbName; d->isConnected = true; } KDbTransaction trans; if (d->driver->transactionsSupported()) { trans = beginTransaction(); if (!trans.active()) return false; } //-create system tables schema objects d->setupKDbSystemSchema(); //-physically create internal KDb tables foreach(KDbInternalTableSchema* t, d->internalKDbTables()) { if (!drv_createTable(*t)) createDatabase_ERROR; } //-insert KDb version info: // (for compatibility with Kexi expect the legacy kexidb_major_ver/kexidb_minor_ver values) KDbTableSchema *table = d->table(QLatin1String("kexi__db")); if (!table) createDatabase_ERROR; if (!insertRecord(table, QLatin1String("kexidb_major_ver"), KDb::version().major()) || !insertRecord(table, QLatin1String("kexidb_minor_ver"), KDb::version().minor())) createDatabase_ERROR; if (trans.active() && !commitTransaction(trans)) createDatabase_ERROR; createDatabase_CLOSE; return true; } #undef createDatabase_CLOSE #undef createDatabase_ERROR bool KDbConnection::useDatabase(const QString &dbName, bool kexiCompatible, bool *cancelled, KDbMessageHandler* msgHandler) { if (cancelled) *cancelled = false; //kdbDebug() << dbName << kexiCompatible; if (!checkConnected()) return false; QString my_dbName; if (dbName.isEmpty()) my_dbName = d->connData.databaseName(); else my_dbName = dbName; if (my_dbName.isEmpty()) return false; if (d->usedDatabase == my_dbName) return true; //already used if (!d->skipDatabaseExistsCheckInUseDatabase) { if (!databaseExists(my_dbName, false /*don't ignore errors*/)) return false; //database must exist } if (!d->usedDatabase.isEmpty() && !closeDatabase()) //close db if already used return false; d->usedDatabase.clear(); if (!drv_useDatabase(my_dbName, cancelled, msgHandler)) { if (cancelled && *cancelled) return false; QString msg(tr("Opening database \"%1\" failed.").arg(my_dbName)); m_result.prependMessage(msg); return false; } if (d->serverVersion.isNull() && d->driver->beh->USING_DATABASE_REQUIRED_TO_CONNECT) { // get version just now, it was not possible earlier if (!drv_getServerVersion(&d->serverVersion)) return false; } //-create system tables schema objects d->setupKDbSystemSchema(); if (kexiCompatible && my_dbName.compare(anyAvailableDatabaseName(), Qt::CaseInsensitive) != 0) { //-get global database information bool ok; const int major = d->dbProperties.value(QLatin1String("kexidb_major_ver")).toInt(&ok); if (!ok) { m_result = d->dbProperties.result(); return false; } const int minor = d->dbProperties.value(QLatin1String("kexidb_minor_ver")).toInt(&ok); if (!ok) { m_result = d->dbProperties.result(); return false; } d->databaseVersion.setMajor(major); d->databaseVersion.setMinor(minor); } d->usedDatabase = my_dbName; return true; } bool KDbConnection::closeDatabase() { if (d->usedDatabase.isEmpty()) return true; //no db used if (!checkConnected()) return true; bool ret = true; /*! @todo (js) add CLEVER algorithm here for nested transactions */ if (d->driver->transactionsSupported()) { //rollback all transactions d->dontRemoveTransactions = true; //lock! foreach(const KDbTransaction& tr, d->transactions) { if (!rollbackTransaction(tr)) {//rollback as much as you can, don't stop on prev. errors ret = false; } else { kdbDebug() << "transaction rolled back!"; kdbDebug() << "trans.refcount==" << (tr.m_data ? QString::number(tr.m_data->refcount) : QLatin1String("(null)")); } } d->dontRemoveTransactions = false; //unlock! d->transactions.clear(); //free trans. data } //delete own cursors: d->deleteAllCursors(); //delete own schemas d->clearTables(); d->clearQueries(); if (!drv_closeDatabase()) return false; d->usedDatabase.clear(); return ret; } QString KDbConnection::currentDatabase() const { return d->usedDatabase; } bool KDbConnection::useTemporaryDatabaseIfNeeded(QString* name) { if (d->driver->beh->USE_TEMPORARY_DATABASE_FOR_CONNECTION_IF_NEEDED && !isDatabaseUsed()) { //we have no db used, but it is required by engine to have used any! *name = anyAvailableDatabaseName(); if (name->isEmpty()) { m_result = KDbResult(ERR_NO_DB_USED, tr("Could not find any database for temporary connection.")); return false; } const bool orig_skipDatabaseExistsCheckInUseDatabase = d->skipDatabaseExistsCheckInUseDatabase; d->skipDatabaseExistsCheckInUseDatabase = true; bool ret = useDatabase(*name, false); d->skipDatabaseExistsCheckInUseDatabase = orig_skipDatabaseExistsCheckInUseDatabase; if (!ret) { m_result = KDbResult(m_result.code(), tr("Error during starting temporary connection using \"%1\" database name.").arg(*name)); return false; } } return true; } bool KDbConnection::dropDatabase(const QString &dbName) { if (d->driver->beh->CONNECTION_REQUIRED_TO_DROP_DB && !checkConnected()) return false; QString dbToDrop; if (dbName.isEmpty() && d->usedDatabase.isEmpty()) { if (!d->driver->metaData()->isFileBased() || (d->driver->metaData()->isFileBased() && d->connData.databaseName().isEmpty())) { m_result = KDbResult(ERR_NO_NAME_SPECIFIED, tr("Could not delete database. Name is not specified.")); return false; } //this is a file driver so reuse previously passed filename dbToDrop = d->connData.databaseName(); } else { if (dbName.isEmpty()) { dbToDrop = d->usedDatabase; } else { if (d->driver->metaData()->isFileBased()) //lets get full path dbToDrop = QFileInfo(dbName).absoluteFilePath(); else dbToDrop = dbName; } } if (dbToDrop.isEmpty()) { m_result = KDbResult(ERR_NO_NAME_SPECIFIED, tr("Could not delete database. Name is not specified.")); return false; } if (d->driver->isSystemDatabaseName(dbToDrop)) { m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED, tr("Could not delete system database \"%1\".").arg(dbToDrop)); return false; } if (isDatabaseUsed() && d->usedDatabase == dbToDrop) { //we need to close database because cannot drop used this database if (!closeDatabase()) return false; } QString tmpdbName; //some engines need to have opened any database before executing "drop database" if (!useTemporaryDatabaseIfNeeded(&tmpdbName)) return false; //ok, now we have access to dropping bool ret = drv_dropDatabase(dbToDrop); if (!tmpdbName.isEmpty()) { //whatever result is - now we have to close temporary opened database: if (!closeDatabase()) return false; } return ret; } QStringList KDbConnection::objectNames(int objectType, bool* ok) { if (!checkIsDatabaseUsed()) { if (ok) { *ok = false; } return QStringList(); } KDbEscapedString sql; if (objectType == KDb::AnyObjectType) { sql = "SELECT o_name FROM kexi__objects ORDER BY o_id"; } else { sql = KDbEscapedString("SELECT o_name FROM kexi__objects WHERE o_type=%1" " ORDER BY o_id").arg(d->driver->valueToSQL(KDbField::Integer, objectType)); } QStringList list; - const bool success = queryStringListInternal(&sql, &list, 0, 0, 0, KDb::isIdentifier); + const bool success = queryStringListInternal(&sql, &list, nullptr, nullptr, 0, KDb::isIdentifier); if (ok) { *ok = success; } if (!success) { m_result.prependMessage(tr("Could not retrieve list of object names.")); } return list; } QStringList KDbConnection::tableNames(bool alsoSystemTables, bool* ok) { bool success; QStringList list = objectNames(KDb::TableObjectType, &success); if (ok) { *ok = success; } if (!success) { m_result.prependMessage(tr("Could not retrieve list of table names.")); } if (alsoSystemTables && success) { list += kdbSystemTableNames(); } return list; } tristate KDbConnection::containsTable(const QString &tableName) { return drv_containsTable(tableName); } QStringList KDbConnection::kdbSystemTableNames() { return *g_kdbSystemTableNames; } KDbServerVersionInfo KDbConnection::serverVersion() const { return isConnected() ? d->serverVersion : KDbServerVersionInfo(); } KDbVersionInfo KDbConnection::databaseVersion() const { return isDatabaseUsed() ? d->databaseVersion : KDbVersionInfo(); } KDbProperties KDbConnection::databaseProperties() const { return d->dbProperties; } QList KDbConnection::tableIds(bool* ok) { return objectIds(KDb::TableObjectType, ok); } QList KDbConnection::queryIds(bool* ok) { return objectIds(KDb::QueryObjectType, ok); } QList KDbConnection::objectIds(int objectType, bool* ok) { if (!checkIsDatabaseUsed()) return QList(); KDbEscapedString sql; if (objectType == KDb::AnyObjectType) sql = "SELECT o_id, o_name FROM kexi__objects ORDER BY o_id"; else sql = "SELECT o_id, o_name FROM kexi__objects WHERE o_type=" + QByteArray::number(objectType) + " ORDER BY o_id"; KDbCursor *c = executeQuery(sql); if (!c) { if (ok) { *ok = false; } m_result.prependMessage(tr("Could not retrieve list of object identifiers.")); return QList(); } QList list; for (c->moveFirst(); !c->eof(); c->moveNext()) { QString tname = c->value(1).toString(); //kexi__objects.o_name if (KDb::isIdentifier(tname)) { list.append(c->value(0).toInt()); //kexi__objects.o_id } } deleteCursor(c); if (ok) { *ok = true; } return list; } //yeah, it is very efficient: #define C_A(a) , const QVariant& c ## a #define V_A0 d->driver->valueToSQL( tableSchema->field(0), c0 ) #define V_A(a) + ',' + d->driver->valueToSQL( \ tableSchema->field(a) ? tableSchema->field(a)->type() : KDbField::Text, c ## a ) // kdbDebug() << "******** " << QString("INSERT INTO ") + // escapeIdentifier(tableSchema->name()) + // " VALUES (" + vals + ")"; bool KDbConnection::insertRecordInternal(const QString &tableSchemaName, KDbFieldList* fields, - const KDbEscapedString &sql, KDbSqlResult** result = 0) + const KDbEscapedString &sql, KDbSqlResult** result = nullptr) { if (!drv_beforeInsert(tableSchemaName,fields )) { return false; } QScopedPointer res(executeSQL(sql)); if (!res || res->lastResult().isError()) { return false; } if (!drv_afterInsert(tableSchemaName, fields)) { return false; } { // Fetching is needed to performs real execution, at least for some backends. // Also we're not expecting record but let's delete if there's any. QScopedPointer record(res->fetchRecord()); } if (!res->lastResult().isError()) { if (result) { *result = res.take(); } return true; } return false; } #define C_INS_REC(args, vals) \ bool KDbConnection::insertRecord(KDbTableSchema* tableSchema args, KDbSqlResult** result) {\ return insertRecordInternal(tableSchema->name(), tableSchema, \ KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableSchema->name()) \ + " (" \ + tableSchema->sqlFieldsList(this) \ + ") VALUES (" + vals + ")", \ result); \ } #define C_INS_REC_ALL \ C_INS_REC( C_A(0), V_A0 ) \ C_INS_REC( C_A(0) C_A(1), V_A0 V_A(1) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2), V_A0 V_A(1) V_A(2) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3), V_A0 V_A(1) V_A(2) V_A(3) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) ) \ C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6) C_A(7), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) V_A(7) ) C_INS_REC_ALL #undef V_A0 #undef V_A #undef C_INS_REC #define V_A0 value += d->driver->valueToSQL( it.next(), c0 ); #define V_A( a ) value += (',' + d->driver->valueToSQL( it.next(), c ## a )); #define C_INS_REC(args, vals) \ bool KDbConnection::insertRecord(KDbFieldList* fields args, KDbSqlResult** result) \ { \ KDbEscapedString value; \ const KDbField::List *flist = fields->fields(); \ QListIterator it(*flist); \ vals \ it.toFront(); \ QString tableName((it.hasNext() && it.peekNext()->table()) ? it.next()->table()->name() : QLatin1String("??")); \ return insertRecordInternal(tableName, fields, \ KDbEscapedString(QLatin1String("INSERT INTO ") + escapeIdentifier(tableName)) \ + " (" + fields->sqlFieldsList(this) \ + ") VALUES (" + value + ')', \ result); \ } C_INS_REC_ALL #undef C_A #undef V_A #undef V_ALAST #undef C_INS_REC #undef C_INS_REC_ALL bool KDbConnection::insertRecord(KDbTableSchema* tableSchema, const QList& values, KDbSqlResult** result) { // Each SQL identifier needs to be escaped in the generated query. const KDbField::List *flist = tableSchema->fields(); if (flist->isEmpty()) { return false; } KDbField::ListIterator fieldsIt(flist->constBegin()); QList::ConstIterator it = values.constBegin(); KDbEscapedString sql; sql.reserve(4096); while (fieldsIt != flist->constEnd() && (it != values.end())) { KDbField *f = *fieldsIt; if (sql.isEmpty()) { sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableSchema->name()) + " VALUES ("; } else { sql += ','; } sql += d->driver->valueToSQL(f, *it); // kdbDebug() << "val" << i++ << ": " << d->driver->valueToSQL( f, *it ); ++it; ++fieldsIt; } sql += ')'; m_result.setSql(sql); return insertRecordInternal(tableSchema->name(), tableSchema, sql, result); } bool KDbConnection::insertRecord(KDbFieldList* fields, const QList& values, KDbSqlResult** result) { // Each SQL identifier needs to be escaped in the generated query. const KDbField::List *flist = fields->fields(); if (flist->isEmpty()) { return false; } KDbField::ListIterator fieldsIt(flist->constBegin()); KDbEscapedString sql; sql.reserve(4096); QList::ConstIterator it = values.constBegin(); const QString tableName(flist->first()->table()->name()); while (fieldsIt != flist->constEnd() && it != values.constEnd()) { KDbField *f = *fieldsIt; if (sql.isEmpty()) { sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableName) + '(' + fields->sqlFieldsList(this) + ") VALUES ("; } else { sql += ','; } sql += d->driver->valueToSQL(f, *it); // kdbDebug() << "val" << i++ << ": " << d->driver->valueToSQL( f, *it ); ++it; ++fieldsIt; if (fieldsIt == flist->constEnd()) break; } sql += ')'; m_result.setSql(sql); return insertRecordInternal(tableName, fields, sql, result); } inline static bool checkSql(const KDbEscapedString& sql, KDbResult* result) { Q_ASSERT(result); if (!sql.isValid()) { *result = KDbResult(ERR_SQL_EXECUTION_ERROR, KDbConnection::tr("SQL statement for execution is invalid or empty.")); result->setErrorSql(sql); //remember for error handling return false; } return true; } KDbSqlResult* KDbConnection::executeSQL(const KDbEscapedString& sql) { m_result.setSql(sql); return drv_executeSQL(sql); } bool KDbConnection::executeVoidSQL(const KDbEscapedString& sql) { m_result.setSql(sql); if (!checkSql(sql, &m_result)) { return false; } if (!drv_executeVoidSQL(sql)) { m_result.setMessage(QString()); //clear as this could be most probably just "Unknown error" string. m_result.setErrorSql(sql); m_result.prependMessage(ERR_SQL_EXECUTION_ERROR, tr("Error while executing SQL statement.")); qWarning() << m_result; return false; } return true; } KDbField* KDbConnection::findSystemFieldName(const KDbFieldList& fieldlist) { for (KDbField::ListIterator it(fieldlist.fieldsIterator()); it != fieldlist.fieldsIteratorConstEnd(); ++it) { if (d->driver->isSystemFieldName((*it)->name())) return *it; } - return 0; + return nullptr; } //! Creates a KDbField list for kexi__fields, for sanity. Used by createTable() static KDbFieldList* createFieldListForKexi__Fields(KDbTableSchema *kexi__fieldsSchema) { if (!kexi__fieldsSchema) - return 0; + return nullptr; return kexi__fieldsSchema->subList( QList() << "t_id" << "f_type" << "f_name" << "f_length" << "f_precision" << "f_constraints" << "f_options" << "f_default" << "f_order" << "f_caption" << "f_help" ); } static QVariant buildLengthValue(const KDbField &f) { if (f.isFPNumericType()) { return f.scale(); } return f.maxLength(); } //! builds a list of values for field's @a f properties. Used by createTable(). static void buildValuesForKexi__Fields(QList& vals, KDbField* f) { const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive vals.clear(); vals << QVariant(f->table()->id()) << QVariant(type) << QVariant(f->name()) << buildLengthValue(*f) << QVariant(KDbField::isFPNumericType(type) ? f->precision() : 0) << QVariant(f->constraints()) << QVariant(f->options()) // KDb::variantToString() is needed here because the value can be of any QVariant type, // depending on f->type() << (f->defaultValue().isNull() ? QVariant() : QVariant(KDb::variantToString(f->defaultValue()))) << QVariant(f->order()) << QVariant(f->caption()) << QVariant(f->description()); } bool KDbConnection::storeMainFieldSchema(KDbField *field) { if (!field || !field->table()) return false; KDbFieldList *fl = createFieldListForKexi__Fields(d->table(QLatin1String("kexi__fields"))); if (!fl) return false; QList vals; buildValuesForKexi__Fields(vals, field); QList::ConstIterator valsIt = vals.constBegin(); bool first = true; KDbEscapedString sql("UPDATE kexi__fields SET "); foreach(KDbField *f, *fl->fields()) { sql.append((first ? QString() : QLatin1String(", ")) + f->name() + QLatin1Char('=') + d->driver->valueToSQL(f, *valsIt)); if (first) first = false; ++valsIt; } delete fl; sql.append(KDbEscapedString(" WHERE t_id=%1 AND f_name=%2") .arg(d->driver->valueToSQL(KDbField::Integer, field->table()->id())) .arg(escapeString(field->name()))); return executeVoidSQL(sql); } #define createTable_ERR \ { kdbDebug() << "ERROR!"; \ m_result.prependMessage(KDbConnection::tr("Creating table failed.")); \ rollbackAutoCommitTransaction(tg.transaction()); \ return false; } bool KDbConnection::createTable(KDbTableSchema* tableSchema, bool replaceExisting) { if (!tableSchema || !checkIsDatabaseUsed()) return false; //check if there are any fields if (tableSchema->fieldCount() < 1) { clearResult(); m_result = KDbResult(ERR_CANNOT_CREATE_EMPTY_OBJECT, tr("Could not create table without fields.")); return false; } KDbInternalTableSchema* internalTable = dynamic_cast(tableSchema); const QString tableName(tableSchema->name()); if (!internalTable) { if (d->driver->isSystemObjectName(tableName)) { clearResult(); m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED, tr("System name \"%1\" cannot be used as table name.") .arg(tableSchema->name())); return false; } KDbField *sys_field = findSystemFieldName(*tableSchema); if (sys_field) { clearResult(); m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED, tr("System name \"%1\" cannot be used as one of fields in \"%2\" table.") .arg(sys_field->name(), tableName)); return false; } } bool previousSchemaStillKept = false; - KDbTableSchema *existingTable = 0; + KDbTableSchema *existingTable = nullptr; if (replaceExisting) { //get previous table (do not retrieve, though) existingTable = this->tableSchema(tableName); if (existingTable) { if (existingTable == tableSchema) { clearResult(); m_result = KDbResult(ERR_OBJECT_EXISTS, tr("Could not create the same table \"%1\" twice.").arg(tableSchema->name())); return false; } //! @todo (js) update any structure (e.g. queries) that depend on this table! if (existingTable->id() > 0) tableSchema->setId(existingTable->id()); //copy id from existing table previousSchemaStillKept = true; if (!dropTable(existingTable, false /*alsoRemoveSchema*/)) return false; } } else { - if (this->tableSchema(tableSchema->name()) != 0) { + if (this->tableSchema(tableSchema->name()) != nullptr) { clearResult(); m_result = KDbResult(ERR_OBJECT_EXISTS, tr("Table \"%1\" already exists.").arg(tableSchema->name())); return false; } } KDbTransactionGuard tg; if (!beginAutoCommitTransaction(&tg)) return false; if (internalTable) { if (!drv_containsTable(internalTable->name())) { // internal table may exist if (!drv_createTable(*tableSchema)) { createTable_ERR; } } } else { if (!drv_createTable(*tableSchema)) { createTable_ERR; } } //add the object data to kexi__* tables if (!internalTable) { //update kexi__objects if (!storeNewObjectData(tableSchema)) createTable_ERR; KDbTableSchema *ts = d->table(QLatin1String("kexi__fields")); if (!ts) return false; //for sanity: remove field info (if any) for this table id if (!KDb::deleteRecords(this, *ts, QLatin1String("t_id"), tableSchema->id())) return false; KDbFieldList *fl = createFieldListForKexi__Fields(ts); if (!fl) return false; foreach(KDbField *f, *tableSchema->fields()) { QList vals; buildValuesForKexi__Fields(vals, f); if (!insertRecord(fl, vals)) createTable_ERR; } delete fl; if (!storeExtendedTableSchemaData(tableSchema)) createTable_ERR; } bool res = commitAutoCommitTransaction(tg.transaction()); if (res) { if (!internalTable) { if (previousSchemaStillKept) { //remove previous table schema d->removeTable(*tableSchema); } } //store locally d->insertTable(tableSchema); //ok, this table is not created by the connection tableSchema->setConnection(this); } return res; } KDbTableSchema *KDbConnection::copyTable(const KDbTableSchema &tableSchema, const KDbObject &newData) { clearResult(); if (this->tableSchema(tableSchema.name()) != &tableSchema) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Table \"%1\" does not exist.").arg(tableSchema.name())); - return 0; + return nullptr; } KDbTableSchema *copiedTable = new KDbTableSchema(tableSchema, false /* !copyId*/); // copy name, caption, description copiedTable->setName(newData.name()); copiedTable->setCaption(newData.caption()); copiedTable->setDescription(newData.description()); // copy the structure and data if (!createTable(copiedTable, false /* !replaceExisting */)) { delete copiedTable; - return 0; + return nullptr; } if (!drv_copyTableData(tableSchema, *copiedTable)) { dropTable(copiedTable); delete copiedTable; - return 0; + return nullptr; } return copiedTable; } KDbTableSchema *KDbConnection::copyTable(const QString &tableName, const KDbObject &newData) { clearResult(); KDbTableSchema* ts = tableSchema(tableName); if (!ts) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Table \"%1\" does not exist.").arg(tableName)); - return 0; + return nullptr; } return copyTable(*ts, newData); } bool KDbConnection::drv_copyTableData(const KDbTableSchema &tableSchema, const KDbTableSchema &destinationTableSchema) { KDbEscapedString sql = KDbEscapedString("INSERT INTO %1 SELECT * FROM %2") .arg(escapeIdentifier(destinationTableSchema.name())) .arg(escapeIdentifier(tableSchema.name())); return executeVoidSQL(sql); } bool KDbConnection::removeObject(int objId) { clearResult(); //remove table schema from kexi__* tables KDbTableSchema *kexi__objects = d->table(QLatin1String("kexi__objects")); KDbTableSchema *kexi__objectdata = d->table(QLatin1String("kexi__objectdata")); if (!kexi__objects || !kexi__objectdata || !KDb::deleteRecords(this, *kexi__objects, QLatin1String("o_id"), objId) //schema entry || !KDb::deleteRecords(this, *kexi__objectdata, QLatin1String("o_id"), objId)) //data blocks { m_result = KDbResult(ERR_DELETE_SERVER_ERROR, tr("Could not delete object's data.")); return false; } return true; } bool KDbConnection::drv_dropTable(const QString& tableName) { return executeVoidSQL(KDbEscapedString("DROP TABLE %1").arg(escapeIdentifier(tableName))); } tristate KDbConnection::dropTable(KDbTableSchema* tableSchema) { return dropTable(tableSchema, true); } tristate KDbConnection::dropTable(KDbTableSchema* tableSchema, bool alsoRemoveSchema) { // Each SQL identifier needs to be escaped in the generated query. clearResult(); if (!tableSchema) return false; //be sure that we handle the correct KDbTableSchema object: if (tableSchema->id() < 0 || this->tableSchema(tableSchema->name()) != tableSchema || this->tableSchema(tableSchema->id()) != tableSchema) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not delete table \"%1\". %2") .arg(tr("Unexpected name or identifier."), tableSchema->name())); return false; } tristate res = KDbTableSchemaChangeListener::closeListeners(this, tableSchema); if (true != res) return res; //sanity checks: if (d->driver->isSystemObjectName(tableSchema->name())) { m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED, tr("Could not delete table \"%1\". %2") .arg(tableSchema->name(), d->strItIsASystemObject())); return false; } KDbTransactionGuard tg; if (!beginAutoCommitTransaction(&tg)) return false; //for sanity we're checking if this table exists physically const tristate result = drv_containsTable(tableSchema->name()); if (~result) { return cancelled; } if (result == true) { if (!drv_dropTable(tableSchema->name())) return false; } KDbTableSchema *ts = d->table(QLatin1String("kexi__fields")); if (!ts || !KDb::deleteRecords(this, *ts, QLatin1String("t_id"), tableSchema->id())) //field entries return false; //remove table schema from kexi__objects table if (!removeObject(tableSchema->id())) { return false; } if (alsoRemoveSchema) { //! @todo js: update any structure (e.g. queries) that depend on this table! tristate res = removeDataBlock(tableSchema->id(), QLatin1String("extended_schema")); if (!res) return false; d->removeTable(*tableSchema); } return commitAutoCommitTransaction(tg.transaction()); } tristate KDbConnection::dropTable(const QString& tableName) { clearResult(); KDbTableSchema* ts = tableSchema(tableName); if (!ts) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Table \"%1\" does not exist.").arg(tableName)); return false; } return dropTable(ts); } tristate KDbConnection::alterTable(KDbTableSchema* tableSchema, KDbTableSchema* newTableSchema) { clearResult(); tristate res = KDbTableSchemaChangeListener::closeListeners(this, tableSchema); if (true != res) return res; if (tableSchema == newTableSchema) { m_result = KDbResult(ERR_OBJECT_THE_SAME, tr("Could not alter table \"%1\" using the same table as destination.") .arg(tableSchema->name())); return false; } //! @todo (js) implement real altering //! @todo (js) update any structure (e.g. query) that depend on this table! bool ok = true; bool empty; #if 0 //! @todo uncomment: empty = isEmpty(tableSchema, ok) && ok; #else empty = true; #endif if (empty) { ok = createTable(newTableSchema, true/*replace*/); } return ok; } bool KDbConnection::alterTableName(KDbTableSchema* tableSchema, const QString& newName, bool replace) { clearResult(); if (tableSchema != this->tableSchema(tableSchema->id())) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Unknown table \"%1\".").arg(tableSchema->name())); return false; } if (newName.isEmpty() || !KDb::isIdentifier(newName)) { m_result = KDbResult(ERR_INVALID_IDENTIFIER, tr("Invalid table name \"%1\".").arg(newName)); return false; } const QString oldTableName = tableSchema->name(); const QString newTableName = newName.trimmed(); if (oldTableName.trimmed() == newTableName) { m_result = KDbResult(ERR_OBJECT_THE_SAME, tr("Could not rename table \"%1\" using the same name.") .arg(newTableName)); return false; } //! @todo alter table name for server DB backends! //! @todo what about objects (queries/forms) that use old name? KDbTableSchema *tableToReplace = this->tableSchema(newName); - const bool destTableExists = tableToReplace != 0; + const bool destTableExists = tableToReplace != nullptr; const int origID = destTableExists ? tableToReplace->id() : -1; //will be reused in the new table if (!replace && destTableExists) { m_result = KDbResult(ERR_OBJECT_EXISTS, tr("Could not rename table \"%1\" to \"%2\". Table \"%3\" already exists.") .arg(tableSchema->name(), newName, newName)); return false; } //helper: #define alterTableName_ERR \ tableSchema->setName(oldTableName) //restore old name KDbTransactionGuard tg; if (!beginAutoCommitTransaction(&tg)) return false; // drop the table replaced (with schema) if (destTableExists) { if (!dropTable(newName)) { return false; } // the new table owns the previous table's id: if (!executeVoidSQL( KDbEscapedString("UPDATE kexi__objects SET o_id=%1 WHERE o_id=%2 AND o_type=%3") .arg(d->driver->valueToSQL(KDbField::Integer, origID)) .arg(d->driver->valueToSQL(KDbField::Integer, tableSchema->id())) .arg(d->driver->valueToSQL(KDbField::Integer, int(KDb::TableObjectType))))) { return false; } if (!executeVoidSQL(KDbEscapedString("UPDATE kexi__fields SET t_id=%1 WHERE t_id=%2") .arg(d->driver->valueToSQL(KDbField::Integer, origID)) .arg(d->driver->valueToSQL(KDbField::Integer, tableSchema->id())))) { return false; } //maintain table ID d->changeTableId(tableSchema, origID); tableSchema->setId(origID); } if (!drv_alterTableName(tableSchema, newTableName)) { alterTableName_ERR; return false; } // Update kexi__objects //! @todo if (!executeVoidSQL(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2") .arg(escapeString(tableSchema->name())) .arg(d->driver->valueToSQL(KDbField::Integer, tableSchema->id())))) { alterTableName_ERR; return false; } //! @todo what about caption? //restore old name: it will be changed soon! tableSchema->setName(oldTableName); if (!commitAutoCommitTransaction(tg.transaction())) { alterTableName_ERR; return false; } //update tableSchema: d->renameTable(tableSchema, newTableName); return true; } bool KDbConnection::drv_alterTableName(KDbTableSchema* tableSchema, const QString& newName) { const QString oldTableName = tableSchema->name(); tableSchema->setName(newName); if (!executeVoidSQL(KDbEscapedString("ALTER TABLE %1 RENAME TO %2") .arg(KDbEscapedString(escapeIdentifier(oldTableName)), KDbEscapedString(escapeIdentifier(newName))))) { tableSchema->setName(oldTableName); //restore old name return false; } return true; } bool KDbConnection::dropQuery(KDbQuerySchema* querySchema) { clearResult(); if (!querySchema) return false; KDbTransactionGuard tg; if (!beginAutoCommitTransaction(&tg)) return false; //remove query schema from kexi__objects table if (!removeObject(querySchema->id())) { return false; } //! @todo update any structure that depend on this table! d->removeQuery(querySchema); return commitAutoCommitTransaction(tg.transaction()); } bool KDbConnection::dropQuery(const QString& queryName) { clearResult(); KDbQuerySchema* qs = querySchema(queryName); if (!qs) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Query \"%1\" does not exist.").arg(queryName)); return false; } return dropQuery(qs); } bool KDbConnection::drv_createTable(const KDbTableSchema& tableSchema) { const KDbNativeStatementBuilder builder(this); KDbEscapedString sql; if (!builder.generateCreateTableStatement(&sql,tableSchema)) { return false; } //kdbDebug() << "******** " << sql; return executeVoidSQL(sql); } bool KDbConnection::drv_createTable(const QString& tableName) { KDbTableSchema *ts = tableSchema(tableName); if (!ts) return false; return drv_createTable(*ts); } bool KDbConnection::beginAutoCommitTransaction(KDbTransactionGuard* tg) { if ((d->driver->beh->features & KDbDriver::IgnoreTransactions) || !d->autoCommit) { tg->setTransaction(KDbTransaction()); return true; } // commit current transaction (if present) for drivers // that allow single transaction per connection if (d->driver->beh->features & KDbDriver::SingleTransactions) { if (d->defaultTransactionStartedInside) //only commit internally started transaction if (!commitTransaction(d->default_trans, true)) { tg->setTransaction(KDbTransaction()); return false; //we have a real error } d->defaultTransactionStartedInside = d->default_trans.isNull(); if (!d->defaultTransactionStartedInside) { tg->setTransaction(d->default_trans); tg->doNothing(); return true; //reuse externally started transaction } } else if (!(d->driver->beh->features & KDbDriver::MultipleTransactions)) { tg->setTransaction(KDbTransaction()); return true; //no trans. supported at all - just return } tg->setTransaction(beginTransaction()); return !m_result.isError(); } bool KDbConnection::commitAutoCommitTransaction(const KDbTransaction& trans) { if (d->driver->beh->features & KDbDriver::IgnoreTransactions) return true; if (trans.isNull() || !d->driver->transactionsSupported()) return true; if (d->driver->beh->features & KDbDriver::SingleTransactions) { if (!d->defaultTransactionStartedInside) //only commit internally started transaction return true; //give up } return commitTransaction(trans, true); } bool KDbConnection::rollbackAutoCommitTransaction(const KDbTransaction& trans) { if (trans.isNull() || !d->driver->transactionsSupported()) return true; return rollbackTransaction(trans); } #define SET_ERR_TRANS_NOT_SUPP \ { m_result = KDbResult(ERR_UNSUPPORTED_DRV_FEATURE, \ KDbConnection::tr("Transactions are not supported for \"%1\" driver.").arg( d->driver->metaData()->name() )); } #define SET_BEGIN_TR_ERROR \ { if (!m_result.isError()) \ m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, \ KDbConnection::tr("Begin transaction failed.")); } KDbTransaction KDbConnection::beginTransaction() { if (!checkIsDatabaseUsed()) return KDbTransaction(); KDbTransaction trans; if (d->driver->beh->features & KDbDriver::IgnoreTransactions) { //we're creating dummy transaction data here, //so it will look like active trans.m_data = new KDbTransactionData(this); d->transactions.append(trans); return trans; } if (d->driver->beh->features & KDbDriver::SingleTransactions) { if (d->default_trans.active()) { m_result = KDbResult(ERR_TRANSACTION_ACTIVE, tr("Transaction already started.")); return KDbTransaction(); } if (!(trans.m_data = drv_beginTransaction())) { SET_BEGIN_TR_ERROR; return KDbTransaction(); } d->default_trans = trans; d->transactions.append(trans); return d->default_trans; } if (d->driver->beh->features & KDbDriver::MultipleTransactions) { if (!(trans.m_data = drv_beginTransaction())) { SET_BEGIN_TR_ERROR; return KDbTransaction(); } d->transactions.append(trans); return trans; } SET_ERR_TRANS_NOT_SUPP; return KDbTransaction(); } bool KDbConnection::commitTransaction(const KDbTransaction trans, bool ignore_inactive) { if (!isDatabaseUsed()) return false; if (!d->driver->transactionsSupported() && !(d->driver->beh->features & KDbDriver::IgnoreTransactions)) { SET_ERR_TRANS_NOT_SUPP; return false; } KDbTransaction t = trans; if (!t.active()) { //try default tr. if (!d->default_trans.active()) { if (ignore_inactive) return true; clearResult(); m_result = KDbResult(ERR_NO_TRANSACTION_ACTIVE, tr("Transaction not started.")); return false; } t = d->default_trans; d->default_trans = KDbTransaction(); //now: no default tr. } bool ret = true; if (!(d->driver->beh->features & KDbDriver::IgnoreTransactions)) ret = drv_commitTransaction(t.m_data); if (t.m_data) t.m_data->m_active = false; //now this transaction if inactive if (!d->dontRemoveTransactions) //true=transaction obj will be later removed from list d->transactions.removeAt(d->transactions.indexOf(t)); if (!ret && !m_result.isError()) m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, tr("Error on commit transaction.")); return ret; } bool KDbConnection::rollbackTransaction(const KDbTransaction trans, bool ignore_inactive) { if (!isDatabaseUsed()) return false; if (!d->driver->transactionsSupported() && !(d->driver->beh->features & KDbDriver::IgnoreTransactions)) { SET_ERR_TRANS_NOT_SUPP; return false; } KDbTransaction t = trans; if (!t.active()) { //try default tr. if (!d->default_trans.active()) { if (ignore_inactive) return true; clearResult(); m_result = KDbResult(ERR_NO_TRANSACTION_ACTIVE, tr("Transaction not started.")); return false; } t = d->default_trans; d->default_trans = KDbTransaction(); //now: no default tr. } bool ret = true; if (!(d->driver->beh->features & KDbDriver::IgnoreTransactions)) ret = drv_rollbackTransaction(t.m_data); if (t.m_data) t.m_data->m_active = false; //now this transaction if inactive if (!d->dontRemoveTransactions) //true=transaction obj will be later removed from list d->transactions.removeAt(d->transactions.indexOf(t)); if (!ret && !m_result.isError()) m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, tr("Error on rollback transaction.")); return ret; } #undef SET_ERR_TRANS_NOT_SUPP #undef SET_BEGIN_TR_ERROR /*bool KDbConnection::duringTransaction() { return drv_duringTransaction(); }*/ KDbTransaction KDbConnection::defaultTransaction() const { return d->default_trans; } void KDbConnection::setDefaultTransaction(const KDbTransaction& trans) { if (!isDatabaseUsed()) return; if (!(d->driver->beh->features & KDbDriver::IgnoreTransactions) && (!trans.active() || !d->driver->transactionsSupported())) { return; } d->default_trans = trans; } QList KDbConnection::transactions() { return d->transactions; } bool KDbConnection::autoCommit() const { return d->autoCommit; } bool KDbConnection::setAutoCommit(bool on) { if (d->autoCommit == on || d->driver->beh->features & KDbDriver::IgnoreTransactions) return true; if (!drv_setAutoCommit(on)) return false; d->autoCommit = on; return true; } KDbTransactionData* KDbConnection::drv_beginTransaction() { if (!executeVoidSQL(KDbEscapedString("BEGIN"))) - return 0; + return nullptr; return new KDbTransactionData(this); } bool KDbConnection::drv_commitTransaction(KDbTransactionData *) { return executeVoidSQL(KDbEscapedString("COMMIT")); } bool KDbConnection::drv_rollbackTransaction(KDbTransactionData *) { return executeVoidSQL(KDbEscapedString("ROLLBACK")); } bool KDbConnection::drv_setAutoCommit(bool /*on*/) { return true; } KDbCursor* KDbConnection::executeQuery(const KDbEscapedString& sql, KDbCursor::Options options) { if (sql.isEmpty()) - return 0; + return nullptr; KDbCursor *c = prepareQuery(sql, options); if (!c) - return 0; + return nullptr; if (!c->open()) {//err - kill that m_result = c->result(); CursorDeleter deleter(c); - return 0; + return nullptr; } return c; } KDbCursor* KDbConnection::executeQuery(KDbQuerySchema* query, const QList& params, KDbCursor::Options options) { KDbCursor *c = prepareQuery(query, params, options); if (!c) - return 0; + return nullptr; if (!c->open()) {//err - kill that m_result = c->result(); CursorDeleter deleter(c); - return 0; + return nullptr; } return c; } KDbCursor* KDbConnection::executeQuery(KDbQuerySchema* query, KDbCursor::Options options) { return executeQuery(query, QList(), options); } KDbCursor* KDbConnection::executeQuery(KDbTableSchema* table, KDbCursor::Options options) { return executeQuery(table->query(), options); } KDbCursor* KDbConnection::prepareQuery(KDbTableSchema* table, KDbCursor::Options options) { return prepareQuery(table->query(), options); } KDbCursor* KDbConnection::prepareQuery(KDbQuerySchema* query, const QList& params, KDbCursor::Options options) { KDbCursor* cursor = prepareQuery(query, options); if (cursor) cursor->setQueryParameters(params); return cursor; } bool KDbConnection::deleteCursor(KDbCursor *cursor) { if (!cursor) return false; if (cursor->connection() != this) {//illegal call kdbWarning() << "Could not delete the cursor not owned by the same connection!"; return false; } const bool ret = cursor->close(); CursorDeleter deleter(cursor); return ret; } //! @todo IMPORTANT: fix KDbConnection::setupObjectData() after refactoring bool KDbConnection::setupObjectData(const KDbRecordData &data, KDbObject *object) { if (data.count() < 5) { kdbWarning() << "Aborting, object data should have at least 5 elements, found" << data.count(); return false; } bool ok; const int id = data[0].toInt(&ok); if (!ok) return false; object->setId(id); const QString name(data[2].toString()); if (!KDb::isIdentifier(name)) { m_result = KDbResult(ERR_INVALID_IDENTIFIER, tr("Invalid object name \"%1\".").arg(name)); return false; } object->setName(name); object->setCaption(data[3].toString()); object->setDescription(data[4].toString()); // kdbDebug()<<"@@@ KDbConnection::setupObjectData() == " << sdata.schemaDataDebugString(); return true; } tristate KDbConnection::loadObjectData(int id, KDbObject* object) { KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1") .arg(d->driver->valueToSQL(KDbField::Integer, id)), &data)) { return cancelled; } return setupObjectData(data, object); } tristate KDbConnection::loadObjectData(int type, const QString& name, KDbObject* object) { KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc " "FROM kexi__objects WHERE o_type=%1 AND o_name=%2") .arg(d->driver->valueToSQL(KDbField::Integer, type)) .arg(escapeString(name)), &data)) { return cancelled; } return setupObjectData(data, object); } bool KDbConnection::storeObjectDataInternal(KDbObject* object, bool newObject) { KDbTableSchema *ts = d->table(QLatin1String("kexi__objects")); if (!ts) return false; if (newObject) { int existingID; if (true == querySingleNumber( KDbEscapedString("SELECT o_id FROM kexi__objects WHERE o_type=%1 AND o_name=%2") .arg(d->driver->valueToSQL(KDbField::Integer, object->type())) .arg(escapeString(object->name())), &existingID)) { //we already have stored an object data with the same name and type: //just update it's properties as it would be existing object object->setId(existingID); newObject = false; } } if (newObject) { if (object->id() <= 0) {//get new ID QScopedPointer fl(ts->subList( QList() << "o_type" << "o_name" << "o_caption" << "o_desc")); if (!fl) { return false; } KDbSqlResult* result; if (!insertRecord(fl.data(), QVariant(object->type()), QVariant(object->name()), QVariant(object->caption()), QVariant(object->description()), &result)) { return false; } QScopedPointer resultGuard(result); //fetch newly assigned ID //! @todo safe to cast it? quint64 obj_id = KDb::lastInsertedAutoIncValue(result, QLatin1String("o_id"), *ts); //kdbDebug() << "NEW obj_id == " << obj_id; if (obj_id == std::numeric_limits::max()) { return false; } object->setId(obj_id); return true; } else { QScopedPointer fl(ts->subList( QList() << "o_id" << "o_type" << "o_name" << "o_caption" << "o_desc")); return fl && insertRecord(fl.data(), QVariant(object->id()), QVariant(object->type()), QVariant(object->name()), QVariant(object->caption()), QVariant(object->description())); } } //existing object: return executeVoidSQL( KDbEscapedString("UPDATE kexi__objects SET o_type=%2, o_caption=%3, o_desc=%4 WHERE o_id=%1") .arg(d->driver->valueToSQL(KDbField::Integer, object->id())) .arg(d->driver->valueToSQL(KDbField::Integer, object->type())) .arg(escapeString(object->caption())) .arg(escapeString(object->description()))); } bool KDbConnection::storeObjectData(KDbObject* object) { return storeObjectDataInternal(object, false); } bool KDbConnection::storeNewObjectData(KDbObject* object) { return storeObjectDataInternal(object, true); } QString KDbConnection::escapeIdentifier(const QString& id, KDb::IdentifierEscapingType escapingType) const { return escapingType == KDb::KDbEscaping ? KDb::escapeIdentifier(id) : escapeIdentifier(id); } KDbCursor* KDbConnection::executeQueryInternal(const KDbEscapedString& sql, KDbQuerySchema* query, const QList* params) { Q_ASSERT(!sql.isEmpty() || query); clearResult(); if (!sql.isEmpty()) { return executeQuery(sql); } if (!query) { - return 0; + return nullptr; } if (params) { return executeQuery(query, *params); } return executeQuery(query); } tristate KDbConnection::querySingleRecordInternal(KDbRecordData* data, const KDbEscapedString* sql, KDbQuerySchema* query, const QList* params, bool addLimitTo1) { Q_ASSERT(sql || query); if (sql) { //! @todo does not work with non-SQL data sources m_result.setSql(d->driver->addLimitTo1(*sql, addLimitTo1)); } KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params); if (!cursor) { kdbWarning() << "!querySingleRecordInternal() " << m_result.sql(); return false; } if (!cursor->moveFirst() || cursor->eof() || !cursor->storeCurrentRecord(data)) { const tristate result = cursor->result().isError() ? tristate(false) : tristate(cancelled); //kdbDebug() << "!cursor->moveFirst() || cursor->eof() || cursor->storeCurrentRecord(data) " // "m_result.sql()=" << m_result.sql(); m_result = cursor->result(); deleteCursor(cursor); return result; } return deleteCursor(cursor); } tristate KDbConnection::querySingleRecord(const KDbEscapedString& sql, KDbRecordData* data, bool addLimitTo1) { - return querySingleRecordInternal(data, &sql, 0, 0, addLimitTo1); + return querySingleRecordInternal(data, &sql, nullptr, nullptr, addLimitTo1); } tristate KDbConnection::querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, bool addLimitTo1) { - return querySingleRecordInternal(data, 0, query, 0, addLimitTo1); + return querySingleRecordInternal(data, nullptr, query, nullptr, addLimitTo1); } tristate KDbConnection::querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, const QList& params, bool addLimitTo1) { - return querySingleRecordInternal(data, 0, query, ¶ms, addLimitTo1); + return querySingleRecordInternal(data, nullptr, query, ¶ms, addLimitTo1); } bool KDbConnection::checkIfColumnExists(KDbCursor *cursor, int column) { if (column >= cursor->fieldCount()) { m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, tr("Column \"%1\" does not exist in the query.").arg(column)); return false; } return true; } tristate KDbConnection::querySingleStringInternal(const KDbEscapedString* sql, QString* value, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1) { Q_ASSERT(sql || query); if (sql) { //! @todo does not work with non-SQL data sources m_result.setSql(d->driver->addLimitTo1(*sql, addLimitTo1)); } KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params); if (!cursor) { kdbWarning() << "!querySingleStringInternal()" << m_result.sql(); return false; } if (!cursor->moveFirst() || cursor->eof()) { const tristate result = cursor->result().isError() ? tristate(false) : tristate(cancelled); //kdbDebug() << "!cursor->moveFirst() || cursor->eof()" << m_result.sql(); deleteCursor(cursor); return result; } if (!checkIfColumnExists(cursor, column)) { deleteCursor(cursor); return false; } *value = cursor->value(column).toString(); return deleteCursor(cursor); } tristate KDbConnection::querySingleString(const KDbEscapedString& sql, QString* value, int column, bool addLimitTo1) { - return querySingleStringInternal(&sql, value, 0, 0, column, addLimitTo1); + return querySingleStringInternal(&sql, value, nullptr, nullptr, column, addLimitTo1); } tristate KDbConnection::querySingleString(KDbQuerySchema* query, QString* value, int column, bool addLimitTo1) { - return querySingleStringInternal(0, value, query, 0, column, addLimitTo1); + return querySingleStringInternal(nullptr, value, query, nullptr, column, addLimitTo1); } tristate KDbConnection::querySingleString(KDbQuerySchema* query, QString* value, const QList& params, int column, bool addLimitTo1) { - return querySingleStringInternal(0, value, query, ¶ms, column, addLimitTo1); + return querySingleStringInternal(nullptr, value, query, ¶ms, column, addLimitTo1); } tristate KDbConnection::querySingleNumberInternal(const KDbEscapedString* sql, int* number, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1) { QString str; const tristate result = querySingleStringInternal(sql, &str, query, params, column, addLimitTo1); if (result != true) return result; bool ok; const int _number = str.toInt(&ok); if (!ok) return false; *number = _number; return true; } tristate KDbConnection::querySingleNumber(const KDbEscapedString& sql, int* number, int column, bool addLimitTo1) { - return querySingleNumberInternal(&sql, number, 0, 0, column, addLimitTo1); + return querySingleNumberInternal(&sql, number, nullptr, nullptr, column, addLimitTo1); } tristate KDbConnection::querySingleNumber(KDbQuerySchema* query, int* number, int column, bool addLimitTo1) { - return querySingleNumberInternal(0, number, query, 0, column, addLimitTo1); + return querySingleNumberInternal(nullptr, number, query, nullptr, column, addLimitTo1); } tristate KDbConnection::querySingleNumber(KDbQuerySchema* query, int* number, const QList& params, int column, bool addLimitTo1) { - return querySingleNumberInternal(0, number, query, ¶ms, column, addLimitTo1); + return querySingleNumberInternal(nullptr, number, query, ¶ms, column, addLimitTo1); } bool KDbConnection::queryStringListInternal(const KDbEscapedString* sql, QStringList* list, KDbQuerySchema* query, const QList* params, int column, bool (*filterFunction)(const QString&)) { if (sql) { m_result.setSql(*sql); } KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params); if (!cursor) { kdbWarning() << "!queryStringListInternal() " << m_result.sql(); return false; } cursor->moveFirst(); if (cursor->result().isError()) { m_result = cursor->result(); deleteCursor(cursor); return false; } if (!cursor->eof() && !checkIfColumnExists(cursor, column)) { deleteCursor(cursor); return false; } list->clear(); while (!cursor->eof()) { const QString str(cursor->value(column).toString()); if (!filterFunction || filterFunction(str)) { list->append(str); } if (!cursor->moveNext() && cursor->result().isError()) { m_result = cursor->result(); deleteCursor(cursor); return false; } } return deleteCursor(cursor); } bool KDbConnection::queryStringList(const KDbEscapedString& sql, QStringList* list, int column) { - return queryStringListInternal(&sql, list, 0, 0, column, 0); + return queryStringListInternal(&sql, list, nullptr, nullptr, column, nullptr); } bool KDbConnection::queryStringList(KDbQuerySchema* query, QStringList* list, int column) { - return queryStringListInternal(0, list, query, 0, column, 0); + return queryStringListInternal(nullptr, list, query, nullptr, column, nullptr); } bool KDbConnection::queryStringList(KDbQuerySchema* query, QStringList* list, const QList& params, int column) { - return queryStringListInternal(0, list, query, ¶ms, column, 0); + return queryStringListInternal(nullptr, list, query, ¶ms, column, nullptr); } tristate KDbConnection::resultExists(const KDbEscapedString& sql, bool addLimitTo1) { //optimization if (d->driver->beh->SELECT_1_SUBQUERY_SUPPORTED) { //this is at least for sqlite if (addLimitTo1 && sql.left(6).toUpper() == "SELECT") { m_result.setSql( d->driver->addLimitTo1("SELECT 1 FROM (" + sql + ')', addLimitTo1)); } else { m_result.setSql(sql); } } else { if (addLimitTo1 && sql.startsWith("SELECT")) { m_result.setSql(d->driver->addLimitTo1(sql, addLimitTo1)); } else { m_result.setSql(sql); } } KDbCursor *cursor = executeQuery(m_result.sql()); if (!cursor) { kdbWarning() << "!executeQuery()" << m_result.sql(); return cancelled; } if (!cursor->moveFirst() || cursor->eof()) { kdbWarning() << "!cursor->moveFirst() || cursor->eof()" << m_result.sql(); m_result = cursor->result(); deleteCursor(cursor); return m_result.isError() ? cancelled : tristate(false); } return deleteCursor(cursor) ? tristate(true) : cancelled; } tristate KDbConnection::isEmpty(KDbTableSchema* table) { const KDbNativeStatementBuilder builder(this); KDbEscapedString sql; if (!builder.generateSelectStatement(&sql, table)) { return cancelled; } const tristate result = resultExists(sql); if (~result) { return cancelled; } return result == false; } //! Used by addFieldPropertyToExtendedTableSchemaData() static void createExtendedTableSchemaMainElementIfNeeded( QDomDocument* doc, QDomElement* extendedTableSchemaMainEl, bool* extendedTableSchemaStringIsEmpty) { if (!*extendedTableSchemaStringIsEmpty) return; //init document *extendedTableSchemaMainEl = doc->createElement(QLatin1String("EXTENDED_TABLE_SCHEMA")); doc->appendChild(*extendedTableSchemaMainEl); extendedTableSchemaMainEl->setAttribute(QLatin1String("version"), QString::number(KDB_EXTENDED_TABLE_SCHEMA_VERSION)); *extendedTableSchemaStringIsEmpty = false; } //! Used by addFieldPropertyToExtendedTableSchemaData() static void createExtendedTableSchemaFieldElementIfNeeded(QDomDocument* doc, QDomElement* extendedTableSchemaMainEl, const QString& fieldName, QDomElement* extendedTableSchemaFieldEl, bool append = true) { if (!extendedTableSchemaFieldEl->isNull()) return; *extendedTableSchemaFieldEl = doc->createElement(QLatin1String("field")); if (append) extendedTableSchemaMainEl->appendChild(*extendedTableSchemaFieldEl); extendedTableSchemaFieldEl->setAttribute(QLatin1String("name"), fieldName); } /*! @internal used by storeExtendedTableSchemaData() Creates DOM node for @a propertyName and @a propertyValue. Creates enclosing EXTENDED_TABLE_SCHEMA element if EXTENDED_TABLE_SCHEMA is true. Updates extendedTableSchemaStringIsEmpty and extendedTableSchemaMainEl afterwards. If extendedTableSchemaFieldEl is null, creates element (with optional "custom" attribute is @a custom is false). */ static void addFieldPropertyToExtendedTableSchemaData( const KDbField& f, const QByteArray &propertyName, const QVariant& propertyValue, QDomDocument* doc, QDomElement* extendedTableSchemaMainEl, QDomElement* extendedTableSchemaFieldEl, bool* extendedTableSchemaStringIsEmpty, bool custom = false) { createExtendedTableSchemaMainElementIfNeeded(doc, extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty); createExtendedTableSchemaFieldElementIfNeeded( doc, extendedTableSchemaMainEl, f.name(), extendedTableSchemaFieldEl); //create QDomElement extendedTableSchemaFieldPropertyEl = doc->createElement(QLatin1String("property")); extendedTableSchemaFieldEl->appendChild(extendedTableSchemaFieldPropertyEl); if (custom) extendedTableSchemaFieldPropertyEl.setAttribute(QLatin1String("custom"), QLatin1String("true")); extendedTableSchemaFieldPropertyEl.setAttribute(QLatin1String("name"), QLatin1String(propertyName)); QDomElement extendedTableSchemaFieldPropertyValueEl; switch (propertyValue.type()) { case QVariant::String: extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("string")); break; case QVariant::ByteArray: extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("cstring")); break; case QVariant::Int: case QVariant::Double: case QVariant::UInt: case QVariant::LongLong: case QVariant::ULongLong: extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("number")); break; case QVariant::Bool: extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("bool")); break; default: //! @todo add more QVariant types kdbCritical() << "addFieldPropertyToExtendedTableSchemaData(): impl. error"; } extendedTableSchemaFieldPropertyEl.appendChild(extendedTableSchemaFieldPropertyValueEl); extendedTableSchemaFieldPropertyValueEl.appendChild( doc->createTextNode(propertyValue.toString())); } bool KDbConnection::storeExtendedTableSchemaData(KDbTableSchema* tableSchema) { //! @todo future: save in older versions if neeed QDomDocument doc(QLatin1String("EXTENDED_TABLE_SCHEMA")); QDomElement extendedTableSchemaMainEl; bool extendedTableSchemaStringIsEmpty = true; //for each field: foreach(KDbField* f, *tableSchema->fields()) { QDomElement extendedTableSchemaFieldEl; const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive if (f->visibleDecimalPlaces() >= 0/*nondefault*/ && KDb::supportsVisibleDecimalPlacesProperty(type)) { addFieldPropertyToExtendedTableSchemaData( *f, "visibleDecimalPlaces", f->visibleDecimalPlaces(), &doc, &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl, &extendedTableSchemaStringIsEmpty); } if (type == KDbField::Text) { if (f->maxLengthStrategy() == KDbField::DefaultMaxLength) { addFieldPropertyToExtendedTableSchemaData( *f, "maxLengthIsDefault", true, &doc, &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl, &extendedTableSchemaStringIsEmpty); } } // boolean field with "not null" // add custom properties const KDbField::CustomPropertiesMap customProperties(f->customProperties()); for (KDbField::CustomPropertiesMap::ConstIterator itCustom = customProperties.constBegin(); itCustom != customProperties.constEnd(); ++itCustom) { addFieldPropertyToExtendedTableSchemaData( *f, itCustom.key(), itCustom.value(), &doc, &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl, &extendedTableSchemaStringIsEmpty, /*custom*/true); } // save lookup table specification, if present KDbLookupFieldSchema *lookupFieldSchema = tableSchema->lookupFieldSchema(*f); if (lookupFieldSchema) { createExtendedTableSchemaFieldElementIfNeeded( &doc, &extendedTableSchemaMainEl, f->name(), &extendedTableSchemaFieldEl, false/* !append */); lookupFieldSchema->saveToDom(&doc, &extendedTableSchemaFieldEl); if (extendedTableSchemaFieldEl.hasChildNodes()) { // this element provides the definition, so let's append it now createExtendedTableSchemaMainElementIfNeeded(&doc, &extendedTableSchemaMainEl, &extendedTableSchemaStringIsEmpty); extendedTableSchemaMainEl.appendChild(extendedTableSchemaFieldEl); } } } // Store extended schema information (see ExtendedTableSchemaInformation in Kexi Wiki) if (extendedTableSchemaStringIsEmpty) { #ifdef KDB_DEBUG_GUI KDb::alterTableActionDebugGUI(QLatin1String("** Extended table schema REMOVED.")); #endif if (!removeDataBlock(tableSchema->id(), QLatin1String("extended_schema"))) return false; } else { #ifdef KDB_DEBUG_GUI KDb::alterTableActionDebugGUI( QLatin1String("** Extended table schema set to:\n") + doc.toString(4)); #endif if (!storeDataBlock(tableSchema->id(), doc.toString(1), QLatin1String("extended_schema"))) return false; } return true; } bool KDbConnection::loadExtendedTableSchemaData(KDbTableSchema* tableSchema) { #define loadExtendedTableSchemaData_ERR \ { m_result = KDbResult(tr("Error while loading extended table schema.", \ "Extended schema for a table: loading error")); \ return false; } #define loadExtendedTableSchemaData_ERR2(details) \ { m_result = KDbResult(details); \ m_result.setMessageTitle(tr("Error while loading extended table schema.", \ "Extended schema for a table: loading error")); \ return false; } #define loadExtendedTableSchemaData_ERR3(data) \ { m_result = KDbResult(tr("Invalid XML data: %1").arg(data.left(1024))); \ m_result.setMessageTitle(tr("Error while loading extended table schema.", \ "Extended schema for a table: loading error")); \ return false; } // Load extended schema information, if present (see ExtendedTableSchemaInformation in Kexi Wiki) QString extendedTableSchemaString; tristate res = loadDataBlock(tableSchema->id(), &extendedTableSchemaString, QLatin1String("extended_schema")); if (!res) loadExtendedTableSchemaData_ERR; // extendedTableSchemaString will be just empty if there is no such data block if (extendedTableSchemaString.isEmpty()) return true; QDomDocument doc; QString errorMsg; int errorLine, errorColumn; if (!doc.setContent(extendedTableSchemaString, &errorMsg, &errorLine, &errorColumn)) { loadExtendedTableSchemaData_ERR2( tr("Error in XML data: \"%1\" in line %2, column %3.\nXML data: %4") .arg(errorMsg).arg(errorLine).arg(errorColumn).arg(extendedTableSchemaString.left(1024))); } //! @todo look at the current format version (KDB_EXTENDED_TABLE_SCHEMA_VERSION) if (doc.doctype().name() != QLatin1String("EXTENDED_TABLE_SCHEMA")) loadExtendedTableSchemaData_ERR3(extendedTableSchemaString); QDomElement docEl = doc.documentElement(); if (docEl.tagName() != QLatin1String("EXTENDED_TABLE_SCHEMA")) loadExtendedTableSchemaData_ERR3(extendedTableSchemaString); for (QDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement fieldEl = n.toElement(); if (fieldEl.tagName() == QLatin1String("field")) { KDbField *f = tableSchema->field(fieldEl.attribute(QLatin1String("name"))); if (f) { //set properties of the field: //! @todo more properties for (QDomNode propNode = fieldEl.firstChild(); !propNode.isNull(); propNode = propNode.nextSibling()) { const QDomElement propEl = propNode.toElement(); bool ok; int intValue; if (propEl.tagName() == QLatin1String("property")) { QByteArray propertyName = propEl.attribute(QLatin1String("name")).toLatin1(); if (propEl.attribute(QLatin1String("custom")) == QLatin1String("true")) { //custom property const QVariant v(KDb::loadPropertyValueFromDom(propEl.firstChild(), &ok)); if (ok) { f->setCustomProperty(propertyName, v); } } else if (propertyName == "visibleDecimalPlaces") { if (KDb::supportsVisibleDecimalPlacesProperty(f->type())) { intValue = KDb::loadIntPropertyValueFromDom(propEl.firstChild(), &ok); if (ok) f->setVisibleDecimalPlaces(intValue); } } else if (propertyName == "maxLengthIsDefault") { if (f->type() == KDbField::Text) { const bool maxLengthIsDefault = KDb::loadPropertyValueFromDom(propEl.firstChild(), &ok).toBool(); if (ok) { f->setMaxLengthStrategy( maxLengthIsDefault ? KDbField::DefaultMaxLength : KDbField::DefinedMaxLength); } } } //! @todo more properties... } else if (propEl.tagName() == QLatin1String("lookup-column")) { KDbLookupFieldSchema *lookupFieldSchema = KDbLookupFieldSchema::loadFromDom(propEl); if (lookupFieldSchema) { kdbDebug() << f->name() << *lookupFieldSchema; tableSchema->setLookupFieldSchema(f->name(), lookupFieldSchema); } } } } else { kdbWarning() << "no such field:" << fieldEl.attribute(QLatin1String("name")) << "in table:" << tableSchema->name(); } } } return true; } KDbField* KDbConnection::setupField(const KDbRecordData &data) { bool ok = true; int f_int_type = data.at(1).toInt(&ok); if (f_int_type <= KDbField::InvalidType || f_int_type > KDbField::LastType) ok = false; if (!ok) - return 0; + return nullptr; KDbField::Type f_type = (KDbField::Type)f_int_type; int f_len = qMax(0, data.at(3).toInt(&ok)); // defined limit if (!ok) { - return 0; + return nullptr; } if (f_len < 0) { f_len = 0; } //! @todo load maxLengthStrategy info to see if the maxLength is the default int f_prec = data.at(4).toInt(&ok); if (!ok) - return 0; + return nullptr; KDbField::Constraints f_constr = (KDbField::Constraints)data.at(5).toInt(&ok); if (!ok) - return 0; + return nullptr; KDbField::Options f_opts = (KDbField::Options)data.at(6).toInt(&ok); if (!ok) - return 0; + return nullptr; QString name(data.at(2).toString()); if (!KDb::isIdentifier(name)) { name = KDb::stringToIdentifier(name); } KDbField *f = new KDbField( name, f_type, f_constr, f_opts, f_len, f_prec); QVariant defaultVariant = data.at(7); if (defaultVariant.isValid()) { defaultVariant = KDb::stringToVariant(defaultVariant.toString(), KDbField::variantType(f_type), &ok); if (ok) { f->setDefaultValue(defaultVariant); } else { kdbWarning() << "problem with KDb::stringToVariant(" << defaultVariant << ')'; ok = true; //problem with defaultValue is not critical } } f->setCaption(data.at(9).toString()); f->setDescription(data.at(10).toString()); return f; } KDbTableSchema* KDbConnection::setupTableSchema(const KDbRecordData &data) { KDbTableSchema *t = new KDbTableSchema(this); if (!setupObjectData(data, t)) { delete t; - return 0; + return nullptr; } KDbCursor *cursor; if (!(cursor = executeQuery( KDbEscapedString("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, " "f_options, f_default, f_order, f_caption, f_help " "FROM kexi__fields WHERE t_id=%1 ORDER BY f_order") .arg(d->driver->valueToSQL(KDbField::Integer, t->id()))))) { delete t; - return 0; + return nullptr; } if (!cursor->moveFirst()) { if (!cursor->result().isError() && cursor->eof()) { m_result = KDbResult(tr("Table has no fields defined.")); } deleteCursor(cursor); delete t; - return 0; + return nullptr; } // For each field: load its schema KDbRecordData fieldData; bool ok = true; while (!cursor->eof()) { // kdbDebug()<<"@@@ f_name=="<value(2).asCString(); if (!cursor->storeCurrentRecord(&fieldData)) { ok = false; break; } KDbField *f = setupField(fieldData); if (!f || !t->addField(f)) { ok = false; break; } cursor->moveNext(); } if (!ok) {//error: deleteCursor(cursor); delete t; - return 0; + return nullptr; } if (!deleteCursor(cursor)) { delete t; - return 0; + return nullptr; } if (!loadExtendedTableSchemaData(t)) { delete t; - return 0; + return nullptr; } //store locally: d->insertTable(t); return t; } KDbTableSchema* KDbConnection::tableSchema(const QString& tableName) { KDbTableSchema *t = d->table(tableName); if (t) return t; //not found: retrieve schema KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects " "WHERE o_name=%1 AND o_type=%2") .arg(escapeString(tableName)) .arg(d->driver->valueToSQL(KDbField::Integer, KDb::TableObjectType)), &data)) { - return 0; + return nullptr; } return setupTableSchema(data); } KDbTableSchema* KDbConnection::tableSchema(int tableId) { KDbTableSchema *t = d->table(tableId); if (t) return t; //not found: retrieve schema KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1") .arg(d->driver->valueToSQL(KDbField::Integer, tableId)), &data)) { - return 0; + return nullptr; } return setupTableSchema(data); } tristate KDbConnection::loadDataBlock(int objectID, QString* dataString, const QString& dataID) { if (objectID <= 0) return false; return querySingleString( KDbEscapedString("SELECT o_data FROM kexi__objectdata WHERE o_id=%1 AND ") .arg(d->driver->valueToSQL(KDbField::Integer, objectID)) + KDbEscapedString(KDb::sqlWhere(d->driver, KDbField::Text, QLatin1String("o_sub_id"), dataID.isEmpty() ? QVariant() : QVariant(dataID))), dataString); } bool KDbConnection::storeDataBlock(int objectID, const QString &dataString, const QString& dataID) { if (objectID <= 0) return false; KDbEscapedString sql( KDbEscapedString("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1") .arg(d->driver->valueToSQL(KDbField::Integer, objectID))); KDbEscapedString sql_sub(KDb::sqlWhere(d->driver, KDbField::Text, QLatin1String("o_sub_id"), dataID.isEmpty() ? QVariant() : QVariant(dataID))); const tristate result = resultExists(sql + " AND " + sql_sub); if (~result) { return false; } if (result == true) { return executeVoidSQL(KDbEscapedString("UPDATE kexi__objectdata SET o_data=%1 WHERE o_id=%2 AND ") .arg(d->driver->valueToSQL(KDbField::LongText, dataString)) .arg(d->driver->valueToSQL(KDbField::Integer, objectID)) + sql_sub); } return executeVoidSQL( KDbEscapedString("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (") + KDbEscapedString::number(objectID) + ',' + d->driver->valueToSQL(KDbField::LongText, dataString) + ',' + d->driver->valueToSQL(KDbField::Text, dataID) + ')'); } bool KDbConnection::copyDataBlock(int sourceObjectID, int destObjectID, const QString &dataID) { if (sourceObjectID <= 0 || destObjectID <= 0) return false; if (sourceObjectID == destObjectID) return true; if (!removeDataBlock(destObjectID, dataID)) // remove before copying return false; KDbEscapedString sql = KDbEscapedString( "INSERT INTO kexi__objectdata SELECT %1, t.o_data, t.o_sub_id " "FROM kexi__objectdata AS t WHERE o_id=%2") .arg(d->driver->valueToSQL(KDbField::Integer, destObjectID)) .arg(d->driver->valueToSQL(KDbField::Integer, sourceObjectID)); if (!dataID.isEmpty()) { sql += KDbEscapedString(" AND ") + KDb::sqlWhere(d->driver, KDbField::Text, QLatin1String("o_sub_id"), dataID); } return executeVoidSQL(sql); } bool KDbConnection::removeDataBlock(int objectID, const QString& dataID) { if (objectID <= 0) return false; if (dataID.isEmpty()) return KDb::deleteRecords(this, QLatin1String("kexi__objectdata"), QLatin1String("o_id"), QString::number(objectID)); else return KDb::deleteRecords(this, QLatin1String("kexi__objectdata"), QLatin1String("o_id"), KDbField::Integer, objectID, QLatin1String("o_sub_id"), KDbField::Text, dataID); } KDbQuerySchema* KDbConnection::setupQuerySchema(const KDbRecordData &data) { bool ok = true; const int objID = data[0].toInt(&ok); if (!ok) - return 0; + return nullptr; QString sql; if (!loadDataBlock(objID, &sql, QLatin1String("sql"))) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find definition for query \"%1\". Deleting this query is recommended.").arg(data[2].toString())); - return 0; + return nullptr; } - KDbQuerySchema *query = 0; + KDbQuerySchema *query = nullptr; if (d->parser()->parse(KDbEscapedString(sql))) { query = d->parser()->query(); } //error? if (!query) { m_result = KDbResult(ERR_SQL_PARSE_ERROR, tr("

Could not load definition for query \"%1\". " "SQL statement for this query is invalid:
%2

\n" "

This query can be edited only in Text View.

") .arg(data[2].toString(), sql)); - return 0; + return nullptr; } if (!setupObjectData(data, query)) { delete query; - return 0; + return nullptr; } d->insertQuery(query); return query; } KDbQuerySchema* KDbConnection::querySchema(const QString& queryName) { QString m_queryName = queryName.toLower(); KDbQuerySchema *q = d->query(m_queryName); if (q) return q; //not found: retrieve schema KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects " "WHERE o_name=%1 AND o_type=%2") .arg(escapeString(m_queryName)) .arg(d->driver->valueToSQL(KDbField::Integer, int(KDb::QueryObjectType))), &data)) { - return 0; + return nullptr; } return setupQuerySchema(data); } KDbQuerySchema* KDbConnection::querySchema(int queryId) { KDbQuerySchema *q = d->query(queryId); if (q) return q; //not found: retrieve schema clearResult(); KDbRecordData data; if (true != querySingleRecord( KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc FROM kexi__objects WHERE o_id=%1") .arg(d->driver->valueToSQL(KDbField::Integer, queryId)), &data)) { - return 0; + return nullptr; } return setupQuerySchema(data); } bool KDbConnection::setQuerySchemaObsolete(const QString& queryName) { KDbQuerySchema* oldQuery = querySchema(queryName); if (!oldQuery) return false; d->setQueryObsolete(oldQuery); return true; } QString KDbConnection::escapeIdentifier(const QString& id) const { return d->driver->escapeIdentifier(id); } bool KDbConnection::isInternalTableSchema(const QString& tableName) { KDbTableSchema* schema = d->table(tableName); return (schema && schema->isInternal()) // these are here for compatiblility because we're no longer instantiate // them but can exist in projects created with previous Kexi versions: || tableName == QLatin1String("kexi__final") || tableName == QLatin1String("kexi__useractions"); } void KDbConnection::removeMe(KDbTableSchema *table) { if (table && d) { d->takeTable(table); } } QString KDbConnection::anyAvailableDatabaseName() { if (!d->availableDatabaseName.isEmpty()) { return d->availableDatabaseName; } return d->driver->beh->ALWAYS_AVAILABLE_DATABASE_NAME; } void KDbConnection::setAvailableDatabaseName(const QString& dbName) { d->availableDatabaseName = dbName; } //! @internal used in updateRecord(), insertRecord(), inline static void updateRecordDataWithNewValues(KDbQuerySchema* query, KDbRecordData* data, const KDbRecordEditBuffer::DbHash& b, QHash* columnsOrderExpanded) { *columnsOrderExpanded = query->columnsOrder(KDbQuerySchema::ExpandedList); QHash::ConstIterator columnsOrderExpandedIt; for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) { columnsOrderExpandedIt = columnsOrderExpanded->constFind(it.key()); if (columnsOrderExpandedIt == columnsOrderExpanded->constEnd()) { kdbWarning() << "(KDbConnection) \"now also assign new value in memory\" step" "- could not find item" << it.key()->aliasOrName(); continue; } (*data)[ columnsOrderExpandedIt.value() ] = it.value(); } } bool KDbConnection::updateRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId) { // Each SQL identifier needs to be escaped in the generated query. // kdbDebug() << *query; clearResult(); //--get PKEY if (buf->dbBuffer().isEmpty()) { kdbDebug() << " -- NO CHANGES DATA!"; return true; } KDbTableSchema *mt = query->masterTable(); if (!mt) { kdbWarning() << " -- NO MASTER TABLE!"; m_result = KDbResult(ERR_UPDATE_NO_MASTER_TABLE, tr("Could not update record because there is no master table defined.")); return false; } - KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr; if (!useRecordId && !pkey) { kdbWarning() << " -- NO MASTER TABLE's PKEY!"; m_result = KDbResult(ERR_UPDATE_NO_MASTER_TABLES_PKEY, tr("Could not update record because master table has no primary key defined.")); //! @todo perhaps we can try to update without using PKEY? return false; } //update the record: KDbEscapedString sql; sql.reserve(4096); sql = KDbEscapedString("UPDATE ") + escapeIdentifier(mt->name()) + " SET "; KDbEscapedString sqlset, sqlwhere; sqlset.reserve(1024); sqlwhere.reserve(1024); KDbRecordEditBuffer::DbHash b = buf->dbBuffer(); //gather the fields which are updated ( have values in KDbRecordEditBuffer) KDbFieldList affectedFields; for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) { if (it.key()->field()->table() != mt) continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field) if (!sqlset.isEmpty()) sqlset += ','; KDbField* currentField = it.key()->field(); const bool affectedFieldsAddOk = affectedFields.addField(currentField); Q_ASSERT(affectedFieldsAddOk); sqlset += KDbEscapedString(escapeIdentifier(currentField->name())) + '=' + d->driver->valueToSQL(currentField, it.value()); } if (pkey) { const QVector pkeyFieldsOrder(query->pkeyFieldsOrder()); //kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount(); if (pkey->fieldCount() != query->pkeyFieldCount()) { //sanity check kdbWarning() << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!"; m_result = KDbResult(ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY, tr("Could not update record because it does not contain entire primary key of master table.")); return false; } if (!pkey->fields()->isEmpty()) { int i = 0; foreach(KDbField *f, *pkey->fields()) { if (!sqlwhere.isEmpty()) sqlwhere += " AND "; QVariant val(data->at(pkeyFieldsOrder.at(i))); if (val.isNull() || !val.isValid()) { m_result = KDbResult(ERR_UPDATE_NULL_PKEY_FIELD, tr("Primary key's field \"%1\" cannot be empty.").arg(f->name())); //js todo: pass the field's name somewhere! return false; } sqlwhere += KDbEscapedString(escapeIdentifier(f->name())) + '=' + d->driver->valueToSQL(f, val); i++; } } } else { //use RecordId sqlwhere = KDbEscapedString(escapeIdentifier(d->driver->beh->ROW_ID_FIELD_NAME)) + '=' + d->driver->valueToSQL(KDbField::BigInteger, (*data)[data->size() - 1]); } sql += (sqlset + " WHERE " + sqlwhere); //kdbDebug() << " -- SQL == " << ((sql.length() > 400) ? (sql.left(400) + "[.....]") : sql); // preprocessing before update if (!drv_beforeUpdate(mt->name(), &affectedFields)) return false; bool res = executeVoidSQL(sql); // postprocessing after update if (!drv_afterUpdate(mt->name(), &affectedFields)) return false; if (!res) { m_result = KDbResult(ERR_UPDATE_SERVER_ERROR, tr("Record updating on the server failed.")); return false; } //success: now also assign new values in memory: QHash columnsOrderExpanded; updateRecordDataWithNewValues(query, data, b, &columnsOrderExpanded); return true; } bool KDbConnection::insertRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool getRecordId) { // Each SQL identifier needs to be escaped in the generated query. clearResult(); //--get PKEY /*disabled: there may be empty records (with autoinc) if (buf.dbBuffer().isEmpty()) { kdbDebug() << " -- NO CHANGES DATA!"; return true; }*/ KDbTableSchema *mt = query->masterTable(); if (!mt) { kdbWarning() << " -- NO MASTER TABLE!"; m_result = KDbResult(ERR_INSERT_NO_MASTER_TABLE, tr("Could not insert record because there is no master table specified.")); return false; } - KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr; if (!getRecordId && !pkey) { kdbWarning() << " -- WARNING: NO MASTER TABLE's PKEY"; } KDbEscapedString sqlcols, sqlvals; sqlcols.reserve(1024); sqlvals.reserve(1024); //insert the record: KDbEscapedString sql; sql.reserve(4096); sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(mt->name()) + " ("; KDbRecordEditBuffer::DbHash b = buf->dbBuffer(); // add default values, if available (for any column without value explicitly set) const KDbQueryColumnInfo::Vector fieldsExpanded(query->fieldsExpanded(KDbQuerySchema::Unique)); int fieldsExpandedCount = fieldsExpanded.count(); for (int i = 0; i < fieldsExpandedCount; i++) { KDbQueryColumnInfo *ci = fieldsExpanded.at(i); if (ci->field() && KDb::isDefaultValueAllowed(*ci->field()) && !ci->field()->defaultValue().isNull() && !b.contains(ci)) { //kdbDebug() << "adding default value" << ci->field->defaultValue().toString() << "for column" << ci->field->name(); b.insert(ci, ci->field()->defaultValue()); } } //collect fields which have values in KDbRecordEditBuffer KDbFieldList affectedFields; if (b.isEmpty()) { // empty record inserting requested: if (!getRecordId && !pkey) { kdbWarning() << "MASTER TABLE's PKEY REQUIRED FOR INSERTING EMPTY RECORDS: INSERT CANCELLED"; m_result = KDbResult(ERR_INSERT_NO_MASTER_TABLES_PKEY, tr("Could not insert record because master table has no primary key specified.")); return false; } if (pkey) { const QVector pkeyFieldsOrder(query->pkeyFieldsOrder()); // kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount(); if (pkey->fieldCount() != query->pkeyFieldCount()) { //sanity check kdbWarning() << "NO ENTIRE MASTER TABLE's PKEY SPECIFIED!"; m_result = KDbResult(ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY, tr("Could not insert record because it does not contain entire master table's primary key.")); return false; } } //at least one value is needed for VALUES section: find it and set to NULL: KDbField *anyField = mt->anyNonPKField(); if (!anyField) { if (!pkey) { kdbWarning() << "WARNING: NO FIELD AVAILABLE TO SET IT TO NULL"; return false; } else { //try to set NULL in pkey field (could not work for every SQL engine!) anyField = pkey->fields()->first(); } } sqlcols += escapeIdentifier(anyField->name()); sqlvals += d->driver->valueToSQL(anyField, QVariant()/*NULL*/); const bool affectedFieldsAddOk = affectedFields.addField(anyField); Q_ASSERT(affectedFieldsAddOk); } else { // non-empty record inserting requested: for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) { if (it.key()->field()->table() != mt) continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field) if (!sqlcols.isEmpty()) { sqlcols += ','; sqlvals += ','; } KDbField* currentField = it.key()->field(); const bool affectedFieldsAddOk = affectedFields.addField(currentField); Q_ASSERT(affectedFieldsAddOk); sqlcols += escapeIdentifier(currentField->name()); sqlvals += d->driver->valueToSQL(currentField, it.value()); } } sql += (sqlcols + ") VALUES (" + sqlvals + ')'); // kdbDebug() << " -- SQL == " << sql; // low-level insert KDbSqlResult* result; if (!insertRecordInternal(mt->name(), &affectedFields, sql, &result)) { m_result = KDbResult(ERR_INSERT_SERVER_ERROR, tr("Record inserting on the server failed.")); return false; } //success: now also assign a new value in memory: QScopedPointer resultGuard(result); QHash columnsOrderExpanded; updateRecordDataWithNewValues(query, data, b, &columnsOrderExpanded); //fetch autoincremented values KDbQueryColumnInfo::List *aif_list = query->autoIncrementFields(); quint64 recordId = 0; if (pkey && !aif_list->isEmpty()) { //! @todo now only if PKEY is present, this should also work when there's no PKEY KDbQueryColumnInfo *id_columnInfo = aif_list->first(); //! @todo safe to cast it? quint64 last_id = KDb::lastInsertedAutoIncValue(result, id_columnInfo->field()->name(), id_columnInfo->field()->table()->name(), &recordId); if (last_id == std::numeric_limits::max()) { //! @todo show error //! @todo remove just inserted record. How? Using ROLLBACK? return false; } KDbRecordData aif_data; KDbEscapedString getAutoIncForInsertedValue("SELECT " + query->autoIncrementSQLFieldsList(this) + " FROM " + escapeIdentifier(id_columnInfo->field()->table()->name()) + " WHERE " + escapeIdentifier(id_columnInfo->field()->name()) + '=' + QByteArray::number(last_id)); if (true != querySingleRecord(getAutoIncForInsertedValue, &aif_data)) { //! @todo show error return false; } int i = 0; foreach(KDbQueryColumnInfo *ci, *aif_list) { // kdbDebug() << "AUTOINCREMENTED FIELD" << fi->field->name() << "==" << aif_data[i].toInt(); ((*data)[ columnsOrderExpanded.value(ci)] = aif_data.value(i)).convert(ci->field()->variantType()); //cast to get proper type i++; } } else { recordId = result->lastInsertRecordId(); // kdbDebug() << "new recordId ==" << recordId; if (d->driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) { kdbWarning() << "d->driver->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE"; return false; } } if (getRecordId && /*sanity check*/data->size() > fieldsExpanded.size()) { // kdbDebug() << "new ROWID ==" << ROWID; (*data)[data->size() - 1] = recordId; } return true; } bool KDbConnection::deleteRecord(KDbQuerySchema* query, KDbRecordData* data, bool useRecordId) { // Each SQL identifier needs to be escaped in the generated query. clearResult(); KDbTableSchema *mt = query->masterTable(); if (!mt) { kdbWarning() << " -- NO MASTER TABLE!"; m_result = KDbResult(ERR_DELETE_NO_MASTER_TABLE, tr("Could not delete record because there is no master table specified.")); return false; } - KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : 0; + KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr; //! @todo allow to delete from a table without pkey if (!useRecordId && !pkey) { kdbWarning() << " -- WARNING: NO MASTER TABLE's PKEY"; m_result = KDbResult(ERR_DELETE_NO_MASTER_TABLES_PKEY, tr("Could not delete record because there is no primary key for master table specified.")); return false; } //update the record: KDbEscapedString sql; sql.reserve(4096); sql = KDbEscapedString("DELETE FROM ") + escapeIdentifier(mt->name()) + " WHERE "; KDbEscapedString sqlwhere; sqlwhere.reserve(1024); if (pkey) { const QVector pkeyFieldsOrder(query->pkeyFieldsOrder()); //kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount(); if (pkey->fieldCount() != query->pkeyFieldCount()) { //sanity check kdbWarning() << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!"; m_result = KDbResult(ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY, tr("Could not delete record because it does not contain entire master table's primary key.")); return false; } int i = 0; foreach(KDbField *f, *pkey->fields()) { if (!sqlwhere.isEmpty()) sqlwhere += " AND "; QVariant val(data->at(pkeyFieldsOrder.at(i))); if (val.isNull() || !val.isValid()) { m_result = KDbResult(ERR_DELETE_NULL_PKEY_FIELD, tr("Primary key's field \"%1\" cannot be empty.").arg(f->name())); //js todo: pass the field's name somewhere! return false; } sqlwhere += KDbEscapedString(escapeIdentifier(f->name())) + '=' + d->driver->valueToSQL(f, val); i++; } } else {//use RecordId sqlwhere = KDbEscapedString(escapeIdentifier(d->driver->beh->ROW_ID_FIELD_NAME)) + '=' + d->driver->valueToSQL(KDbField::BigInteger, (*data)[data->size() - 1]); } sql += sqlwhere; //kdbDebug() << " -- SQL == " << sql; if (!executeVoidSQL(sql)) { m_result = KDbResult(ERR_DELETE_SERVER_ERROR, tr("Record deletion on the server failed.")); return false; } return true; } bool KDbConnection::deleteAllRecords(KDbQuerySchema* query) { clearResult(); KDbTableSchema *mt = query->masterTable(); if (!mt) { kdbWarning() << " -- NO MASTER TABLE!"; return false; } KDbIndexSchema *pkey = mt->primaryKey(); if (!pkey || pkey->fields()->isEmpty()) { kdbWarning() << "-- WARNING: NO MASTER TABLE's PKEY"; } KDbEscapedString sql = KDbEscapedString("DELETE FROM ") + escapeIdentifier(mt->name()); //kdbDebug() << "-- SQL == " << sql; if (!executeVoidSQL(sql)) { m_result = KDbResult(ERR_DELETE_SERVER_ERROR, tr("Record deletion on the server failed.")); return false; } return true; } KDbConnectionOptions* KDbConnection::options() { return &d->options; } void KDbConnection::addCursor(KDbCursor* cursor) { d->cursors.insert(cursor); } void KDbConnection::takeCursor(KDbCursor* cursor) { if (d && !d->cursors.isEmpty()) { // checking because this may be called from ~KDbConnection() d->cursors.remove(cursor); } } KDbPreparedStatement KDbConnection::prepareStatement(KDbPreparedStatement::Type type, KDbFieldList* fields, const QStringList& whereFieldNames) { //! @todo move to ConnectionInterface just like we moved execute() and prepare() to KDbPreparedStatementInterface... KDbPreparedStatementInterface *iface = prepareStatementInternal(); if (!iface) return KDbPreparedStatement(); return KDbPreparedStatement(iface, type, fields, whereFieldNames); } KDbEscapedString KDbConnection::recentSQLString() const { return result().errorSql().isEmpty() ? m_result.sql() : result().errorSql(); } KDbEscapedString KDbConnection::escapeString(const QString& str) const { return d->driver->escapeString(str); } //! @todo extraMessages #if 0 static const char *extraMessages[] = { QT_TRANSLATE_NOOP("KDbConnection", "Unknown error.") }; #endif diff --git a/src/KDbConnection.h b/src/KDbConnection.h index 0ae5d956..66cc3bda 100644 --- a/src/KDbConnection.h +++ b/src/KDbConnection.h @@ -1,1286 +1,1286 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_CONNECTION_H #define KDB_CONNECTION_H #include "KDbCursor.h" #include "KDbDriver.h" #include "KDbPreparedStatement.h" #include "KDbTableSchema.h" #include "KDbTransaction.h" #include "KDbTristate.h" class KDbConnectionPrivate; class KDbConnectionData; class KDbConnectionOptions; class KDbConnectionProxy; class KDbDriver; class KDbProperties; class KDbRecordData; class KDbRecordEditBuffer; class KDbServerVersionInfo; class KDbSqlResult; class KDbTableSchemaChangeListener; class KDbVersionInfo; /*! @short Provides database connection, allowing queries and data modification. This class represents a database connection established within a data source. It supports data queries and modification by creating client-side database cursors. Database transactions are supported. */ class KDB_EXPORT KDbConnection : public KDbResultable { Q_DECLARE_TR_FUNCTIONS(KDbConnection) public: /*! Opened connection is automatically disconnected and removed from driver's connections list. Note for driver developers: you should call destroy() from you KDbConnection's subclass destructor. */ - virtual ~KDbConnection(); + ~KDbConnection() override; /*! @return parameters that were used to create this connection. */ KDbConnectionData data() const; /*! @return the driver used for this connection. */ KDbDriver* driver() const; /*! @brief Connects to driver with given parameters. @return true if successful. Note: many database drivers may require connData.databaseName() to be specified because explicit database name is needed to perform connection (e.g. SQLite, PostgreSQL). MySQL does not require database name; KDbConnection::useDatabase() can be called later. */ bool connect(); /*! @return true, if connection is properly established. */ bool isConnected() const; /*! @return true, both if connection is properly established and any database within this connection is properly used with useDatabase(). */ bool isDatabaseUsed() const; /*! @return generic options for a single connection. The options are accessible using key/value pairs. This enables extensibility depending on driver's type and version. */ KDbConnectionOptions *options(); /*! Reimplemented, also clears sql string. @sa recentSQLString() */ void clearResult(); /*! @brief Disconnects from driver with given parameters. The database (if used) is closed, and any active transactions (if supported) are rolled back, so commit these before disconnecting, if you'd like to save your changes. */ bool disconnect(); /*! @return list of database names for opened connection. If @a also_system_db is true, the system database names are also returned. */ QStringList databaseNames(bool also_system_db = false); /*! @return true if database @a dbName exists. If @a ignoreErrors is true, error flag of connection won't be modified for any errors (it will quietly return), else (ignoreErrors == false) we can check why the database does not exist using error(), errorNum() and/or errorMsg(). */ bool databaseExists(const QString &dbName, bool ignoreErrors = true); /*! @brief Creates new database with name @a dbName, using this connection. If database with @a dbName already exists, or other error occurred, false is returned. For file-based drivers, @a dbName should be equal to the database filename (the same as specified for KDbConnectionData). See docs/kdb_issues.txt document, chapter "Table schema, query schema, etc. storage" for database schema documentation (detailed description of kexi__* 'system' tables). @see useDatabase() */ bool createDatabase(const QString &dbName); /*! @brief Opens an existing database specified by @a dbName. If @a kexiCompatible is true (the default) initial checks will be performed to recognize database Kexi-specific format. Set @a kexiCompatible to false if you're using native database (one that have no Kexi System tables). For file-based drivers, @a dbName can be skipped, so the same as specified for KDbConnectionData is used. @return true on success, false on failure. If user has cancelled this action and @a cancelled is not 0, *cancelled is set to true. */ - bool useDatabase(const QString &dbName = QString(), bool kexiCompatible = true, bool *cancelled = 0, - KDbMessageHandler* msgHandler = 0); + bool useDatabase(const QString &dbName = QString(), bool kexiCompatible = true, bool *cancelled = nullptr, + KDbMessageHandler* msgHandler = nullptr); /*! @brief Closes currently used database for this connection. Any active transactions (if supported) are rolled back, so commit these before closing, if you'd like to save your changes. */ bool closeDatabase(); /*! @brief Get the name of the current database @return name of currently used database for this connection or empty string if there is no used database */ QString currentDatabase() const; /*! @brief Drops database with name @a dbName. if dbName is not specified, currently used database name is used (it is closed before dropping). */ bool dropDatabase(const QString &dbName = QString()); /*! @return names of all the @a objectType (see @a ObjectType in KDbGlobal.h) schemas stored in currently used database. KDb::AnyObjectType can be passed as @a objectType to get names of objects of any type. The list ordered is based on object identifiers. Only names that are identifiers (checked using KDb::isIdentifier()) are returned. If @a ok is not 0 then variable pointed by it will be set to the result. On error, the function returns empty list. @see kdbSystemTableNames() tableNames(int,bool*) */ - QStringList objectNames(int objectType = KDb::AnyObjectType, bool* ok = 0); + QStringList objectNames(int objectType = KDb::AnyObjectType, bool* ok = nullptr); /*! @return names of all table schemas stored in currently used database. If @a alsoSystemTables is true, internal KDb system table names (kexi__*) are also returned. The list ordered is based on object identifiers. Only names that are identifiers (checked using KDb::isIdentifier()) are returned. If @a ok is not 0 then variable pointed by it will be set to the result. On error, the function returns empty list. @see kdbSystemTableNames() objectNames(int,bool*) */ - QStringList tableNames(bool alsoSystemTables = false, bool* ok = 0); + QStringList tableNames(bool alsoSystemTables = false, bool* ok = nullptr); /*! @return true if table with name @a tableName exists in the database. @return @c false if it does not exist or @c cancelled if error occurred. The lookup is case insensitive. This method can be much faster than tableNames(). */ tristate containsTable(const QString &tableName); /*! @return list of internal KDb system table names (kexi__*). This does not mean that these tables can be found in currently opened database. Just static list of table names is returned. The list contents may depend on KDb library version; opened database can contain fewer 'system' tables than in current KDb implementation, if the current one is newer than the one used to build the database. @todo this will depend on KDb lib version */ static QStringList kdbSystemTableNames(); /*! @return server version information for this connection. If database is not connected (i.e. isConnected() is false) null KDbServerVersionInfo is returned. */ KDbServerVersionInfo serverVersion() const; /*! @return version information for this connection. If database is not used (i.e. isDatabaseUsed() is false) null KDbVersionInfo is returned. It can be compared to drivers' and KDb library version to maintain backward/upward compatiblility. */ KDbVersionInfo databaseVersion() const; /*! @return KDbProperties object allowing to read and write global database properties for this connection. */ KDbProperties databaseProperties() const; /*! @return ids of all table schema names stored in currently used database. These ids can be later used as argument for tableSchema(). This is a shortcut for objectIds(KDb::TableObjectType). Internal KDb system tables (kexi__*) are not available here because these have no identifiers assigned (more formally: id=-1). If @a ok is not 0 then variable pointed by it will be set to the result. @note The fact that given id is on the returned list does not mean that tableSchema( id ) returns anything. The table definition can be broken, so you have to double check this. Only IDs of objects with names that are identifiers (checked using KDb::isIdentifier()) are returned. @see queryIds() */ - QList tableIds(bool* ok = 0); + QList tableIds(bool* ok = nullptr); /*! @return ids of all database query schemas stored in currently used database. These ids can be later used as argument for querySchema(). This is a shortcut for objectIds(KDb::QueryObjectType). If @a ok is not 0 then variable pointed by it will be set to the result. @note The fact that given id is on the returned list does not mean that querySchema( id ) returns anything. The query definition can be broken, so you have to double check this. Only IDs of objects with names that are identifiers (checked using KDb::isIdentifier()) are returned. @see tableIds() */ - QList queryIds(bool* ok = 0); + QList queryIds(bool* ok = nullptr); /*! @return names of all schemas of object with @a objectType type that are stored in currently used database. If @a ok is not 0 then variable pointed by it will be set to the result. @note The fact that given id is on the returned list does not mean that the definition of the object is valid, so you have to double check this. Only IDs of objects with names that are identifiers (checked using KDb::isIdentifier()) are returned. @see tableIds() queryIds() */ - QList objectIds(int objectType, bool* ok = 0); + QList objectIds(int objectType, bool* ok = nullptr); /*! @brief Creates new KDbTransaction handle and starts a new transaction. @return KDbTransaction object if transaction has been started successfully, otherwise null transaction. For drivers that allow single transaction per connection (KDbDriver::features() && SingleTransactions) this method can be called one time, and then this single transaction will be default ( setDefaultTransaction() will be called). For drivers that allow multiple transactions per connection, no default transaction is set automatically in beginTransaction() method, you could do this by hand. @see setDefaultTransaction(), defaultTransaction(). */ KDbTransaction beginTransaction(); /*! @todo for nested transactions: Tansaction* beginTransaction(transaction *parent_transaction); */ /*! Commits transaction @a trans. If there is not @a trans argument passed, and there is default transaction (obtained from defaultTransaction()) defined, this one will be committed. If default is not present, false is returned (when ignore_inactive is false, the default), or true is returned (when ignore_inactive is true). On successful commit, @a trans object will be destroyed. If this was default transaction, there is no default transaction for now. */ bool commitTransaction(KDbTransaction trans = KDbTransaction(), bool ignore_inactive = false); /*! Rollbacks transaction @a trans. If there is not @a trans argument passed, and there is default transaction (obtained from defaultTransaction()) defined, this one will be rolled back. If default is not present, false is returned (when ignore_inactive is false, the default), or true is returned (when ignore_inactive is true). or any error occurred, false is returned. On successful rollback, @a trans object will be destroyed. If this was default transaction, there is no default transaction for now. */ bool rollbackTransaction(KDbTransaction trans = KDbTransaction(), bool ignore_inactive = false); /*! @return handle for default transaction for this connection or null transaction if there is no such a transaction defined. If transactions are supported: Any operation on database (e.g. inserts) that is started without specifying transaction context, will be performed in the context of this transaction. Returned null transaction doesn't mean that there is no transactions started at all. Default transaction can be defined automatically for some drivers -- see beginTransaction(). @see KDbDriver::transactionsSupported() */ KDbTransaction defaultTransaction() const; /*! Sets default transaction that will be used as context for operations on data in opened database for this connection. */ void setDefaultTransaction(const KDbTransaction& trans); /*! @return set of handles of currently active transactions. Note that in multithreading environment some of these transactions can be already inactive after calling this method. Use KDbTransaction::active() to check that. Inactive transaction handle is useless and can be safely dropped. */ QList transactions(); /*! @return true if "auto commit" option is on. When auto commit is on (the default on for any new KDbConnection object), every sql functional statement (statement that changes data in the database implicitly starts a new transaction. This transaction is automatically committed after successful statement execution or rolled back on error. For drivers that do not support transactions (see KDbDriver::features()) this method shouldn't be called because it does nothing ans always returns false. No internal KDb object should changes this option, although auto commit's behavior depends on database engine's specifics. Engines that support only single transaction per connection (see KDbDriver::SingleTransactions), use this single connection for autocommiting, so if there is already transaction started by the KDb user program (with beginTransaction()), this transaction is committed before any sql functional statement execution. In this situation default transaction is also affected (see defaultTransaction()). Only for drivers that support nested transactions (KDbDriver::NestedTransactions), autocommiting works independently from previously started transaction, For other drivers set this option off if you need use transaction for grouping more statements together. NOTE: nested transactions are not yet implemented in KDb API. */ bool autoCommit() const; /*! Changes auto commit option. This does not affect currently started transactions. This option can be changed even when connection is not established. @see autoCommit() */ bool setAutoCommit(bool on); /*! Connection-specific string escaping. Default implementation uses driver's escaping. Use KDbEscapedString::isValid() to check if escaping has been performed successfully. Invalid strings are set to null in addition, that is KDbEscapedString::isNull() is true, not just isEmpty(). */ virtual KDbEscapedString escapeString(const QString& str) const; /*! Prepares SELECT query described by a raw statement @a sql. @return opened cursor created for results of this query or @c nullptr if there was any error. Ownership of the returned object is passed to the caller. KDbCursor can have optionally applied @a options (one of more selected from KDbCursor::Options). Preparation means that returned cursor is created but not opened. Open this when you would like to do it with KDbCursor::open(). Note for driver developers: you should initialize cursor engine-specific resources and return KDbCursor subclass' object (passing @a sql and @a options to its constructor). */ virtual KDbCursor* prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT = 0; /*! @overload Prepares query described by @a query schema. @a params are values of parameters that will be inserted into places marked with [] before execution of the query. Note for driver developers: you should initialize cursor engine-specific resources and return KDbCursor subclass' object (passing @a query and @a options to it's constructor). Kexi SQL and driver-specific escaping is performed on table names. */ KDbCursor* prepareQuery(KDbQuerySchema* query, const QList& params, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! @overload Prepares query described by @a query schema without parameters. */ virtual KDbCursor* prepareQuery(KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT = 0; /*! @overload Statement is build from data provided by @a table schema, it is like "select * from table_name".*/ KDbCursor* prepareQuery(KDbTableSchema* table, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! Executes SELECT query described by a raw SQL statement @a sql. @return opened cursor created for results of this query or 0 if there was any error on the cursor creation or opening. Ownership of the returned object is passed to the caller. KDbCursor can have optionally applied @a options. Identifiers in @a sql that are the same as keywords in KDbSQL dialect or the backend's SQL have to be escaped. */ KDbCursor* executeQuery(const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! @overload executeQuery(const KDbEscapedString&, int) @a params are values of parameters that will be inserted into places marked with [] before execution of the query. Statement is build from data provided by @a query schema. Kexi SQL and driver-specific escaping is performed on table names. */ KDbCursor* executeQuery(KDbQuerySchema* query, const QList& params, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! @overload */ KDbCursor* executeQuery(KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! @overload Executes query described by @a query schema without parameters. Statement is build from data provided by @a table schema, it is like "select * from table_name".*/ KDbCursor* executeQuery(KDbTableSchema* table, KDbCursor::Options options = KDbCursor::Option::None) Q_REQUIRED_RESULT; /*! Deletes cursor @a cursor previously created by functions like executeQuery() for this connection. There is an attempt to close the cursor with KDbCursor::close() if it was opened. Anyway, at last cursor is deleted. @return true if cursor is properly closed before deletion. */ bool deleteCursor(KDbCursor *cursor); /*! @return schema of a table pointed by @a tableId, retrieved from currently used database. The schema is cached inside connection, so retrieval is performed only once, on demand. */ KDbTableSchema* tableSchema(int tableId); /*! @return schema of a table pointed by @a tableName, retrieved from currently used database. KDb system table schema can be also retrieved. @see tableSchema( int tableId ) */ KDbTableSchema* tableSchema(const QString& tableName); /*! @return schema of a query pointed by @a queryId, retrieved from currently used database. The schema is cached inside connection, so retrieval is performed only once, on demand. */ KDbQuerySchema* querySchema(int queryId); /*! @return schema of a query pointed by @a queryName, retrieved from currently used database. @see querySchema( int queryId ) */ KDbQuerySchema* querySchema(const QString& queryName); /*! Sets @a queryName query obsolete by moving it out of the query sets, so it will not be accessible by querySchema( const QString& queryName ). The existing query object is not destroyed, to avoid problems when it's referenced. In this case, a new query schema will be retrieved directly from the backend. For now it's used in KexiQueryDesignerGuiEditor::storeLayout(). This solves the problem when user has changed a query schema but already form still uses previously instantiated query schema. @return true if there is such query. Otherwise the method does nothing. */ bool setQuerySchemaObsolete(const QString& queryName); /*! Executes query for a raw statement @a sql and stores first record's data inside @a data. This is a convenient method when we need only first record from query result, or when we know that query result has only one record. If @a addLimitTo1 is true (the default), adds a LIMIT clause to the query, so @a sql should not include one already. @return true if query was successfully executed and first record has been found, false on data retrieving failure, and cancelled if there's no single record available. */ tristate querySingleRecord(const KDbEscapedString& sql, KDbRecordData* data, bool addLimitTo1 = true); /*! @overload tristate querySingleRecord(const KDbEscapedString& sql, KDbRecordData* data, bool addLimitTo1) Uses a KDbQuerySchema object. */ tristate querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, bool addLimitTo1 = true); /*! @overload tristate querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, bool addLimitTo1) Accepts @a params as parameters that will be inserted into places marked with [] before query execution. */ tristate querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, const QList& params, bool addLimitTo1 = true); /*! Executes query for a raw statement @a sql and stores first record's field's (number @a column) string value inside @a value. For efficiency it's recommended that a query defined by @a sql should have just one field (SELECT one_field FROM ....). If @a addLimitTo1 is true (the default), adds a LIMIT clause to the query, so @a sql should not include one already. @return true if query was successfully executed and first record has been found, false on data retrieving failure, and cancelled if there's no single record available. @see queryStringList() */ tristate querySingleString(const KDbEscapedString& sql, QString* value, int column = 0, bool addLimitTo1 = true); /*! @overload tristate querySingleString(const KDbEscapedString& sql, QString* value, int column, bool addLimitTo1) Uses a KDbQuerySchema object. */ tristate querySingleString(KDbQuerySchema* query, QString* value, int column = 0, bool addLimitTo1 = true); /*! @overload tristate querySingleString(QuerySchema* query, QString* value, int column, bool addLimitTo1) Accepts @a params as parameters that will be inserted into places marked with [] before query execution. */ tristate querySingleString(KDbQuerySchema* query, QString* value, const QList& params, int column = 0, bool addLimitTo1 = true); /*! Convenience function: executes query for a raw SQL statement @a sql and stores first record's field's (number @a column) value inside @a number. @see querySingleString(). Note: "LIMIT 1" is appended to @a sql statement if @a addLimitTo1 is true (the default). @return true if query was successfully executed and first record has been found, false on data retrieving failure, and cancelled if there's no single record available. */ tristate querySingleNumber(const KDbEscapedString& sql, int* number, int column = 0, bool addLimitTo1 = true); /*! @overload tristate querySingleNumber(const KDbEscapedString& sql, int* number, int column, bool addLimitTo1) Uses a KDbQuerySchema object. */ tristate querySingleNumber(KDbQuerySchema* query, int* number, int column = 0, bool addLimitTo1 = true); /*! @overload tristate querySingleNumber(KDbQuerySchema* query, int* number, int column, bool addLimitTo1) Accepts @a params as parameters that will be inserted into places marked with [] before query execution. */ tristate querySingleNumber(KDbQuerySchema* query, int* number, const QList& params, int column = 0, bool addLimitTo1 = true); /*! Executes query for a raw SQL statement @a sql and stores Nth field's string value of every record inside @a list, where N is equal to @a column. The list is initially cleared. For efficiency it's recommended that a query defined by @a sql should have just one field (SELECT one_field FROM ....). @return true if all values were fetched successfuly, false on data retrieving failure. Returning empty list can be still a valid result. On errors, the list is not cleared, it may contain a few retrieved values. */ bool queryStringList(const KDbEscapedString& sql, QStringList* list, int column = 0); /*! @overload tristate queryStringList(const KDbEscapedString& sql, QStringList* list, int column) Uses a QuerySchema object. */ bool queryStringList(KDbQuerySchema* query, QStringList* list, int column = 0); /*! @overload tristate queryStringList(KDbQuerySchema* query, QStringList* list, int column) Accepts @a params as parameters that will be inserted into places marked with [] before query execution. */ bool queryStringList(KDbQuerySchema* query, QStringList* list, const QList& params, int column = 0); /*! @return @c true if there is at least one record has been returned by executing query for a raw SQL statement @a sql or @c false if no such record exists. Does not fetch any records. On error returns @c cancelled. Note: real executed query is: "SELECT 1 FROM (@a sql) LIMIT 1" if @a addLimitTo1 is true (the default). */ tristate resultExists(const KDbEscapedString& sql, bool addLimitTo1 = true); /*! @return true if there is at least one record in @a table. */ tristate isEmpty(KDbTableSchema* table); virtual KDbEscapedString recentSQLString() const; //PROTOTYPE: #define A , const QVariant& #define H_INS_REC(args) bool insertRecord(KDbTableSchema* tableSchema args, KDbSqlResult** result = 0) #define H_INS_REC_ALL \ H_INS_REC(A); \ H_INS_REC(A A); \ H_INS_REC(A A A); \ H_INS_REC(A A A A); \ H_INS_REC(A A A A A); \ H_INS_REC(A A A A A A); \ H_INS_REC(A A A A A A A); \ H_INS_REC(A A A A A A A A) H_INS_REC_ALL; #undef H_INS_REC #define H_INS_REC(args) bool insertRecord(KDbFieldList* fields args, KDbSqlResult** result = 0) H_INS_REC_ALL; #undef H_INS_REC_ALL #undef H_INS_REC #undef A - bool insertRecord(KDbTableSchema* tableSchema, const QList& values, KDbSqlResult** result = 0); + bool insertRecord(KDbTableSchema* tableSchema, const QList& values, KDbSqlResult** result = nullptr); - bool insertRecord(KDbFieldList* fields, const QList& values, KDbSqlResult** result = 0); + bool insertRecord(KDbFieldList* fields, const QList& values, KDbSqlResult** result = nullptr); /*! Creates table defined by @a tableSchema. Schema information is also added into kexi system tables, for later reuse. @return true on success - @a tableSchema object is then inserted to KDbConnection structures - it is owned by KDbConnection object now, so you shouldn't destroy the tableSchema object by hand (or declare it as local-scope variable). If @a replaceExisting is false (the default) and table with the same name (as tableSchema->name()) exists, false is returned. If @a replaceExisting is true, a table schema with the same name (if exists) is overwritten, then a new table schema gets the same identifier as existing table schema's identifier. Table and column definitions are added to to kexi__* "system schema" tables. Checks that a database is in use, and that the schema defines at least one column. Note that on error: - @a tableSchema is not inserted into KDbConnection's structures, so you are still owner of this object - existing table schema object is not destroyed (i.e. it is still available e.g. using KDbConnection::tableSchema(const QString&), even if the table was physically dropped. */ bool createTable(KDbTableSchema* tableSchema, bool replaceExisting = false); /*! Creates a copy of table schema defined by @a tableSchema with data. Name, caption and description will be copied from @a newData. @return a table schema object. It is inserted into the KDbConnection structures and is owned by the KDbConnection object. The created table schema object should not be destroyed by hand afterwards. 0 is returned on failure. Table with destination name must not exist. @see createTable() */ KDbTableSchema *copyTable(const KDbTableSchema &tableSchema, const KDbObject &newData); /*! It is a convenience function, does exactly the same as KDbTableSchema *copyTable(const KDbTableSchema&, const KDbObject&). */ KDbTableSchema *copyTable(const QString& tableName, const KDbObject &newData); /*! Drops a table defined by @a tableSchema (both table object as well as physically). If true is returned, schema information @a tableSchema is destoyed (because it's owned), so don't keep this anymore! No error is raised if the table does not exist physically - its schema is removed even in this case. Removes the table and column definitions in kexi__* "system schema" tables. First checks that the table is not a system table. @todo Check that a database is currently in use? (c.f. createTable) @todo Update any structure (e.g. query) that depends on this table */ tristate dropTable(KDbTableSchema* tableSchema); /*! It is a convenience function, does exactly the same as bool dropTable( KDbTableSchema* tableSchema ) */ tristate dropTable(const QString& tableName); /*! Alters @a tableSchema using @a newTableSchema in memory and on the db backend. @return true on success, cancelled if altering was cancelled. */ //! @todo (js): implement real altering //! @todo (js): update any structure (e.g. query) that depend on this table! tristate alterTable(KDbTableSchema* tableSchema, KDbTableSchema* newTableSchema); /*! Alters name of table described by @a tableSchema to @a newName. If @a replace is true, destination table is completely dropped and replaced by @a tableSchema, if present. In this case, identifier of @a tableSchema becomes equal to the dropped table's id, what can be useful if @a tableSchema was created with a temporary name and ID (used in KDbAlterTableHandler). If @a replace is false (the default) and destination table is present -- false is returned and ERR_OBJECT_EXISTS error is set. The schema of @a tableSchema is updated on success. @return true on success. */ bool alterTableName(KDbTableSchema* tableSchema, const QString& newName, bool replace = false); /*! Drops a query defined by @a querySchema. If true is returned, schema information @a querySchema is destoyed (because it's owned), so don't keep this anymore! */ bool dropQuery(KDbQuerySchema* querySchema); /*! It is a convenience function, does exactly the same as bool dropQuery( KDbQuerySchema* querySchema ) */ bool dropQuery(const QString& queryName); /*! Removes information about object with @a objId from internal "kexi__object" and "kexi__objectdata" tables. @return true on success. */ bool removeObject(int objId); /*! @return first field from @a fieldlist that has system name, null if there are no such field. For checking, KDbDriver::isSystemFieldName() is used, so this check can be driver-dependent. */ KDbField* findSystemFieldName(const KDbFieldList& fieldlist); /*! @return name of any (e.g. first found) database for this connection. This method does not close or open this connection. The method can be used (it is also internally used, e.g. for database dropping) when we need a database name before we can connect and execute any SQL statement (e.g. DROP DATABASE). The method can return nul lstring, but in this situation no automatic (implicit) connections could be made, what is useful by e.g. dropDatabase(). Note for driver developers: return here a name of database which you are sure is existing. Default implementation returns: - value that previously had been set using setAvailableDatabaseName() for this connection, if it is not empty - else (2nd priority): value of KDbDriverBehavior::ALWAYS_AVAILABLE_DATABASE_NAME if it is not empty. See description of KDbDriverBehavior::ALWAYS_AVAILABLE_DATABASE_NAME member. You may want to reimplement this method only when you need to depend on this connection specifics (e.g. you need to check something remotely). */ virtual QString anyAvailableDatabaseName(); /*! Sets @a dbName as name of a database that can be accessible. This is option that e.g. application that make use of KDb library can set to tune connection's behavior when it needs to temporary connect to any database in the server to do some work. You can pass empty dbName - then anyAvailableDatabaseName() will try return KDbDriverBehavior::ALWAYS_AVAILABLE_DATABASE_NAME (the default) value instead of the one previously set with setAvailableDatabaseName(). @see anyAvailableDatabaseName() */ void setAvailableDatabaseName(const QString& dbName); /*! Because some engines need to have opened any database before executing administrative SQL statements like "create database" or "drop database", this method is used to use appropriate, existing database for this connection. For file-based db drivers this always return true and does not set @a name to any value. For other db drivers: this sets @a name to db name computed using anyAvailableDatabaseName(), and if the name computed is empty, false is returned; if it is not empty, useDatabase() is called. False is returned also when useDatabase() fails. You can call this method from your application's level if you really want to perform tasks that require any used database. In such a case don't forget to closeDatabase() if returned @a name is not empty. Note: This method has nothing to do with creating or using temporary databases in such meaning that these database are not persistent */ bool useTemporaryDatabaseIfNeeded(QString* name); /*! Executes a new native (raw, backend-specific) SQL query for statement @a sql. * Only use this method in cases if a non-portable raw query is required. * Access to results can be obtained using the returned KDbSqlResult object. * @c nullptr is returned on failure. Use result() to obtain detailed status information. * The KDbConnection object is owner of the returned object. Before closing, the * connection object deletes all its owned KDbSqlResult objects. It is also possible * and recommended that caller deletes the KDbSqlResult object as soon as the result * is not needed. * If the query is not supposed to return records (e.g. is a functional query) * or the caller is not interested in the records, the returned object can be just deleted. * In this case executeVoidSQL() can be a better choice. * Using QScopedPointer construct may simplify memory management of the * returned object. */ KDbSqlResult* executeSQL(const KDbEscapedString& sql) Q_REQUIRED_RESULT; /*! Executes a new native (raw, backend-specific) SQL query for statement @a sql. * This method is equivalent of executeSQL() useful for cases when the query is not * supposed to return records (e.g. is a functional query) or if the caller is not * interested in the records. */ bool executeVoidSQL(const KDbEscapedString& sql); /*! Stores object (id, name, caption, description) described by @a object on the backend. It is expected that entry on the backend already exists, so it's updated. Changes to identifier attribute are not allowed. @return true on success. */ bool storeObjectData(KDbObject* object); /*! Stores new entry for object (id, name, caption, description) described by @a object on the backend. If object.id() was less than 0, new, unique object identifier is obtained and assigned to @a object (see KDbObject::id()). @return true on success. */ bool storeNewObjectData(KDbObject* object); /*! Added for convenience. @see setupObjectData(const KDbRecordData*, KDbObject*). @return true on success, false on failure and cancelled when such object couldn't be found. */ tristate loadObjectData(int id, KDbObject* object); /*! Finds object data for object of type @a type and name @a name. If the object is found, resulted schema is stored in @a object and true is returned, otherwise false is returned. */ tristate loadObjectData(int type, const QString& name, KDbObject* object); /*! Loads (potentially large) data block (e.g. xml form's representation), referenced by objectID and puts it to @a dataString. The can be block indexed with optional @a dataID. @return true on success, false on failure and cancelled when there is no such data block @see storeDataBlock(). */ tristate loadDataBlock(int objectID, QString* dataString, const QString& dataID = QString()); /*! Stores (potentially large) data block @a dataString (e.g. xml form's representation), referenced by objectID. Block will be stored in "kexi__objectdata" table and an optional @a dataID identifier. If there is already such record in the table, it's simply overwritten. @return true on success @see loadDataBlock() removeDataBlock() copyDataBlock(). */ bool storeDataBlock(int objectID, const QString &dataString, const QString& dataID = QString()); /*! Copies (potentially large) data, e.g. form's XML representation, referenced by @a sourceObjectID pointed by optional @a dataID. @return true on success. Does not fail if blocks do not exist. Prior to copying, existing data blocks are removed even if there are no new blocks to copy. Copied data blocks will have @a destObjectID object identifier assigned. Note that if @a dataID is not specified, all data blocks found for the @a sourceObjectID will be copied. @see loadDataBlock() storeDataBlock() removeDataBlock(). */ bool copyDataBlock(int sourceObjectID, int destObjectID, const QString& dataID = QString()); /*! Removes (potentially large) string data (e.g. xml form's representation), referenced by @a objectID, and pointed by optional @a dataID. @return true on success. Does not fail if the block does not exist. Note that if @a dataID is not specified, all data blocks for the @a objectID will be removed. @see loadDataBlock() storeDataBlock() copyDataBlock(). */ bool removeDataBlock(int objectID, const QString& dataID = QString()); /*! Prepare an SQL statement and return a @a KDbPreparedStatement instance. */ KDbPreparedStatement prepareStatement(KDbPreparedStatement::Type type, KDbFieldList* fields, const QStringList& whereFieldNames = QStringList()); bool isInternalTableSchema(const QString& tableName); //! Identifier escaping function in the associated KDbDriver. /*! Calls the identifier escaping function in this connection to escape table and column names. This should be used when explicitly constructing SQL strings (e.g. "FROM " + escapeIdentifier(tablename)). It should not be used for other functions (e.g. don't do useDatabase(escapeIdentifier(database))), because the identifier will be escaped when the called function generates, for example, "USE " + escapeIdentifier(database). For efficiency, KDb "system" tables (prefixed with kexi__) and columns therein are not escaped - we assume these are valid identifiers for all drivers. Use KDbEscapedString::isValid() to check if escaping has been performed successfully. Invalid strings are set to null in addition, that is KDbEscapedString::isNull() is true, not just isEmpty(). */ virtual QString escapeIdentifier(const QString& id) const; protected: /*! Used by KDbDriver */ KDbConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options); /*! Method to be called form KDbConnection's subclass destructor. @see ~KDbConnection() */ void destroy(); /*! For implementation: connects to database. @return true on success. */ virtual bool drv_connect() = 0; /*! For implementation: disconnects database @return true on success. */ virtual bool drv_disconnect() = 0; /*! For implementation: Sets @a version to real server's version. Depending on backend type this method is called after (if KDbDriverBehavior::USING_DATABASE_REQUIRED_TO_CONNECT is true) or before database is used (if KDbDriverBehavior::USING_DATABASE_REQUIRED_TO_CONNECT is false), i.e. for PostgreSQL it is called after. In any case it is called after successful drv_connect(). @return true on success. */ virtual bool drv_getServerVersion(KDbServerVersionInfo* version) = 0; /*! LOW LEVEL METHOD. For implementation: returns true if table with name @a tableName exists in the database. @return @c false if it does not exist or @c cancelled if error occurred. The lookup is case insensitive. */ virtual tristate drv_containsTable(const QString &tableName) = 0; /*! Creates table using @a tableSchema information. @return true on success. Default implementation builds a statement using createTableStatement() and calls drv_executeSQL() Note for driver developers: reimplement this only if you want do to this in other way. */ virtual bool drv_createTable(const KDbTableSchema& tableSchema); /*! Alters table's described @a tableSchema name to @a newName. This is the default implementation, using "ALTER TABLE RENAME TO ", what's supported by SQLite >= 3.2, PostgreSQL, MySQL. Backends lacking ALTER TABLE, for example SQLite2, reimplement this with by an inefficient data copying to a new table. In any case, renaming is performed at the backend. It's good idea to keep the operation within a transaction. @return true on success. */ virtual bool drv_alterTableName(KDbTableSchema* tableSchema, const QString& newName); /*! Copies table data from @a tableSchema to @a destinationTableSchema Default implementation executes "INSERT INTO .. SELECT * FROM .." @return true on success. */ virtual bool drv_copyTableData(const KDbTableSchema &tableSchema, const KDbTableSchema &destinationTableSchema); /*! Physically drops table named with @a name. Default impelmentation executes "DROP TABLE.." command, so you rarely want to change this. */ virtual bool drv_dropTable(const QString& tableName); /*! @internal drops table @a tableSchema physically, but destroys @a tableSchema object only if @a alsoRemoveSchema is true. Used (alsoRemoveSchema==false) on table altering: if recreating table can fail we're giving up and keeping the original table schema (even if it is no longer points to any real data). */ tristate dropTable(KDbTableSchema* tableSchema, bool alsoRemoveSchema); /*! Setups data for object that owns @a object (e.g. table, query) opened on 'kexi__objects' table, pointing to a record corresponding to given object. */ bool setupObjectData(const KDbRecordData& data, KDbObject* object); /*! @return a new field table schema for a table retrieved from @a data. Ownership of the returned object is passed to the caller. Used internally by tableSchema(). */ KDbField* setupField(const KDbRecordData& data) Q_REQUIRED_RESULT; /*! Executes query for a raw SQL statement @a sql with possibility of returning records. It is useful mostly for functional (SELECT) queries. While INSERT queries do not return records, the KDbSqlResult object offers KDbSqlResult::lastInsertRecordId(). The @sql should be is valid and properly escaped. Only use this method if you really need. For low-level access to the results (without cursors). The result may be not stored (not buffered) yet. Use KDbSqlResult::fetchRecord() to fetch each record. @return @c nullptr if there is no proper result. Ownership of the returned object is passed to the caller. @see executeSQL */ virtual KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) Q_REQUIRED_RESULT = 0; /*! Executes query for a raw SQL statement @a sql without returning resulting records. It is useful mostly for INSERT queries but it is possible to execute SELECT queries when returned records can be ignored. The @sql should be is valid and properly escaped. Only use this method if you really need. @see executeVoidSQL */ virtual bool drv_executeVoidSQL(const KDbEscapedString& sql) = 0; /*! Uses result of execution of raw SQL query using drv_executeSQL(). For low-level access to the results (without cursors). The result may be not stored (not buffered) yet. Use KDbSqlResult::fetchRecord() to fetch each record. @return @c nullptr if there is no proper result. Ownership of the returned object is passed to the caller. */ //virtual KDbSqlResult* drv_getSqlResult() Q_REQUIRED_RESULT = 0; /*! For reimplementation: loads list of databases' names available for this connection and adds these names to @a list. If your server is not able to offer such a list, consider reimplementing drv_databaseExists() instead. The method should return true only if there was no error on getting database names list from the server. Default implementation puts empty list into @a list and returns true. @see databaseNames */ virtual bool drv_getDatabasesList(QStringList* list); /*! For optional reimplementation: asks server if database @a dbName exists. This method is used internally in databaseExists(). The default implementation calls databaseNames and checks if that list contains @a dbName. If you need to ask the server specifically if a database exists, eg. if you can't retrieve a list of all available database names, please reimplement this method and do all needed checks. See databaseExists() description for details about ignoreErrors argument. You should use it properly in your implementation. Note: This method should also work if there is already database used (with useDatabase()); in this situation no changes should be made in current database selection. */ virtual bool drv_databaseExists(const QString &dbName, bool ignoreErrors = true); /*! For implementation: creates new database using connection */ virtual bool drv_createDatabase(const QString &dbName = QString()) = 0; /*! For implementation: opens existing database using connection @return true on success, false on failure; sets @a cancelled to true if this action has been cancelled. */ - virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = 0, - KDbMessageHandler* msgHandler = 0) = 0; + virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = nullptr, + KDbMessageHandler* msgHandler = nullptr) = 0; /*! For implementation: closes previously opened database using connection. */ virtual bool drv_closeDatabase() = 0; /*! @return true if internal driver's structure is still in opened/connected state and database is used. Note for driver developers: Put here every test that you can do using your internal engine's database API, eg (a bit schematic): my_connection_struct->isConnected()==true. Do not check things like KDbConnection::isDatabaseUsed() here or other things that "KDb already knows" at its level. If you cannot test anything, just leave default implementation (that returns true). Result of this method is used as an additional chance to check for isDatabaseUsed(). Do not call this method from your driver's code, it should be used at KDb level only. */ virtual bool drv_isDatabaseUsed() const { return true; } /*! For implementation: drops database from the server using connection. After drop, database shouldn't be accessible anymore. */ virtual bool drv_dropDatabase(const QString &dbName = QString()) = 0; /*! Creates table named by @a tableName. Schema object must be on schema tables' list before calling this method (otherwise false if returned). Just uses drv_createTable( const KDbTableSchema& tableSchema ). Used internally, e.g. in createDatabase(). @return true on success */ virtual bool drv_createTable(const QString& tableName); /*! Note for driver developers: begins new transaction and returns handle to it. Default implementation just executes "BEGIN" sql statement and returns just empty data (KDbTransactionData object). Ownership of the returned object is passed to the caller. Drivers that do not support transactions (see KDbDriver::features()) do never call this method. Reimplement this method if you need to do something more (e.g. if you driver will support multiple transactions per connection). Make subclass of KDbTransactionData (declared in KDbTransaction.h) and return object of this subclass. - You should return NULL if any error occurred. + @c nullptr should be returned on error. Do not check anything in connection (isConnected(), etc.) - all is already done. */ virtual KDbTransactionData* drv_beginTransaction() Q_REQUIRED_RESULT; /*! Note for driver developers: begins new transaction and returns handle to it. Default implementation just executes "COMMIT" sql statement and returns true on success. @see drv_beginTransaction() */ virtual bool drv_commitTransaction(KDbTransactionData* trans); /*! Note for driver developers: begins new transaction and returns handle to it. Default implementation just executes "ROLLBACK" sql statement and returns true on success. @see drv_beginTransaction() */ virtual bool drv_rollbackTransaction(KDbTransactionData* trans); /*! Preprocessing (if any) required by drivers before execution of an Insert statement. Reimplement this method in your driver if there are any special processing steps to be executed before an Insert statement. @see drv_afterInsert() */ virtual bool drv_beforeInsert(const QString& tableName, KDbFieldList* fields) { Q_UNUSED(tableName); Q_UNUSED(fields); return true; } /*! Postprocessing (if any) required by drivers before execution of an Insert statement. Reimplement this method in your driver if there are any special processing steps to be executed after an Insert statement. @see drv_beforeInsert() */ virtual bool drv_afterInsert(const QString& tableName, KDbFieldList* fields) { Q_UNUSED(tableName); Q_UNUSED(fields); return true; } /*! Preprocessing required by drivers before execution of an Update statement. Reimplement this method in your driver if there are any special processing steps to be executed before an Update statement. @see drv_afterUpdate() */ virtual bool drv_beforeUpdate(const QString& tableName, KDbFieldList* fields) { Q_UNUSED(tableName); Q_UNUSED(fields); return true; } /*! Postprocessing required by drivers before execution of an Insert statement. Reimplement this method in your driver if there are any special processing steps to be executed after an Update statement. @see drv_beforeUpdate() */ virtual bool drv_afterUpdate(const QString& tableName, KDbFieldList* fields) { Q_UNUSED(tableName); Q_UNUSED(fields); return true; } /*! Changes autocommiting option for established connection. @return true on success. Note for driver developers: reimplement this only if your engine allows to set special auto commit option (like "SET AUTOCOMMIT=.." in MySQL). If not, auto commit behavior will be simulated if at least single transactions per connection are supported by the engine. Do not set any internal flags for autocommiting -- it is already done inside setAutoCommit(). Default implementation does nothing with connection, just returns true. @see drv_beginTransaction(), autoCommit(), setAutoCommit() */ virtual bool drv_setAutoCommit(bool on); /*! Prepare an SQL statement and return a @a KDbPreparedStatementInterface-derived object. Ownership of the returned object is passed to the caller. */ virtual KDbPreparedStatementInterface* prepareStatementInternal() Q_REQUIRED_RESULT = 0; /*! Internal, for handling autocommited transactions: begins transaction if one is supported. @return true if new transaction started successfully or no transactions are supported at all by the driver or if autocommit option is turned off. A handle to a newly created transaction (or null on error) is passed to @a tg parameter. Special case when used database driver has only single transaction support (KDbDriver::SingleTransactions): and there is already transaction started, it is committed before starting a new one, but only if this transaction has been started inside KDbConnection object. (i.e. by beginAutoCommitTransaction()). Otherwise, a new transaction will not be started, but true will be returned immediately. */ bool beginAutoCommitTransaction(KDbTransactionGuard* tg); /*! Internal, for handling autocommited transactions: Commits transaction prevoiusly started with beginAutoCommitTransaction(). @return true on success or when no transactions are supported at all by the driver. Special case when used database driver has only single transaction support (KDbDriver::SingleTransactions): if @a trans has been started outside KDbConnection object (i.e. not by beginAutoCommitTransaction()), the transaction will not be committed. */ bool commitAutoCommitTransaction(const KDbTransaction& trans); /*! Internal, for handling autocommited transactions: Rollbacks transaction prevoiusly started with beginAutoCommitTransaction(). @return true on success or when no transactions are supported at all by the driver. Special case when used database driver has only single transaction support (KDbDriver::SingleTransactions): @a trans will not be rolled back if it has been started outside this KDbConnection object. */ bool rollbackAutoCommitTransaction(const KDbTransaction& trans); /*! Helper: checks if connection is established; if not: error message is set up and false returned */ bool checkConnected(); /*! Helper: checks both if connection is established and database any is used; if not: error message is set up and false returned */ bool checkIsDatabaseUsed(); /*! @return a full table schema for a table retrieved using 'kexi__*' system tables. Connection keeps ownership of the returned object. Used internally by tableSchema() methods. */ KDbTableSchema* setupTableSchema(const KDbRecordData& data) Q_REQUIRED_RESULT; /*! @return a full query schema for a query using 'kexi__*' system tables. Connection keeps ownership of the returned object. Used internally by querySchema() methods. */ KDbQuerySchema* setupQuerySchema(const KDbRecordData& data) Q_REQUIRED_RESULT; /*! Update a record. */ bool updateRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId = false); /*! Insert a new record. */ bool insertRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool getRecordId = false); /*! Delete an existing record. */ bool deleteRecord(KDbQuerySchema* query, KDbRecordData* data, bool useRecordId = false); /*! Delete all existing records. */ bool deleteAllRecords(KDbQuerySchema* query); /*! Called by KDbTableSchema -- signals destruction to KDbConnection object To avoid having deleted table object on its list. */ void removeMe(KDbTableSchema *ts); /*! @internal @return true if the cursor @a cursor contains column @a column, else, sets appropriate error with a message and returns false. */ bool checkIfColumnExists(KDbCursor *cursor, int column); /*! @internal used by insertRecord() methods. */ bool insertRecordInternal(const QString &tableSchemaName, KDbFieldList* fields, const KDbEscapedString &sql, KDbSqlResult** result); /*! @internal used by querySingleRecord() methods. Note: "LIMIT 1" is appended to @a sql statement if @a addLimitTo1 is true (the default). */ tristate querySingleRecordInternal(KDbRecordData* data, const KDbEscapedString* sql, KDbQuerySchema* query, const QList* params, bool addLimitTo1 = true); /*! @internal used by querySingleString() methods. Note: "LIMIT 1" is appended to @a sql statement if @a addLimitTo1 is true (the default). */ tristate querySingleStringInternal(const KDbEscapedString* sql, QString* value, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1); /*! @internal used by queryNumberString() methods. Note: "LIMIT 1" is appended to @a sql statement if @a addLimitTo1 is true (the default). */ tristate querySingleNumberInternal(const KDbEscapedString* sql, int* number, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1); /*! @internal used by queryStringList() methods. */ bool queryStringListInternal(const KDbEscapedString *sql, QStringList* list, KDbQuerySchema* query, const QList* params, int column, bool (*filterFunction)(const QString&)); /*! @internal used by *Internal() methods. Executes query based on a raw SQL statement @a sql or @a query with optional @a params. Ownership of the returned object is passed to the caller.*/ KDbCursor* executeQueryInternal(const KDbEscapedString& sql, KDbQuerySchema* query, const QList* params) Q_REQUIRED_RESULT; /*! Loads extended schema information for table @a tableSchema, if present (see ExtendedTableSchemaInformation in Kexi Wiki). @return true on success */ bool loadExtendedTableSchemaData(KDbTableSchema* tableSchema); /*! Stores extended schema information for table @a tableSchema, (see ExtendedTableSchemaInformation in Kexi Wiki). The action is performed within the current transaction, so it's up to you to commit. Used, e.g. by createTable(), within its transaction. @return true on success */ bool storeExtendedTableSchemaData(KDbTableSchema* tableSchema); /*! @internal Stores main field's schema information for field @a field. Used in table altering code when information in kexi__fields has to be updated. @return true on success and false on failure. */ bool storeMainFieldSchema(KDbField *field); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*! This is a part of alter table interface implementing lower-level operations used to perform table schema altering. Used by KDbAlterTableHandler. Changes value of field property. @return true on success, false on failure, cancelled if the action has been cancelled. Note for driver developers: implement this if the driver has to supprot the altering. */ virtual tristate drv_changeFieldProperty(KDbTableSchema* table, KDbField* field, const QString& propertyName, const QVariant& value) { Q_UNUSED(table); Q_UNUSED(field); Q_UNUSED(propertyName); Q_UNUSED(value); return cancelled; } //! Used by KDbCursor class void addCursor(KDbCursor* cursor); //! Used by KDbCursor class void takeCursor(KDbCursor* cursor); private: //! Internal, used by storeObjectData(KDbObject*) and storeNewObjectData(KDbObject* object). bool storeObjectDataInternal(KDbObject* object, bool newObject); //! @internal //! @return identifier escaped by driver (if @a escapingType is KDb::DriverEscaping) //! or by the KDb's built-in escape routine. QString escapeIdentifier(const QString& id, KDb::IdentifierEscapingType escapingType) const; KDbConnectionPrivate* d; //!< @internal d-pointer class. Q_DISABLE_COPY(KDbConnection) friend class KDbConnectionPrivate; friend class KDbAlterTableHandler; friend class KDbConnectionProxy; friend class KDbCursor; friend class KDbDriver; friend class KDbProperties; //!< for setError() friend class KDbTableSchemaChangeListener; friend class KDbTableSchema; //!< for removeMe() }; #endif diff --git a/src/KDbConnectionProxy.h b/src/KDbConnectionProxy.h index bdbe6485..8e15e0b2 100644 --- a/src/KDbConnectionProxy.h +++ b/src/KDbConnectionProxy.h @@ -1,397 +1,401 @@ /* This file is part of the KDE project Copyright (C) 2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_CONNECTIONPROXY_H #define KDB_CONNECTIONPROXY_H #include //! The KDbConnectionProxy class gives access to protected (low-level) API of KDbConnection /** * The connection object specified in constructor of the proxy is called a parent connection. * All inherited methods of the KDbConnection API call equivalent methods of the parent * connection. The KDbConnectionProxy class also provides non-virtual methods that are * equivalent to KDbConnection ones. These KDbConnection's equivalent methods are called * by the proxy too. * * Example use of this class is Kexi's database database migration plugins when the source * database is only accessibly using low-level routines. */ class KDB_EXPORT KDbConnectionProxy : protected KDbConnection { public: //! Creates a proxy object for parent @a connection. //! @a connection must not be @c nullptr. //! It is owned by this proxy unless setConnectionIsOwned(false) is called. explicit KDbConnectionProxy(KDbConnection *connection); //! Deletes this proxy. Owned connection is closed and destroyed. - ~KDbConnectionProxy(); + ~KDbConnectionProxy() override; //! @return parent connection for this proxy KDbConnection *parentConnection(); //! @overload KDbConnection *parentConnection() const KDbConnection *parentConnection() const; //! Control owhership of parent connection that is assigned to this proxy. //! Owned connection is closed and destroyed upon destruction of the KDbConnectionProxy //! object. void setParentConnectionIsOwned(bool set); KDbConnectionData data() const; KDbDriver* driver() const; bool connect(); bool isConnected() const; bool isDatabaseUsed() const; KDbConnectionOptions *options(); void clearResult(); KDbResult result() const; KDbResultable resultable() const; bool disconnect(); QStringList databaseNames(bool also_system_db = false); bool databaseExists(const QString &dbName, bool ignoreErrors = true); bool createDatabase(const QString &dbName); bool useDatabase(const QString &dbName = QString(), bool kexiCompatible = true, bool *cancelled = nullptr, KDbMessageHandler* msgHandler = nullptr); bool closeDatabase(); QString currentDatabase() const; bool dropDatabase(const QString &dbName = QString()); QStringList objectNames(int objectType = KDb::AnyObjectType, bool* ok = nullptr); QStringList tableNames(bool alsoSystemTables = false, bool* ok = nullptr); tristate containsTable(const QString &tableName); KDbServerVersionInfo serverVersion() const; KDbVersionInfo databaseVersion() const; KDbProperties databaseProperties() const; QList tableIds(bool* ok = nullptr); QList queryIds(bool* ok = nullptr); QList objectIds(int objectType, bool* ok = nullptr); KDbTransaction beginTransaction(); bool commitTransaction(KDbTransaction trans = KDbTransaction(), bool ignore_inactive = false); bool rollbackTransaction(KDbTransaction trans = KDbTransaction(), bool ignore_inactive = false); KDbTransaction defaultTransaction() const; void setDefaultTransaction(const KDbTransaction& trans); QList transactions(); bool autoCommit() const; bool setAutoCommit(bool on); - KDbEscapedString escapeString(const QString& str) const Q_DECL_OVERRIDE; + KDbEscapedString escapeString(const QString& str) const override; - KDbCursor* prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE; + KDbCursor *prepareQuery(const KDbEscapedString &sql, + KDbCursor::Options options = KDbCursor::Option::None) override; - KDbCursor* prepareQuery(KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE; + KDbCursor *prepareQuery(KDbQuerySchema *query, + KDbCursor::Options options = KDbCursor::Option::None) override; - KDbCursor* prepareQuery(KDbTableSchema* table, KDbCursor::Options options = KDbCursor::Option::None); + KDbCursor *prepareQuery(KDbTableSchema *table, + KDbCursor::Options options = KDbCursor::Option::None); - KDbCursor* executeQuery(const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None); + KDbCursor *executeQuery(const KDbEscapedString &sql, + KDbCursor::Options options = KDbCursor::Option::None); KDbCursor* executeQuery(KDbQuerySchema* query, const QList& params, KDbCursor::Options options = KDbCursor::Option::None); KDbCursor* executeQuery(KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None); KDbCursor* executeQuery(KDbTableSchema* table, KDbCursor::Options options = KDbCursor::Option::None); bool deleteCursor(KDbCursor *cursor); KDbTableSchema* tableSchema(int tableId); KDbTableSchema* tableSchema(const QString& tableName); KDbQuerySchema* querySchema(int queryId); KDbQuerySchema* querySchema(const QString& queryName); bool setQuerySchemaObsolete(const QString& queryName); tristate querySingleRecord(const KDbEscapedString& sql, KDbRecordData* data, bool addLimitTo1 = true); tristate querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, bool addLimitTo1 = true); tristate querySingleRecord(KDbQuerySchema* query, KDbRecordData* data, const QList& params, bool addLimitTo1 = true); tristate querySingleString(const KDbEscapedString& sql, QString* value, int column = 0, bool addLimitTo1 = true); tristate querySingleString(KDbQuerySchema* query, QString* value, int column = 0, bool addLimitTo1 = true); tristate querySingleString(KDbQuerySchema* query, QString* value, const QList& params, int column = 0, bool addLimitTo1 = true); tristate querySingleNumber(const KDbEscapedString& sql, int* number, int column = 0, bool addLimitTo1 = true); tristate querySingleNumber(KDbQuerySchema* query, int* number, int column = 0, bool addLimitTo1 = true); tristate querySingleNumber(KDbQuerySchema* query, int* number, const QList& params, int column = 0, bool addLimitTo1 = true); bool queryStringList(const KDbEscapedString& sql, QStringList* list, int column = 0); bool queryStringList(KDbQuerySchema* query, QStringList* list, int column = 0); bool queryStringList(KDbQuerySchema* query, QStringList* list, const QList& params, int column = 0); tristate resultExists(const KDbEscapedString& sql, bool addLimitTo1 = true); tristate isEmpty(KDbTableSchema* table); - KDbEscapedString recentSQLString() const Q_DECL_OVERRIDE; + KDbEscapedString recentSQLString() const override; //PROTOTYPE: #define A , const QVariant& #define H_INS_REC(args) bool insertRecord(KDbTableSchema* tableSchema args) #define H_INS_REC_ALL \ H_INS_REC(A); \ H_INS_REC(A A); \ H_INS_REC(A A A); \ H_INS_REC(A A A A); \ H_INS_REC(A A A A A); \ H_INS_REC(A A A A A A); \ H_INS_REC(A A A A A A A); \ H_INS_REC(A A A A A A A A) H_INS_REC_ALL; #undef H_INS_REC #define H_INS_REC(args) bool insertRecord(KDbFieldList* fields args) H_INS_REC_ALL; #undef H_INS_REC_ALL #undef H_INS_REC #undef A bool insertRecord(KDbTableSchema* tableSchema, const QList& values); bool insertRecord(KDbFieldList* fields, const QList& values); bool createTable(KDbTableSchema* tableSchema, bool replaceExisting = false); KDbTableSchema *copyTable(const KDbTableSchema &tableSchema, const KDbObject &newData); KDbTableSchema *copyTable(const QString& tableName, const KDbObject &newData); tristate dropTable(KDbTableSchema* tableSchema); tristate dropTable(const QString& tableName); tristate alterTable(KDbTableSchema* tableSchema, KDbTableSchema* newTableSchema); bool alterTableName(KDbTableSchema* tableSchema, const QString& newName, bool replace = false); bool dropQuery(KDbQuerySchema* querySchema); bool dropQuery(const QString& queryName); bool removeObject(int objId); KDbField* findSystemFieldName(const KDbFieldList& fieldlist); - QString anyAvailableDatabaseName() Q_DECL_OVERRIDE; + QString anyAvailableDatabaseName() override; void setAvailableDatabaseName(const QString& dbName); bool useTemporaryDatabaseIfNeeded(QString* name); KDbSqlResult* executeSQL(const KDbEscapedString& sql) Q_REQUIRED_RESULT; bool executeVoidSQL(const KDbEscapedString& sql); bool storeObjectData(KDbObject* object); bool storeNewObjectData(KDbObject* object); tristate loadObjectData(int id, KDbObject* object); tristate loadObjectData(int type, const QString& name, KDbObject* object); tristate loadDataBlock(int objectID, QString* dataString, const QString& dataID = QString()); bool storeDataBlock(int objectID, const QString &dataString, const QString& dataID = QString()); bool copyDataBlock(int sourceObjectID, int destObjectID, const QString& dataID = QString()); bool removeDataBlock(int objectID, const QString& dataID = QString()); KDbPreparedStatement prepareStatement(KDbPreparedStatement::Type type, KDbFieldList* fields, const QStringList& whereFieldNames = QStringList()); bool isInternalTableSchema(const QString& tableName); - QString escapeIdentifier(const QString& id) const Q_DECL_OVERRIDE; + QString escapeIdentifier(const QString& id) const override; - bool drv_connect() Q_DECL_OVERRIDE; + bool drv_connect() override; - bool drv_disconnect() Q_DECL_OVERRIDE; + bool drv_disconnect() override; - bool drv_getServerVersion(KDbServerVersionInfo* version) Q_DECL_OVERRIDE; + bool drv_getServerVersion(KDbServerVersionInfo* version) override; - tristate drv_containsTable(const QString &tableName) Q_DECL_OVERRIDE; + tristate drv_containsTable(const QString &tableName) override; - bool drv_createTable(const KDbTableSchema& tableSchema) Q_DECL_OVERRIDE; + bool drv_createTable(const KDbTableSchema& tableSchema) override; - bool drv_alterTableName(KDbTableSchema* tableSchema, const QString& newName) Q_DECL_OVERRIDE; + bool drv_alterTableName(KDbTableSchema* tableSchema, const QString& newName) override; bool drv_copyTableData(const KDbTableSchema &tableSchema, - const KDbTableSchema &destinationTableSchema) Q_DECL_OVERRIDE; + const KDbTableSchema &destinationTableSchema) override; - bool drv_dropTable(const QString& tableName) Q_DECL_OVERRIDE; + bool drv_dropTable(const QString& tableName) override; tristate dropTable(KDbTableSchema* tableSchema, bool alsoRemoveSchema); bool setupObjectData(const KDbRecordData& data, KDbObject* object); KDbField* setupField(const KDbRecordData& data); - KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) override Q_REQUIRED_RESULT; - bool drv_executeVoidSQL(const KDbEscapedString& sql) Q_DECL_OVERRIDE; + bool drv_executeVoidSQL(const KDbEscapedString& sql) override; - bool drv_getDatabasesList(QStringList* list) Q_DECL_OVERRIDE; + bool drv_getDatabasesList(QStringList* list) override; - bool drv_databaseExists(const QString &dbName, bool ignoreErrors = true) Q_DECL_OVERRIDE; + bool drv_databaseExists(const QString &dbName, bool ignoreErrors = true) override; - bool drv_createDatabase(const QString &dbName = QString()) Q_DECL_OVERRIDE; + bool drv_createDatabase(const QString &dbName = QString()) override; bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = nullptr, - KDbMessageHandler* msgHandler = nullptr) Q_DECL_OVERRIDE; + KDbMessageHandler* msgHandler = nullptr) override; - bool drv_closeDatabase() Q_DECL_OVERRIDE; + bool drv_closeDatabase() override; - bool drv_isDatabaseUsed() const Q_DECL_OVERRIDE; + bool drv_isDatabaseUsed() const override; - bool drv_dropDatabase(const QString &dbName = QString()) Q_DECL_OVERRIDE; + bool drv_dropDatabase(const QString &dbName = QString()) override; - bool drv_createTable(const QString& tableName) Q_DECL_OVERRIDE; + bool drv_createTable(const QString& tableName) override; - KDbTransactionData* drv_beginTransaction() Q_DECL_OVERRIDE; + KDbTransactionData* drv_beginTransaction() override; - bool drv_commitTransaction(KDbTransactionData* trans) Q_DECL_OVERRIDE; + bool drv_commitTransaction(KDbTransactionData* trans) override; - bool drv_rollbackTransaction(KDbTransactionData* trans) Q_DECL_OVERRIDE; + bool drv_rollbackTransaction(KDbTransactionData* trans) override; - bool drv_beforeInsert(const QString& tableName, KDbFieldList* fields) Q_DECL_OVERRIDE; + bool drv_beforeInsert(const QString& tableName, KDbFieldList* fields) override; - bool drv_afterInsert(const QString& tableName, KDbFieldList* fields) Q_DECL_OVERRIDE; + bool drv_afterInsert(const QString& tableName, KDbFieldList* fields) override; - bool drv_beforeUpdate(const QString& tableName, KDbFieldList* fields) Q_DECL_OVERRIDE; + bool drv_beforeUpdate(const QString& tableName, KDbFieldList* fields) override; - bool drv_afterUpdate(const QString& tableName, KDbFieldList* fields) Q_DECL_OVERRIDE; + bool drv_afterUpdate(const QString& tableName, KDbFieldList* fields) override; - bool drv_setAutoCommit(bool on) Q_DECL_OVERRIDE; + bool drv_setAutoCommit(bool on) override; - KDbPreparedStatementInterface* prepareStatementInternal() Q_DECL_OVERRIDE; + KDbPreparedStatementInterface* prepareStatementInternal() override; bool beginAutoCommitTransaction(KDbTransactionGuard* tg); bool commitAutoCommitTransaction(const KDbTransaction& trans); bool rollbackAutoCommitTransaction(const KDbTransaction& trans); bool checkConnected(); bool checkIsDatabaseUsed(); KDbTableSchema* setupTableSchema(const KDbRecordData& data); KDbQuerySchema* setupQuerySchema(const KDbRecordData& data); bool updateRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId = false); bool insertRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool getRecordId = false); bool deleteRecord(KDbQuerySchema* query, KDbRecordData* data, bool useRecordId = false); bool deleteAllRecords(KDbQuerySchema* query); bool checkIfColumnExists(KDbCursor *cursor, int column); tristate querySingleRecordInternal(KDbRecordData* data, const KDbEscapedString* sql, KDbQuerySchema* query, const QList* params, bool addLimitTo1 = true); tristate querySingleStringInternal(const KDbEscapedString* sql, QString* value, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1); tristate querySingleNumberInternal(const KDbEscapedString* sql, int* number, KDbQuerySchema* query, const QList* params, int column, bool addLimitTo1); bool queryStringListInternal(const KDbEscapedString *sql, QStringList* list, KDbQuerySchema* query, const QList* params, int column, bool (*filterFunction)(const QString&)); KDbCursor* executeQueryInternal(const KDbEscapedString& sql, KDbQuerySchema* query, const QList* params); bool loadExtendedTableSchemaData(KDbTableSchema* tableSchema); bool storeExtendedTableSchemaData(KDbTableSchema* tableSchema); bool storeMainFieldSchema(KDbField *field); private: Q_DISABLE_COPY(KDbConnectionProxy) class Private; Private * const d; }; #endif diff --git a/src/KDbCursor.cpp b/src/KDbCursor.cpp index b4e26a8b..3144eb1f 100644 --- a/src/KDbCursor.cpp +++ b/src/KDbCursor.cpp @@ -1,608 +1,608 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbCursor.h" #include "KDbConnection.h" #include "KDbDriver.h" #include "KDbDriverBehavior.h" #include "KDbError.h" #include "KDb.h" #include "KDbNativeStatementBuilder.h" #include "KDbQuerySchema.h" #include "KDbRecordData.h" #include "KDbRecordEditBuffer.h" #include "kdb_debug.h" #include #include class Q_DECL_HIDDEN KDbCursor::Private { public: Private() : opened(false) , atLast(false) , readAhead(false) , validRecord(false) , atBuffer(false) { } ~Private() { } bool containsRecordIdInfo; //!< true if result contains extra column for record id; //!< used only for PostgreSQL now //! @todo IMPORTANT: use something like QPointer conn; KDbConnection *conn; KDbEscapedString rawSql; bool opened; bool atLast; bool readAhead; bool validRecord; //!< true if valid record is currently retrieved @ current position //! Used by setOrderByColumnList() KDbQueryColumnInfo::Vector orderByColumnList; QList queryParameters; // bool atBuffer; //!< true if we already point to the buffer with curr_coldata // }; KDbCursor::KDbCursor(KDbConnection* conn, const KDbEscapedString& sql, Options options) - : m_query(0) + : m_query(nullptr) , m_options(options) , d(new Private) { #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String("Create cursor for raw SQL: ") + sql.toString()); #endif init(conn); d->rawSql = sql; } KDbCursor::KDbCursor(KDbConnection* conn, KDbQuerySchema* query, Options options) : m_query(query) , m_options(options) , d(new Private) { #ifdef KDB_DEBUG_GUI KDb::debugGUI(QString::fromLatin1("Create cursor for query \"%1\":\n") .arg(KDb::iifNotEmpty(query->name(), QString::fromLatin1(""))) + KDbUtils::debugString(query)); #endif init(conn); } void KDbCursor::init(KDbConnection* conn) { Q_ASSERT(conn); d->conn = conn; d->conn->addCursor(this); m_afterLast = false; m_at = 0; m_records_in_buf = 0; m_buffering_completed = false; m_fetchResult = FetchInvalid; d->containsRecordIdInfo = (m_query && m_query->masterTable()) && d->conn->driver()->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false; if (m_query) { //get list of all fields m_visibleFieldsExpanded = new KDbQueryColumnInfo::Vector(); *m_visibleFieldsExpanded = m_query->visibleFieldsExpanded( d->containsRecordIdInfo ? KDbQuerySchema::WithInternalFieldsAndRecordId : KDbQuerySchema::WithInternalFields); m_logicalFieldCount = m_visibleFieldsExpanded->count() - m_query->internalFields().count() - (d->containsRecordIdInfo ? 1 : 0); m_fieldCount = m_visibleFieldsExpanded->count(); m_fieldsToStoreInRecord = m_fieldCount; } else { - m_visibleFieldsExpanded = 0; + m_visibleFieldsExpanded = nullptr; m_logicalFieldCount = 0; m_fieldCount = 0; m_fieldsToStoreInRecord = 0; } } KDbCursor::~KDbCursor() { #ifdef KDB_DEBUG_GUI #if 0 // too many details if (m_query) KDb::debugGUI(QLatin1String("~ Delete cursor for query")); else KDb::debugGUI(QLatin1String("~ Delete cursor: ") + m_rawSql.toString()); #endif #endif /* if (!m_query) kdbDebug() << "KDbCursor::~KDbCursor() '" << m_rawSql.toLatin1() << "'"; else kdbDebug() << "KDbCursor::~KDbCursor() ";*/ d->conn->takeCursor(this); delete m_visibleFieldsExpanded; delete d; } bool KDbCursor::readAhead() const { return d->readAhead; } KDbConnection* KDbCursor::connection() const { return d->conn; } KDbQuerySchema *KDbCursor::query() const { return m_query; } KDbEscapedString KDbCursor::rawSql() const { return d->rawSql; } KDbCursor::Options KDbCursor::options() const { return m_options; } bool KDbCursor::isOpened() const { return d->opened; } bool KDbCursor::containsRecordIdInfo() const { return d->containsRecordIdInfo; } KDbRecordData* KDbCursor::storeCurrentRecord() const { KDbRecordData* data = new KDbRecordData(m_fieldsToStoreInRecord); if (!drv_storeCurrentRecord(data)) { delete data; - return 0; + return nullptr; } return data; } bool KDbCursor::storeCurrentRecord(KDbRecordData* data) const { Q_ASSERT(data); data->resize(m_fieldsToStoreInRecord); return drv_storeCurrentRecord(data); } bool KDbCursor::open() { if (d->opened) { if (!close()) return false; } if (!d->rawSql.isEmpty()) { m_result.setSql(d->rawSql); } else { if (!m_query) { kdbDebug() << "no query statement (or schema) defined!"; m_result = KDbResult(ERR_SQL_EXECUTION_ERROR, tr("No query statement or schema defined.")); return false; } KDbSelectStatementOptions options; options.alsoRetrieveRecordId = d->containsRecordIdInfo; /*get record Id if needed*/ KDbNativeStatementBuilder builder(d->conn); KDbEscapedString sql; if (!builder.generateSelectStatement(&sql, m_query, options, d->queryParameters) || sql.isEmpty()) { kdbDebug() << "no statement generated!"; m_result = KDbResult(ERR_SQL_EXECUTION_ERROR, tr("Could not generate query statement.")); return false; } m_result.setSql(sql); #ifdef KDB_DEBUG_GUI KDb::debugGUI(QString::fromLatin1("SQL for query \"%1\": ") .arg(KDb::iifNotEmpty(m_query->name(), QString::fromLatin1(""))) + m_result.sql().toString()); #endif } d->opened = drv_open(m_result.sql()); m_afterLast = false; //we are not @ the end m_at = 0; //we are before 1st rec if (!d->opened) { m_result.setCode(ERR_SQL_EXECUTION_ERROR); m_result.setMessage(tr("Error opening database cursor.")); return false; } d->validRecord = false; if (d->conn->driver()->beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY) { // kdbDebug() << "READ AHEAD:"; d->readAhead = getNextRecord(); //true if any record in this query // kdbDebug() << "READ AHEAD = " << d->readAhead; } m_at = 0; //we are still before 1st rec return !m_result.isError(); } bool KDbCursor::close() { if (!d->opened) { return true; } bool ret = drv_close(); clearBuffer(); d->opened = false; m_afterLast = false; d->readAhead = false; m_fieldCount = 0; m_fieldsToStoreInRecord = 0; m_logicalFieldCount = 0; m_at = -1; // kdbDebug() << ret; return ret; } bool KDbCursor::reopen() { if (!d->opened) { return open(); } return close() && open(); } bool KDbCursor::moveFirst() { if (!d->opened) { return false; } if (!d->readAhead) { if (m_options & KDbCursor::Option::Buffered) { if (m_records_in_buf == 0 && m_buffering_completed) { //eof and bof should now return true: m_afterLast = true; m_at = 0; return false; //buffering completed and there is no records! } if (m_records_in_buf > 0) { //set state as we would be before first rec: d->atBuffer = false; m_at = 0; //..and move to next, ie. 1st record m_afterLast = !getNextRecord(); return !m_afterLast; } } else if (!(d->conn->driver()->beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY)) { // not buffered m_at = 0; m_afterLast = !getNextRecord(); return !m_afterLast; } if (m_afterLast && m_at == 0) //failure if already no records return false; if (!reopen()) //try reopen return false; if (m_afterLast) //eof return false; } else { //we have a record already read-ahead: we now point @ that: m_at = 1; } //get first record m_afterLast = false; d->readAhead = false; //1st record had been read return d->validRecord; } bool KDbCursor::moveLast() { if (!d->opened) { return false; } if (m_afterLast || d->atLast) { return d->validRecord; //we already have valid last record retrieved } if (!getNextRecord()) { //at least next record must be retrieved m_afterLast = true; d->validRecord = false; d->atLast = false; return false; //no records } while (getNextRecord()) //move after last rec. ; m_afterLast = false; //cursor shows last record data d->atLast = true; return true; } bool KDbCursor::moveNext() { if (!d->opened || m_afterLast) { return false; } if (getNextRecord()) { return true; } return false; } bool KDbCursor::movePrev() { if (!d->opened /*|| m_beforeFirst*/ || !(m_options & KDbCursor::Option::Buffered)) { return false; } //we're after last record and there are records in the buffer //--let's move to last record if (m_afterLast && (m_records_in_buf > 0)) { drv_bufferMovePointerTo(m_records_in_buf - 1); m_at = m_records_in_buf; d->atBuffer = true; //now current record is stored in the buffer d->validRecord = true; m_afterLast = false; return true; } //we're at first record: go BOF if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) { m_at = 0; d->atBuffer = false; d->validRecord = false; return false; } m_at--; if (d->atBuffer) {//we already have got a pointer to buffer drv_bufferMovePointerPrev(); //just move to prev record in the buffer } else {//we have no pointer //compute a place in the buffer that contain next record's data drv_bufferMovePointerTo(m_at - 1); d->atBuffer = true; //now current record is stored in the buffer } d->validRecord = true; m_afterLast = false; return true; } bool KDbCursor::isBuffered() const { return m_options & KDbCursor::Option::Buffered; } void KDbCursor::setBuffered(bool buffered) { if (!d->opened) { return; } if (isBuffered() == buffered) return; m_options ^= KDbCursor::Option::Buffered; } void KDbCursor::clearBuffer() { if (!isBuffered() || m_fieldCount == 0) return; drv_clearBuffer(); m_records_in_buf = 0; d->atBuffer = false; } bool KDbCursor::getNextRecord() { m_fetchResult = FetchInvalid; //by default: invalid result of record fetching if (m_options & KDbCursor::Option::Buffered) {//this cursor is buffered: // kdbDebug() << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf; if (m_at < m_records_in_buf) {//we have next record already buffered: if (d->atBuffer) {//we already have got a pointer to buffer drv_bufferMovePointerNext(); //just move to next record in the buffer } else {//we have no pointer //compute a place in the buffer that contain next record's data drv_bufferMovePointerTo(m_at - 1 + 1); d->atBuffer = true; //now current record is stored in the buffer } } else {//we are after last retrieved record: we need to physically fetch next record: if (!d->readAhead) {//we have no record that was read ahead if (!m_buffering_completed) { //retrieve record only if we are not after //the last buffer's item (i.e. when buffer is not fully filled): // kdbDebug()<<"==== buffering: drv_getNextRecord() ===="; drv_getNextRecord(); } if (m_fetchResult != FetchOK) {//there is no record m_buffering_completed = true; //no more records for buffer // kdbDebug()<<"m_fetchResult != FetchOK ********"; d->validRecord = false; m_afterLast = true; m_at = -1; //position is invalid now and will not be used if (m_fetchResult == FetchError) { m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, tr("Could not fetch next record.")); return false; } return false; // in case of m_fetchResult = FetchEnd or m_fetchResult = FetchInvalid } //we have a record: store this record's values in the buffer drv_appendCurrentRecordToBuffer(); m_records_in_buf++; } else //we have a record that was read ahead: eat this d->readAhead = false; } } else {//we are after last retrieved record: we need to physically fetch next record: if (!d->readAhead) {//we have no record that was read ahead // kdbDebug()<<"==== no prefetched record ===="; drv_getNextRecord(); if (m_fetchResult != FetchOK) {//there is no record // kdbDebug()<<"m_fetchResult != FetchOK ********"; d->validRecord = false; m_afterLast = true; m_at = -1; if (m_fetchResult == FetchEnd) { return false; } m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, tr("Could not fetch next record.")); return false; } } else { //we have a record that was read ahead: eat this d->readAhead = false; } } m_at++; // if (m_data->curr_colname && m_data->curr_coldata) // for (int i=0;icurr_cols;i++) { // kdbDebug()<curr_colname[i]<<" == "<< m_data->curr_coldata[i]; // } // kdbDebug()<<"m_at == "<<(long)m_at; d->validRecord = true; return true; } bool KDbCursor::updateRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId) { //! @todo doesn't update cursor's buffer YET! clearResult(); if (!m_query) return false; return d->conn->updateRecord(m_query, data, buf, useRecordId); } bool KDbCursor::insertRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId) { //! @todo doesn't update cursor's buffer YET! if (!m_query) { clearResult(); return false; } return d->conn->insertRecord(m_query, data, buf, useRecordId); } bool KDbCursor::deleteRecord(KDbRecordData* data, bool useRecordId) { //! @todo doesn't update cursor's buffer YET! clearResult(); if (!m_query) return false; return d->conn->deleteRecord(m_query, data, useRecordId); } bool KDbCursor::deleteAllRecords() { //! @todo doesn't update cursor's buffer YET! clearResult(); if (!m_query) return false; return d->conn->deleteAllRecords(m_query); } QDebug operator<<(QDebug dbg, const KDbCursor& cursor) { dbg.nospace() << "CURSOR("; if (!cursor.query()) { dbg.nospace() << "RAW SQL STATEMENT:" << cursor.rawSql().toString() << "\n"; } else { KDbNativeStatementBuilder builder(cursor.connection()); KDbEscapedString sql; QString sqlString; if (builder.generateSelectStatement(&sql, cursor.query())) { sqlString = sql.toString(); } else { sqlString = QLatin1String(""); } dbg.nospace() << "KDbQuerySchema:" << sqlString << "\n"; } if (cursor.isOpened()) { dbg.space() << "OPENED"; } else { dbg.space() << "NOT_OPENED"; } if (cursor.isBuffered()) { dbg.space() << "BUFFERED"; } else { dbg.space() << "NOT_BUFFERED"; } dbg.nospace() << "AT=" << cursor.at() << ")"; return dbg.space(); } void KDbCursor::setOrderByColumnList(const QStringList& columnNames) { Q_UNUSED(columnNames); //! @todo implement this: all field names should be found, exit otherwise // OK //! @todo if (!d->orderByColumnList) } /*! Convenience method, similar to setOrderBy(const QStringList&). */ void KDbCursor::setOrderByColumnList(const QString& column1, const QString& column2, const QString& column3, const QString& column4, const QString& column5) { Q_UNUSED(column1); Q_UNUSED(column2); Q_UNUSED(column3); Q_UNUSED(column4); Q_UNUSED(column5); //! @todo implement this, like above //! @todo add ORDER BY info to debugString() } KDbQueryColumnInfo::Vector KDbCursor::orderByColumnList() const { return d->orderByColumnList; } QList KDbCursor::queryParameters() const { return d->queryParameters; } void KDbCursor::setQueryParameters(const QList& params) { d->queryParameters = params; } //! @todo extraMessages #if 0 static const char *extraMessages[] = { QT_TRANSLATE_NOOP("KDbCursor", "No connection for cursor open operation specified.") }; #endif diff --git a/src/KDbCursor.h b/src/KDbCursor.h index 62ff2dd8..b16ae375 100644 --- a/src/KDbCursor.h +++ b/src/KDbCursor.h @@ -1,335 +1,335 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_CURSOR_H #define KDB_CURSOR_H #include #include #include "KDbResult.h" #include "KDbQueryColumnInfo.h" class KDbConnection; class KDbRecordData; class KDbQuerySchema; class KDbRecordEditBuffer; //! Provides database cursor functionality. /*! Cursor can be defined in two ways: -# by passing KDbQuerySchema object to KDbConnection::executeQuery() or KDbConnection::prepareQuery(); then query is defined for in engine-independent way -- this is recommended usage -# by passing raw query statement string to KDbConnection::executeQuery() or KDbConnection::prepareQuery(); then query may be defined for in engine-dependent way -- this is not recommended usage, but convenient when we can't or do not want to allocate KDbQuerySchema object, while we know that the query statement is syntactically and logically ok in our context. You can move cursor to next record with moveNext() and move back with movePrev(). The cursor is always positioned on record, not between records, with exception that after open() it is positioned before the first record (if any) -- then bof() equals true. The cursor can also be positioned after the last record (if any) with moveNext() -- then eof() equals true. For example, if you have four records, 1, 2, 3, 4, then after calling open(), moveNext(), moveNext(), moveNext(), movePrev() you are going through records: 1, 2, 3, 2. Cursor can be buffered or unbuferred. @warning Buffered cursors are not implemented! Buffering in this class is not related to any SQL engine capatibilities for server-side cursors (eg. like 'DECLARE CURSOR' statement) - buffered data is at client (application) side. Any record retrieved in buffered cursor will be stored inside an internal buffer and reused when needed. Unbuffered cursor always requires one record fetching from db connection at every step done with moveNext(), movePrev(), etc. Notes: - Do not use delete operator for KDbCursor objects - this will fail; use KDbConnection::deleteCursor() instead. - KDbQuerySchema object is not owned by KDbCursor object that uses it. */ class KDB_EXPORT KDbCursor: public KDbResultable { Q_DECLARE_TR_FUNCTIONS(KDbCursor) public: //! Options that describe behavior of database cursor enum class Option { None = 0, Buffered = 1 }; Q_DECLARE_FLAGS(Options, Option) /*! @return connection used for the cursor */ KDbConnection* connection() const; /*! Opens the cursor using data provided on creation. The data might be either KDbQuerySchema or a raw SQL statement. */ bool open(); /*! Closes and then opens again the same cursor. If the cursor is not opened it is just opened and result of this open is returned. Otherwise, true is returned if cursor is successfully closed and then opened. */ bool reopen(); /*! Closes previously opened cursor. If the cursor is closed, nothing happens. */ virtual bool close(); /*! @return query schema used to define this cursor or 0 if the cursor is not defined by a query schema but by a raw SQL statement. */ KDbQuerySchema *query() const; //! @return query parameters assigned to this cursor QList queryParameters() const; //! Sets query parameters @a params for this cursor. void setQueryParameters(const QList& params); /*! @return raw query statement used to define this cursor or null string if raw statement instead (but KDbQuerySchema is defined instead). */ KDbEscapedString rawSql() const; /*! @return cursor options */ Options options() const; /*! @return true if the cursor is opened. */ bool isOpened() const; /*! @return true if the cursor is buffered. */ bool isBuffered() const; /*! Sets this cursor to buffered type or not. See description of buffered and nonbuffered cursors in class description. This method only works if cursor is not opened (isOpened()==false). You can close already opened cursor and then switch this option on/off. */ void setBuffered(bool buffered); /*! Moves current position to the first record and retrieves it. @return true if the first record was retrieved. False could mean that there was an error or there is no record available. */ bool moveFirst(); /*! Moves current position to the last record and retrieves it. @return true if the last record was retrieved. False could mean that there was an error or there is no record available. */ virtual bool moveLast(); /*! Moves current position to the next record and retrieves it. */ virtual bool moveNext(); /*! Moves current position to the next record and retrieves it. Currently it's only supported for buffered cursors. */ virtual bool movePrev(); /*! @return true if current position is after last record. */ inline bool eof() const { return m_afterLast; } /*! @return true if current position is before first record. */ inline bool bof() const { return m_at == 0; } /*! @return current internal position of the cursor's query. We are counting records from 0. Value -1 means that cursor does not point to any valid record (this happens eg. after open(), close(), and after moving after last record or before first one. */ inline qint64 at() const { return readAhead() ? 0 : (m_at - 1); } /*! @return number of fields available for this cursor. This never includes ROWID column or other internal columns (e.g. lookup). */ inline int fieldCount() const { return m_query ? m_logicalFieldCount : m_fieldCount; } /*! @return true if ROWID information is available for each record. ROWID information is available if KDbDriverBehavior::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false for a KDb database driver and the master table has no primary key defined. Phisically, ROWID value is returned after last returned field, so data vector's length is expanded by one. */ bool containsRecordIdInfo() const; /*! @return a value stored in column number @a i (counting from 0). It has unspecified behavior if the cursor is not at valid record. Note for driver developers: If @a i is >= than m_fieldCount, null QVariant value should be returned. To return a value typically you can use a pointer to internal structure that contain current record data (buffered or unbuffered). */ virtual QVariant value(int i) = 0; - /*! [PROTOTYPE] @return current record data or NULL if there is no current records. */ + /*! [PROTOTYPE] @return current record data or @c nullptr if there is no current records. */ virtual const char ** recordData() const = 0; /*! Sets a list of columns for ORDER BY section of the query. Only works when the cursor has been created using KDbQuerySchema object (i.e. when query()!=0; does not work with raw statements). Each name on the list must be a field or alias present within the query and must not be covered by aliases. If one or more names cannot be found within the query, the method will have no effect. Any previous ORDER BY settings will be removed. The order list provided here has priority over a list defined in the KDbQuerySchema object itseld (using KDbQuerySchema::setOrderByColumnList()). The KDbQuerySchema object itself is not modifed by this method: only order of records retrieved by this cursor is affected. Use this method before calling open(). You can also call reopen() after calling this method to see effects of applying records order. */ //! @todo implement this void setOrderByColumnList(const QStringList& columnNames); /*! Convenience method, similar to setOrderByColumnList(const QStringList&). */ //! @todo implement this void setOrderByColumnList(const QString& column1, const QString& column2 = QString(), const QString& column3 = QString(), const QString& column4 = QString(), const QString& column5 = QString()); /*! @return a list of fields contained in ORDER BY section of the query. @see setOrderBy(const QStringList&) */ KDbQueryColumnInfo::Vector orderByColumnList() const; /*! Allocates a new KDbRecordData and stores data in it (makes a deep copy of each field). If the cursor is not at valid record, the result is undefined. @return newly created record data object or 0 on error. */ KDbRecordData* storeCurrentRecord() const; /*! Puts current record's data into @a data (makes a deep copy of each field). If the cursor is not at valid record, the result is undefined. @return true on success. */ bool storeCurrentRecord(KDbRecordData* data) const; bool updateRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId = false); bool insertRecord(KDbRecordData* data, KDbRecordEditBuffer* buf, bool getRecrordId = false); bool deleteRecord(KDbRecordData* data, bool useRecordId = false); bool deleteAllRecords(); protected: /*! Cursor will operate on @a conn, raw SQL statement @a sql will be used to execute query. */ KDbCursor(KDbConnection* conn, const KDbEscapedString& sql, Options options = KDbCursor::Option::None); /*! Cursor will operate on @a conn, @a query schema will be used to execute query. */ KDbCursor(KDbConnection* conn, KDbQuerySchema* query, Options options = KDbCursor::Option::None); - virtual ~KDbCursor(); + ~KDbCursor() override; void init(KDbConnection* conn); /*! Internal: cares about proper flag setting depending on result of drv_getNextRecord() and depending on wherher a cursor is buffered. */ bool getNextRecord(); /*! Note for driver developers: this method should initialize engine-specific cursor's resources using an SQL statement @a sql. It is not required to store @a sql statement somewhere in your KDbCursor subclass (it is already stored in m_query or m_rawStatement, depending query type) - only pass it to proper engine's function. */ virtual bool drv_open(const KDbEscapedString& sql) = 0; virtual bool drv_close() = 0; virtual void drv_getNextRecord() = 0; /*! Stores currently fetched record's values in appropriate place of the buffer. Note for driver developers: This place can be computed using m_at. Do not change value of m_at or any other KDbCursor members, only change your internal structures like pointer to current record, etc. If your database engine's API function (for record fetching) do not allocates such a space, you want to allocate a space for current record. Otherwise, reuse existing structure, what could be more efficient. All functions like drv_appendCurrentRecordToBuffer() operates on the buffer, i.e. array of stored records. You are not forced to have any particular fixed structure for buffer item or buffer itself - the structure is internal and only methods like storeCurrentRecord() visible to public. */ virtual void drv_appendCurrentRecordToBuffer() = 0; /*! Moves pointer (that points to the buffer) -- to next item in this buffer. Note for driver developers: probably just execute "your_pointer++" is enough. */ virtual void drv_bufferMovePointerNext() = 0; /*! Like drv_bufferMovePointerNext() but execute "your_pointer--". */ virtual void drv_bufferMovePointerPrev() = 0; /*! Moves pointer (that points to the buffer) to a new place: @a at. */ virtual void drv_bufferMovePointerTo(qint64 at) = 0; /*! Clears cursor's buffer if this was allocated (only for buffered cursor type). Otherwise do nothing. For reimplementing. Default implementation does nothing. */ virtual void drv_clearBuffer() {} //! @internal clears buffer with reimplemented drv_clearBuffer(). */ void clearBuffer(); /*! Puts current record's data into @a data (makes a deep copy of each field). This method has unspecified behavior if the cursor is not at valid record. @return true on success. Note: For reimplementation in driver's code. Shortly, this method translates a record data from internal representation (probably also used in buffer) to simple public KDbRecordData representation. */ virtual bool drv_storeCurrentRecord(KDbRecordData* data) const = 0; KDbQuerySchema *m_query; bool m_afterLast; qint64 m_at; int m_fieldCount; //!< cached field count information int m_fieldsToStoreInRecord; //!< Used by storeCurrentRecord(), reimplement if needed //!< (e.g. PostgreSQL driver, when m_containsRecordIdInfo is true //!< sets m_fieldCount+1 here) int m_logicalFieldCount; //!< logical field count, i.e. without intrernal values like Record Id or lookup KDbCursor::Options m_options; //!< cursor options that describes its behavior //! possible results of record fetching, used for m_fetchResult enum FetchResult { FetchInvalid, //!< used before starting the fetching, result is not known yet FetchError, //!< error of fetching FetchOK, //!< the data is fetched FetchEnd //!< at the end of data }; FetchResult m_fetchResult; //!< result of a record fetching // int m_records_in_buf; //!< number of records currently stored in the buffer bool m_buffering_completed; //!< true if we already have all records stored in the buffer // //! Useful e.g. for value(int) method to obtain access to schema definition. KDbQueryColumnInfo::Vector* m_visibleFieldsExpanded; private: bool readAhead() const; Q_DISABLE_COPY(KDbCursor) friend class CursorDeleter; class Private; Private * const d; }; //! Sends information about object @a cursor to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbCursor& cursor); Q_DECLARE_OPERATORS_FOR_FLAGS(KDbCursor::Options) #endif diff --git a/src/KDbDriver.cpp b/src/KDbDriver.cpp index 4ec71e9b..bac2dfbb 100644 --- a/src/KDbDriver.cpp +++ b/src/KDbDriver.cpp @@ -1,414 +1,414 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbDriver.h" #include "KDbAdmin.h" #include "KDbConnectionData.h" #include "KDbConnection.h" #include "KDbConnectionOptions.h" #include "KDbDriverManager_p.h" #include "KDbDriverMetaData.h" #include "KDbDriver_p.h" #include "KDbDriverBehavior.h" #include "KDbError.h" #include "KDbExpression.h" #include "kdb_debug.h" #include /*! @internal Used in KDbDriver::defaultSQLTypeName(int) when we do not have KDbDriver instance yet, or when we cannot get one */ static const char* const KDb_defaultSQLTypeNames[] = { "InvalidType", "Byte", "ShortInteger", "Integer", "BigInteger", "Boolean", "Date", "DateTime", "Time", "Float", "Double", "Text", "LongText", "BLOB" }; //--------------------------------------------- KDbDriver::KDbDriver(QObject *parent, const QVariantList &args) : QObject(parent) , beh(new KDbDriverBehavior(this)) , d(new DriverPrivate(this)) { Q_UNUSED(args); beh->typeNames.resize(KDbField::LastType + 1); } KDbDriver::~KDbDriver() { // make a copy because d->connections will be touched by ~KDbConnection QSet connections(d->connections); qDeleteAll(connections); d->connections.clear(); delete beh; delete d; // kdbDebug() << "ok"; } bool KDbDriver::isValid() { clearResult(); QString inv_impl(tr("Invalid database driver's \"%1\" implementation.").arg(metaData()->name())); QString not_init(tr("Value of \"%1\" is not initialized for the driver.")); if (beh->ROW_ID_FIELD_NAME.isEmpty()) { m_result = KDbResult(ERR_INVALID_DRIVER_IMPL, inv_impl + QLatin1Char(' ') + not_init.arg(QLatin1String("KDbDriverBehavior::ROW_ID_FIELD_NAME"))); return false; } return true; } const QSet KDbDriver::connections() const { return d->connections; } const KDbDriverMetaData* KDbDriver::metaData() const { return d->metaData; } int KDbDriver::features() const { return beh->features; } bool KDbDriver::transactionsSupported() const { return beh->features & (SingleTransactions | MultipleTransactions); } KDbAdminTools& KDbDriver::adminTools() const { if (!d->adminTools) d->adminTools = drv_createAdminTools(); return *d->adminTools; } KDbAdminTools* KDbDriver::drv_createAdminTools() const { return new KDbAdminTools(); //empty impl. } QString KDbDriver::sqlTypeName(KDbField::Type type, const KDbField &field) const { Q_UNUSED(field); if (type > KDbField::InvalidType && type <= KDbField::LastType) { /*sanity*/ return beh->typeNames[type]; } return beh->typeNames[KDbField::InvalidType]; } KDbConnection *KDbDriver::createConnection(const KDbConnectionData& connData, const KDbConnectionOptions &options) { clearResult(); if (!isValid()) - return 0; + return nullptr; KDbConnection *conn = drv_createConnection(connData, options); //! @todo needed? connData->setDriverId(id()); d->connections.insert(conn); return conn; } KDbConnection *KDbDriver::createConnection(const KDbConnectionData& connData) { return createConnection(connData, KDbConnectionOptions()); } KDbConnection* KDbDriver::removeConnection(KDbConnection *conn) { clearResult(); if (d->connections.remove(conn)) return conn; - return 0; + return nullptr; } QString KDbDriver::defaultSQLTypeName(KDbField::Type type) { if (type > KDbField::LastType) return QLatin1String("Null"); return QLatin1String(KDb_defaultSQLTypeNames[type]); } bool KDbDriver::isKDbSystemObjectName(const QString& name) { if (!name.startsWith(QLatin1String("kexi__"), Qt::CaseInsensitive)) return false; return KDbConnection::kdbSystemTableNames().contains(name, Qt::CaseInsensitive); } bool KDbDriver::isSystemFieldName(const QString& name) const { if (!beh->ROW_ID_FIELD_NAME.isEmpty() && 0 == name.compare(beh->ROW_ID_FIELD_NAME, Qt::CaseInsensitive)) { return true; } return drv_isSystemFieldName(name); } static KDbEscapedString valueToSQLInternal(const KDbDriver *driver, KDbField::Type ftype, const QVariant& v) { if (v.isNull() || ftype == KDbField::Null) { return KDbEscapedString("NULL"); } switch (ftype) { case KDbField::Text: case KDbField::LongText: { return driver ? driver->escapeString(v.toString()) : KDbEscapedString(KDb::escapeString(v.toString())); } case KDbField::Byte: case KDbField::ShortInteger: case KDbField::Integer: case KDbField::BigInteger: return KDbEscapedString(v.toByteArray()); case KDbField::Float: case KDbField::Double: { if (v.type() == QVariant::String) { //workaround for values stored as string that should be casted to floating-point KDbEscapedString s(v.toByteArray()); return s.replace(',', '.'); } return KDbEscapedString(v.toByteArray()); } //! @todo here special encoding method needed case KDbField::Boolean: return driver ? KDbEscapedString(v.toInt() == 0 ? driver->behavior()->BOOLEAN_FALSE_LITERAL : driver->behavior()->BOOLEAN_TRUE_LITERAL) : KDbEscapedString(v.toInt() == 0 ? "FALSE" : "TRUE"); case KDbField::Time: return KDbEscapedString('\'') + v.toTime().toString(Qt::ISODate) + '\''; case KDbField::Date: return KDbEscapedString('\'') + v.toDate().toString(Qt::ISODate) + '\''; case KDbField::DateTime: return driver ? driver->dateTimeToSQL(v.toDateTime()) : KDb::dateTimeToSQL(v.toDateTime()); case KDbField::BLOB: { if (v.toByteArray().isEmpty()) { return KDbEscapedString("NULL"); } if (v.type() == QVariant::String) { return driver ? driver->escapeBLOB(v.toString().toUtf8()) : KDbEscapedString(KDb::escapeBLOB(v.toString().toUtf8(), KDb::BLOBEscape0xHex)); } return driver ? driver->escapeBLOB(v.toByteArray()) : KDbEscapedString(KDb::escapeBLOB(v.toByteArray(), KDb::BLOBEscape0xHex)); } case KDbField::InvalidType: return KDbEscapedString("!INVALIDTYPE!"); default: kdbDebug() << KDbEscapedString("UNKNOWN!"); } return KDbEscapedString(); } KDbEscapedString KDbDriver::valueToSQL(KDbField::Type ftype, const QVariant& v) const { //! note, it was compatible with SQLite: http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions. return valueToSQLInternal(this, ftype, v); } KDbEscapedString KDb::valueToSQL(KDbField::Type ftype, const QVariant& v) { - return valueToSQLInternal(0, ftype, v); + return valueToSQLInternal(nullptr, ftype, v); } KDbEscapedString KDb::dateTimeToSQL(const QDateTime& v) { /*! (was compatible with SQLite: http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions) Now it's ISO 8601 DateTime format - with "T" delimiter: http://www.w3.org/TR/NOTE-datetime (e.g. "1994-11-05T13:15:30" not "1994-11-05 13:15:30") @todo add support for time zones? */ return KDbEscapedString('\'') + v.toString(Qt::ISODate) + KDbEscapedString('\''); } KDbEscapedString KDbDriver::dateTimeToSQL(const QDateTime& v) const { return KDb::dateTimeToSQL(v); } QString KDbDriver::escapeIdentifier(const QString& str) const { return QLatin1Char(beh->OPENING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER) + drv_escapeIdentifier(str) + QLatin1Char(beh->CLOSING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER); } QByteArray KDbDriver::escapeIdentifier(const QByteArray& str) const { return beh->OPENING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER + drv_escapeIdentifier(str) + beh->CLOSING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER; } KDbUtils::Property KDbDriver::internalProperty(const QByteArray& name) const { return beh->properties.property(name); } QList KDbDriver::internalPropertyNames() const { QList names(beh->properties.names()); qSort(names); return names; } void KDbDriver::initDriverSpecificKeywords(const char* const* keywords) { d->driverSpecificSQLKeywords.setStrings(keywords); } KDbEscapedString KDbDriver::addLimitTo1(const KDbEscapedString& sql, bool add) { return add ? (sql + " LIMIT 1") : sql; } bool KDbDriver::isDriverSpecificKeyword(const QByteArray& word) const { return d->driverSpecificSQLKeywords.contains(word); } void KDbDriver::setMetaData(const KDbDriverMetaData *metaData) { d->metaData = metaData; beh->initInternalProperties(); } KDbEscapedString KDbDriver::hexFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(QLatin1String("HEX"), this, args, params, callStack); } KDbEscapedString KDbDriver::ifnullFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(QLatin1String("IFNULL"), this, args, params, callStack); } KDbEscapedString KDbDriver::lengthFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(QLatin1String("LENGTH"), this, args, params, callStack); } KDbEscapedString KDbDriver::greatestOrLeastFunctionToString( const QString &name, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(name, this, args, params, callStack); } KDbEscapedString KDbDriver::randomFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { static QLatin1String randomStatic("()"); if (!args.isNull() || args.argCount() < 1 ) { return KDbEscapedString(beh->RANDOM_FUNCTION + randomStatic); } Q_ASSERT(args.argCount() == 2); const KDbEscapedString x(args.arg(0).toString(this, params, callStack)); const KDbEscapedString y(args.arg(1).toString(this, params, callStack)); static KDbEscapedString floorRandomStatic("+FLOOR("); static KDbEscapedString floorRandomStatic2("()*("); static KDbEscapedString floorRandomStatic3(")))"); return KDbEscapedString('(') + x + floorRandomStatic + beh->RANDOM_FUNCTION + floorRandomStatic2 + y + QLatin1Char('-') + x + floorRandomStatic3; } KDbEscapedString KDbDriver::ceilingOrFloorFunctionToString( const QString &name, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(name, this, args, params, callStack); } KDbEscapedString KDbDriver::unicodeFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return KDbFunctionExpression::toString(QLatin1String("UNICODE"), this, args, params, callStack); } KDbEscapedString KDbDriver::concatenateFunctionToString(const KDbBinaryExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { return args.left().toString(this, params, callStack) + KDbEscapedString("||") + args.right().toString(this, params, callStack); } //--------------- Q_GLOBAL_STATIC_WITH_ARGS( KDbUtils::StaticSetOfStrings, KDb_kdbSQLKeywords, (DriverPrivate::kdbSQLKeywords) ) KDB_EXPORT bool KDb::isKDbSQLKeyword(const QByteArray& word) { return KDb_kdbSQLKeywords->contains(word.toUpper()); } KDB_EXPORT QString KDb::escapeIdentifier(const KDbDriver* driver, const QString& str) { return driver ? driver->escapeIdentifier(str) : KDb::escapeIdentifier(str); } KDB_EXPORT QByteArray KDb::escapeIdentifier(const KDbDriver* driver, const QByteArray& str) { return driver ? driver->escapeIdentifier(str) : KDb::escapeIdentifier(str); } diff --git a/src/KDbDriver.h b/src/KDbDriver.h index a703252a..1de5ec8e 100644 --- a/src/KDbDriver.h +++ b/src/KDbDriver.h @@ -1,402 +1,402 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_DRIVER_H #define KDB_DRIVER_H #include #include "KDb.h" #include "KDbResult.h" #include "KDbEscapedString.h" #include "KDbExpressionData.h" class KDbAdminTools; class KDbConnection; class KDbConnectionData; class KDbConnectionOptions; class KDbDriverBehavior; class KDbDriverMetaData; class KDbBinaryExpression; class KDbNArgExpression; class KDbQuerySchemaParameterValueListIterator; class DriverPrivate; #define KDB_DRIVER_PLUGIN_FACTORY(class_name, name) \ K_PLUGIN_FACTORY_WITH_JSON(class_name ## Factory, name, registerPlugin();) //! Database driver's abstraction. /*! This class is a prototype of the database driver. KDbDriver allows new connections to be created, and groups as their parent. Before destruction, all owned connections are destructed. */ class KDB_EXPORT KDbDriver : public QObject, public KDbResultable { Q_OBJECT public: /*! Features supported by driver (sum of few Features enum items). */ enum Features { NoFeatures = 0, //! single trasactions are only supported SingleTransactions = 1, //! multiple concurrent trasactions are supported //! (this implies !SingleTransactions) MultipleTransactions = 2, //(js) NOT YET IN USE: /*! nested trasactions are supported (this should imply !SingleTransactions and MultipleTransactions) */ NestedTransactions = 4, /*! forward moving is supported for cursors (if not available, no cursors available at all) */ CursorForward = 8, /*! backward moving is supported for cursors (this implies CursorForward) */ CursorBackward = (CursorForward + 16), /*! compacting database supported (aka VACUUM) */ CompactingDatabaseSupported = 32, //-- temporary options: can be removed later, use at your own risk -- /*! If set, actions related to transactions will be silently bypassed with success. Set this if your driver does not support transactions at all Currently, this is only way to get it working with KDb. Keep in mind that this hack do not provide data integrity! This flag is currently used for MySQL driver. */ IgnoreTransactions = 1024 }; /*! Creates connection using @a connData as parameters. - @return 0 and sets error message on error. + @return @c nullptr and sets error message on error. driverId member of @a connData will be updated with the driver's ID. @a options can be set for the new connection. */ KDbConnection *createConnection(const KDbConnectionData& connData, const KDbConnectionOptions &options); //! @overload createConnection(const KDbConnectionData&, const KDbConnectionOptions&) KDbConnection *createConnection(const KDbConnectionData& connData); /*! @return Set of created connections. */ const QSet connections() const; /*! Info about the driver. */ const KDbDriverMetaData* metaData() const; /*! @return true if @a n is a database type-specific system object's name, e.g. name of a built-in system table that cannot be created by the user, and in most cases a name that user shouldn't even see. @see isSystemDatabaseName() isKDbSystemObjectName() isSystemFieldName() */ virtual bool isSystemObjectName(const QString& name) const = 0; /*! @return true if @a name is a related to KDb's 'system' object's name, i.e. when @a name starts with "kexi__" prefix. @see isSystemDatabaseName() isSystemObjectName() isSystemFieldName() */ static bool isKDbSystemObjectName(const QString& name); /*! @return true if @a name is a database type-specific system database's name, e.g. name of a built-in system database that cannot be created by a user, and in most cases user a name that user shouldn't even see. @see isKDbSystemObjectName() isSystemObjectName() isSystemFieldName() */ virtual bool isSystemDatabaseName(const QString& name) const = 0; /*! @return true if @a name is a system field's name, build-in system field that cannot be used or created by a user, and in most cases user even shouldn't see this. The list is specific for a given driver implementation. @see isSystemDatabaseName() isKDbSystemObjectName() isSystemObjectName() */ bool isSystemFieldName(const QString& name) const; /*! @return true if @a word is a driver-specific keyword. @see KDb::isKDbSQLKeyword(const QByteArray&) */ bool isDriverSpecificKeyword(const QByteArray& word) const; /*! @return driver's features that are combination of KDbDriver::Features enum. @todo change int to Features */ int features() const; /*! @return true if transaction are supported (single or multiple). */ bool transactionsSupported() const; /*! @return admin tools object providing a number of database administration tools for the driver. Tools availablility varies from driver to driver. You can check it using features(). */ KDbAdminTools& adminTools() const; /*! SQL-implementation-dependent name of given type */ virtual QString sqlTypeName(KDbField::Type type, const KDbField &field) const; /*! used when we do not have KDbDriver instance yet */ static QString defaultSQLTypeName(KDbField::Type type); /*! Escapes and converts value @a v (for type @a ftype) to string representation required by SQL commands. Reimplement this if you need other behavior (eg. for 'date' type handling) This implementation return date, datetime and time values in ISO format, what seems to be accepted by SQL servers. @see Qt::DateFormat */ virtual KDbEscapedString valueToSQL(KDbField::Type ftype, const QVariant& v) const; //! Like above but with the fildtype as string. inline KDbEscapedString valueToSQL(const QString& ftype, const QVariant& v) const { return valueToSQL(KDbField::typeForString(ftype), v); } //! Like above method, for @a field. inline KDbEscapedString valueToSQL(const KDbField *field, const QVariant& v) const { return valueToSQL((field ? field->type() : KDbField::InvalidType), v); } /*! @todo not compatible with all drivers - reimplement */ virtual KDbEscapedString dateTimeToSQL(const QDateTime& v) const; /*! Driver-specific SQL string escaping. Implement escaping for any character like " or ' as your database engine requires. Prepend and append quotation marks. */ virtual KDbEscapedString escapeString(const QString& str) const = 0; /*! This is overloaded version of escapeString( const QString& str ) to be implemented in the same way. */ virtual KDbEscapedString escapeString(const QByteArray& str) const = 0; /*! Driver-specific SQL BLOB value escaping. Implement escaping for any character like " or ' and \\0 as your database engine requires. Prepend and append quotation marks. */ virtual KDbEscapedString escapeBLOB(const QByteArray& array) const = 0; /*! @return SQL clause to add for unicode text collation sequence used in ORDER BY clauses of SQL statements generated by KDb. Later other clauses may use this statement. One space character should be be prepended. Can be reimplemented for other drivers, e.g. the SQLite3 driver returns " COLLATE ''". Default implementation returns empty string. */ virtual KDbEscapedString collationSQL() const { return KDbEscapedString(); } //! @return @a str string with applied driver-specific identifier escaping /*! This escaping can be used for field, table, database names, etc. @see KDb::escapeIdentifier */ QString escapeIdentifier(const QString& str) const; //! @overload QString escapeIdentifier(const QString&) const QByteArray escapeIdentifier(const QByteArray& str) const; //! @return internal property with a name @a name for this driver. //! If there's no such property defined for driver, a null property is returned. KDbUtils::Property internalProperty(const QByteArray& name) const; //! @return a list of internal property names for this driver. QList internalPropertyNames() const; //! @return a structure that provides detailed information about driver's default behavior. inline const KDbDriverBehavior* behavior() const { return this->beh; } //! @internal - virtual ~KDbDriver(); + ~KDbDriver() override; //! Generates native (driver-specific) HEX() function call. //! Default implementation uses HEX(val). virtual KDbEscapedString hexFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) IFNULL() function call. //! Default implementation uses IFNULL(). virtual KDbEscapedString ifnullFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) LENGTH() function call. //! Default implementation uses LENGTH(). virtual KDbEscapedString lengthFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) GREATEST() and LEAST() function calls. //! Default implementation just uses GREATEST() and LEAST(), respectively. //! (this works only with MySQL >= 5.0.13). //! For backends workarounds are added. virtual KDbEscapedString greatestOrLeastFunctionToString( const QString &name, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) RANDOM() and RANDOM(X,Y) function calls. //! Accepted @a args can contain zero or two positive integer arguments X, Y; X < Y. //! In case of numeric arguments, RANDOM(X, Y) returns a random integer that is equal //! or greater than X and less than Y. //! Default implementation for RANDOM() returns F() where F is behavior()->RANDOM_FUNCTION. //! This works with PostgreSQL. //! Default implementation for RANDOM(X,Y) returns (X + FLOOR(F()*(Y-X+1))) where //! F is behavior()->RANDOM_FUNCTION. This works with PostgreSQL. virtual KDbEscapedString randomFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) CEILING() and FLOOR() function calls. //! Default implementation USES CEILING() and FLOOR(), respectively. //! Special case is for SQLite. virtual KDbEscapedString ceilingOrFloorFunctionToString( const QString &name, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) UNICODE() function call. //! Default implementation USES UNICODE(). //! Special case is for MYSQL and PostgreSQL. virtual KDbEscapedString unicodeFunctionToString( const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; //! Generates native (driver-specific) function call for concatenation of two strings. //! Default implementation USES infix "||" operator. //! Special case is for MYSQL (CONCAT()). //! @todo API supporting KDbNArgExpression would be useful so instead of a||b||c can be //! expressed as CONCAT(a,b,c) instead of CONCAT(CONCAT(a,b),c). //! This requires changes to the KDbSQL parser. KDbEscapedString concatenateFunctionToString(const KDbBinaryExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; protected: /*! Used by KDbDriverManager. Note for driver developers: Reimplement this. In your reimplementation you should initialize: - beh->typeNames - to types accepted by your engine - beh->features - to combination of selected values from Features enum You may also want to change options in KDbDriverBehavior *beh member. See drivers/mySQL/mysqldriver.cpp for usage example. */ KDbDriver(QObject *parent, const QVariantList &args); /*! For reimplementation: creates and returns connection object with additional structures specific for a given driver. KDbConnection object should inherit KDbConnection and have a destructor that descructs all allocated driver-dependent connection structures. */ virtual KDbConnection *drv_createConnection(const KDbConnectionData& connData, const KDbConnectionOptions &options) = 0; /*! Driver-specific SQL string escaping. This method is used by escapeIdentifier(). Implement escaping for any character like " or ' as your database engine requires. Do not append or prepend any quotation marks characters - it is automatically done by escapeIdentifier() using KDbDriverBehavior::OPENING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER and KDbDriverBehavior::CLOSING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER. */ virtual QString drv_escapeIdentifier(const QString& str) const = 0; /*! This is overloaded version of drv_escapeIdentifier( const QString& str ) to be implemented in the same way. */ virtual QByteArray drv_escapeIdentifier(const QByteArray& str) const = 0; /*! @return true if @a name is a system field's name, build-in system field that cannot be used or created by a user, and in most cases user even shouldn't see this. The list is specific for a given driver implementation. For implementation.*/ virtual bool drv_isSystemFieldName(const QString& name) const = 0; /*! Creates admin tools object providing a number of database administration tools for the driver. This is called once per driver. Note for driver developers: Reimplement this method by returning a KDbAdminTools-derived object. Default implementation creates anmd returns an empty admin tools KDbAdminTools object. @see adminTools() */ virtual KDbAdminTools* drv_createAdminTools() const; /*! @return connection @a conn, does not delete it nor affect. - Returns 0 if @a conn is not owned by this driver. + Returns @c nullptr if @a conn is not owned by this driver. After this, you are owner of @a conn object, so you should eventually delete it. Better use KDbConnection destructor. */ KDbConnection* removeConnection(KDbConnection *conn); /*! Used to initialise the dictionary of driver-specific keywords. Should be called by the driver's constructor. @a keywords should be 0-terminated array of null-terminated strings. */ void initDriverSpecificKeywords(const char* const* keywords); /*! @return SQL statement @a sql modified by adding limiting command, (if possible and if @add is true). Used for optimization for the server side. Can be reimplemented for other drivers. */ virtual KDbEscapedString addLimitTo1(const KDbEscapedString& sql, bool add); /*! @return true if the database supports specifying default values for field @a field. @c true by default. For example MySQL does not support default values for BLOB, TEXT, GEOMETRY, and JSON types. (http://dev.mysql.com/doc/refman/5.7/en/data-type-defaults.html). */ virtual bool supportsDefaultValue(const KDbField &field) const { Q_UNUSED(field); return true; } /*! Used by the driver manager to set metaData for just loaded driver. */ void setMetaData(const KDbDriverMetaData *metaData); /*! @return true if this driver's implementation is valid. Just a few constraints are checked to ensure that driver developer didn't forget something. This method is called automatically on createConnection(), and proper error message is set properly on error. Drivers can reimpement this method but should call KDbDriver::isValid() first. */ virtual bool isValid(); friend class KDbConnection; friend class KDbCursor; friend class KDbDriverBehavior; friend class KDbNativeStatementBuilder; friend class DriverManagerInternal; friend class DriverPrivate; KDbDriverBehavior *beh; DriverPrivate * const d; private: Q_DISABLE_COPY(KDbDriver) }; namespace KDb { //! @return string @a string with applied driver-specific identifier escaping if @a driver //! is not KDbSQL general identifier escaping when @a driver is 0. /*! This escaping can be used for field, table, database names, etc. @see KDb::escapeIdentifier */ KDB_EXPORT QString escapeIdentifier(const KDbDriver* driver, const QString& string); //! @overload QString escapeIdentifier(const KDbDriver*, const QString&) KDB_EXPORT QByteArray escapeIdentifier(const KDbDriver* driver, const QByteArray& str); inline KDbEscapedString valueToSQL(const KDbDriver *driver, KDbField::Type ftype, const QVariant& v) { return driver ? driver->valueToSQL(ftype, v) : KDb::valueToSQL(ftype, v); } } #endif diff --git a/src/KDbDriverManager.cpp b/src/KDbDriverManager.cpp index a34ad00f..0d35729c 100644 --- a/src/KDbDriverManager.cpp +++ b/src/KDbDriverManager.cpp @@ -1,288 +1,288 @@ /* This file is part of the KDE project Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2015 Jarosław Staniek Copyright (C) 2012 Dimitrios T. Tanis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbDriverManager.h" #include "KDbDriverManager_p.h" #include "KDbJsonTrader_p.h" #include "KDbDriver.h" #include "KDbDriverMetaData.h" #include "kdb_debug.h" #include #include #include #include Q_GLOBAL_STATIC(DriverManagerInternal, s_self) DriverManagerInternal::DriverManagerInternal() : m_lookupDriversNeeded(true) { qsrand(QTime::currentTime().msec()); // needed e.g. to create random table names } DriverManagerInternal::~DriverManagerInternal() { drivermanagerDebug(); clear(); drivermanagerDebug() << "ok"; } void DriverManagerInternal::clear() { drivermanagerDebug() << "Clearing drivers..."; qDeleteAll(m_drivers); m_drivers.clear(); qDeleteAll(m_driversMetaData); m_driversMetaData.clear(); } void DriverManagerInternal::slotAppQuits() { if (qApp && !qApp->topLevelWidgets().isEmpty() && qApp->topLevelWidgets().first()->isVisible()) { return; //what a hack! - we give up when app is still there } clear(); } //static DriverManagerInternal *DriverManagerInternal::self() { return s_self; } bool DriverManagerInternal::lookupDrivers() { if (!m_lookupDriversNeeded) return true; if (!forceEmpty) { lookupDriversInternal(); m_lookupDriversNeeded = false; } if (m_driversMetaData.isEmpty()) { m_result = KDbResult(ERR_DRIVERMANAGER, tr("Could not find any database drivers.")); return false; } return true; } void DriverManagerInternal::lookupDriversInternal() { if (qApp) { connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(slotAppQuits())); } clearResult(); //drivermanagerDebug() << "Load all plugins"; QList offers = KDbJsonTrader::self()->query(QLatin1String("KDb/Driver")); const QString expectedVersion = QString::fromLatin1("%1.%2") .arg(KDB_STABLE_VERSION_MAJOR).arg(KDB_STABLE_VERSION_MINOR); foreach(const QPluginLoader *loader, offers) { //QJsonObject json = loader->metaData(); //drivermanagerDebug() << json; QScopedPointer metaData(new KDbDriverMetaData(*loader)); //qDebug() << "VER:" << metaData->version(); if (metaData->version() != expectedVersion) { kdbWarning() << "Driver with ID" << metaData->id() << "(" << metaData->fileName() << ")" << "has version" << metaData->version() << "but expected version is" << expectedVersion << "-- skipping it"; continue; } if (m_driversMetaData.contains(metaData->id())) { if (qgetenv("KDB_NO_DUPLICATED_DRIVER_WARNINGS").isEmpty()) { kdbWarning() << "Driver with ID" << metaData->id() << "already found at" << m_driversMetaData.value(metaData->id())->fileName() << "-- skipping another at" << metaData->fileName(); } continue; } foreach (const QString& mimeType, metaData->mimeTypes()) { m_metadata_by_mimetype.insertMulti(mimeType, metaData.data()); } m_driversMetaData.insert(metaData->id(), metaData.data()); metaData.take(); } qDeleteAll(offers); offers.clear(); } QStringList DriverManagerInternal::driverIds() { if (!lookupDrivers()) { return QStringList(); } if (m_driversMetaData.isEmpty() && result().isError()) { return QStringList(); } return m_driversMetaData.keys(); } const KDbDriverMetaData* DriverManagerInternal::driverMetaData(const QString &id) { if (!lookupDrivers()) { - return 0; + return nullptr; } const KDbDriverMetaData *metaData = m_driversMetaData.value(id.toLower()); if (!metaData || m_result.isError()) { m_result = KDbResult(ERR_DRIVERMANAGER, tr("Could not find database driver \"%1\".").arg(id)); } return metaData; } QStringList DriverManagerInternal::driverIdsForMimeType(const QString &mimeType) { if (!lookupDrivers()) { return QStringList(); } const QList metaDatas(m_metadata_by_mimetype.values(mimeType.toLower())); QStringList result; foreach (const KDbDriverMetaData* metaData, metaDatas) { result.append(metaData->id()); } return result; } QStringList DriverManagerInternal::possibleProblems() const { return m_possibleProblems; } KDbDriver* DriverManagerInternal::driver(const QString& id) { if (!lookupDrivers()) - return 0; + return nullptr; clearResult(); drivermanagerDebug() << "loading" << id; - KDbDriver *driver = 0; + KDbDriver *driver = nullptr; if (!id.isEmpty()) { driver = m_drivers.value(id.toLower()); } if (driver) return driver; //cached if (!m_driversMetaData.contains(id.toLower())) { m_result = KDbResult(ERR_DRIVERMANAGER, tr("Could not find database driver \"%1\".").arg(id)); - return 0; + return nullptr; } const KDbDriverMetaData *metaData = m_driversMetaData.value(id.toLower()); KPluginFactory *factory = qobject_cast(metaData->instantiate()); if (!factory) { m_result = KDbResult(ERR_DRIVERMANAGER, tr("Could not load database driver's plugin file \"%1\".") .arg(metaData->fileName())); QPluginLoader loader(metaData->fileName()); // use this to get the message (void)loader.load(); m_result.setServerMessage(loader.errorString()); kdbWarning() << m_result.message() << m_result.serverMessage(); - return 0; + return nullptr; } driver = factory->create(); if (!driver) { m_result = KDbResult(ERR_DRIVERMANAGER, tr("Could not open database driver \"%1\" from plugin file \"%2\".") .arg(metaData->id(), metaData->fileName())); kdbWarning() << m_result.message(); - return 0; + return nullptr; } driver->setMetaData(metaData); m_drivers.insert(id.toLower(), driver); return driver; } // --------------------------- KDbDriverManager::KDbDriverManager() { } KDbDriverManager::~KDbDriverManager() { } KDbResult KDbDriverManager::result() const { return s_self->result(); } KDbResultable* KDbDriverManager::resultable() const { return s_self; } QStringList KDbDriverManager::driverIds() { return s_self->driverIds(); } const KDbDriverMetaData* KDbDriverManager::driverMetaData(const QString &id) { return s_self->driverMetaData(id); } QStringList KDbDriverManager::driverIdsForMimeType(const QString &mimeType) { return s_self->driverIdsForMimeType(mimeType); } KDbDriver* KDbDriverManager::driver(const QString& id) { return s_self->driver(id); } QString KDbDriverManager::possibleProblemsMessage() const { if (s_self->possibleProblems().isEmpty()) { return QString(); } QString str; str.reserve(1024); str = QLatin1String("
    "); foreach (const QString& problem, s_self->possibleProblems()) str += (QLatin1String("
  • ") + problem + QLatin1String("
  • ")); str += QLatin1String("
"); return str; } bool KDbDriverManager::hasDatabaseServerDrivers() { foreach(const QString& id, driverIds()) { const KDbDriverMetaData *metaData = s_self->driverMetaData(id); if (!metaData->isFileBased()) { return true; } } return false; } diff --git a/src/KDbDriverManager_p.h b/src/KDbDriverManager_p.h index 9312969a..d8105927 100644 --- a/src/KDbDriverManager_p.h +++ b/src/KDbDriverManager_p.h @@ -1,84 +1,84 @@ /* This file is part of the KDE project Copyright (C) 2003-2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_DRIVER_MANAGER_P_H #define KDB_DRIVER_MANAGER_P_H #include #include "config-kdb.h" #include "KDbResult.h" class KDbDriverMetaData; class KDbDriver; //! Internal class of the KDbDriverManager. class KDB_TESTING_EXPORT DriverManagerInternal : public QObject, public KDbResultable { Q_OBJECT public: /*! Used by self() */ DriverManagerInternal(); - ~DriverManagerInternal(); + ~DriverManagerInternal() override; QStringList driverIds(); /*! Tries to load a driver with identifier @a id. @return db driver, or @c nullptr on error (then error result is also set) */ KDbDriver* driver(const QString& id); const KDbDriverMetaData* driverMetaData(const QString &id); QStringList driverIdsForMimeType(const QString& mimeType); QStringList possibleProblems() const; static DriverManagerInternal *self(); #if BUILD_TESTING //! If @c true, sets the driver manager to have no drivers so this case can be tested. //! Afects driverIds(), driver(), driverMetaData(), driverIdsForMimeType() bool forceEmpty = false; #else const bool forceEmpty = false; #endif protected Q_SLOTS: /*! Used to destroy all drivers on QApplication quit, so even if there are KDbDriverManager's static instances that are destroyed on program "static destruction", drivers are not kept after QApplication death. */ void slotAppQuits(); private: Q_DISABLE_COPY(DriverManagerInternal) bool lookupDrivers(); void lookupDriversInternal(); void clear(); QMap m_metadata_by_mimetype; QMap m_driversMetaData; //!< used to store driver metadata QMap m_drivers; //!< for owning drivers QString m_pluginsDir; QStringList m_possibleProblems; bool m_lookupDriversNeeded; }; #endif diff --git a/src/KDbDriver_p.cpp b/src/KDbDriver_p.cpp index 0ae8d4b6..134928ab 100644 --- a/src/KDbDriver_p.cpp +++ b/src/KDbDriver_p.cpp @@ -1,122 +1,122 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek Copyright (C) 2004 Martin Ellis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbDriver_p.h" #include "KDbAdmin.h" #include "KDbDriverBehavior.h" #include "KDbDriverMetaData.h" #include "KDbVersionInfo.h" class Q_DECL_HIDDEN KDbDriverBehavior::Private { public: Private() {} KDbDriver *driver; }; KDbDriverBehavior::KDbDriverBehavior(KDbDriver *driver) : features(KDbDriver::NoFeatures) , UNSIGNED_TYPE_KEYWORD(QLatin1String("UNSIGNED")) , AUTO_INCREMENT_FIELD_OPTION(QLatin1String("AUTO_INCREMENT")) , AUTO_INCREMENT_PK_FIELD_OPTION(QLatin1String("AUTO_INCREMENT PRIMARY KEY")) , SPECIAL_AUTO_INCREMENT_DEF(false) , AUTO_INCREMENT_REQUIRES_PK(false) , ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE(false) , OPENING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER('"') , CLOSING_QUOTATION_MARK_BEGIN_FOR_IDENTIFIER('"') , USING_DATABASE_REQUIRED_TO_CONNECT(true) , CONNECTION_REQUIRED_TO_CHECK_DB_EXISTENCE(true) , CONNECTION_REQUIRED_TO_CREATE_DB(true) , CONNECTION_REQUIRED_TO_DROP_DB(true) , USE_TEMPORARY_DATABASE_FOR_CONNECTION_IF_NEEDED(false) , IS_DB_OPEN_AFTER_CREATE(false) , _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY(false) , SELECT_1_SUBQUERY_SUPPORTED(false) , BOOLEAN_TRUE_LITERAL(QLatin1Char('1')) , BOOLEAN_FALSE_LITERAL(QLatin1Char('0')) , TEXT_TYPE_MAX_LENGTH(0) , LIKE_OPERATOR(QLatin1String("LIKE")) , RANDOM_FUNCTION(QLatin1String("RANDOM")) , d(new Private) { d->driver = driver; properties.insert("client_library_version", QVariant(), KDbDriver::tr("Client library version")); properties.insert("default_server_encoding", QVariant(), KDbDriver::tr("Default character encoding on server")); } KDbDriverBehavior::~KDbDriverBehavior() { delete d; } void KDbDriverBehavior::initInternalProperties() { properties.insert("is_file_database", d->driver->metaData()->isFileBased(), KDbDriver::tr("File-based database driver")); if (d->driver->metaData()->isFileBased()) { properties.insert("file_database_mimetypes", d->driver->metaData()->mimeTypes(), KDbDriver::tr("File-based database's MIME types")); } #if 0 QString str; if (features & KDbDriver::SingleTransactions) str = futureTr("Single transactions support"); else if (features & KDbDriver::MultipleTransactions) str = futureTr("Multiple transactions support"); else if (features & KDbDriver::NestedTransactions) str = futureTr("Nested transactions support"); else if (features & KDbDriver::IgnoreTransactions) str = futureTr("Ignored", "Ignored transactions"); else str = futureTr2("None", "No transactions"); #endif // properties["transaction_support"] = features & KDbDriver::TransactionsMask; // propertyCaptions["transaction_support"] = KDbDriver::tr("Transaction support"); properties.insert("transactions_single", bool(d->driver->behavior()->features & KDbDriver::SingleTransactions), KDbDriver::tr("Single transactions support")); properties.insert("transactions_multiple", bool(d->driver->behavior()->features & KDbDriver::MultipleTransactions), KDbDriver::tr("Multiple transactions support")); properties.insert("transactions_nested", bool(d->driver->behavior()->features & KDbDriver::NestedTransactions), KDbDriver::tr("Nested transactions support")); properties.insert("transactions_ignored", bool(d->driver->behavior()->features & KDbDriver::IgnoreTransactions), KDbDriver::tr("Ignored transactions support")); //! @todo more KDbDriver::Features const KDbVersionInfo v(KDb::version()); properties.insert("kdb_driver_version", QString::fromLatin1("%1.%2.%3").arg(v.major()).arg(v.minor()).arg(v.release()), KDbDriver::tr("KDb driver version")); } //--------------------------------------------- DriverPrivate::DriverPrivate(KDbDriver *aDriver) : driver(aDriver) - , metaData(0) - , adminTools(0) + , metaData(nullptr) + , adminTools(nullptr) { } DriverPrivate::~DriverPrivate() { delete adminTools; } diff --git a/src/KDbEscapedString.h b/src/KDbEscapedString.h index 897ca165..f7181fbf 100644 --- a/src/KDbEscapedString.h +++ b/src/KDbEscapedString.h @@ -1,595 +1,595 @@ /* This file is part of the KDE project Copyright (C) 2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_ESCAPEDSTRING_H #define KDB_ESCAPEDSTRING_H #include #include #include #include "kdb_export.h" //! Specialized string for escaping. //! In addition to byte array, contains "validity" flag that is transferred //! when strings are concatenated or in general when any operation with invalid //! escaped string is performed. class KDB_EXPORT KDbEscapedString : protected QByteArray { public: inline KDbEscapedString() : m_valid(true) {} explicit inline KDbEscapedString(char ch) : QByteArray(1, ch), m_valid(true) {} explicit inline KDbEscapedString(QChar ch) : QByteArray(1, ch.toLatin1()), m_valid(true) {} explicit inline KDbEscapedString(const char* string) : QByteArray(string), m_valid(true) {} explicit inline KDbEscapedString(const QByteArray& string) : QByteArray(string), m_valid(true) {} explicit inline KDbEscapedString(const QString& string) : QByteArray(string.toUtf8()), m_valid(true) {} inline KDbEscapedString(const KDbEscapedString& string) : QByteArray(string), m_valid(string.isValid()) {} inline ~KDbEscapedString() {} //! @return invalid escaped string. static inline KDbEscapedString invalid() { return KDbEscapedString(false); } //! @return true if the string is valid. Valid string means that the escaping process //! has finished successfully. It does not mean that the statement itself parses //! or can be executed without errors. inline bool isValid() const { return m_valid; } inline QByteArray toByteArray() const { return static_cast(*this); } inline QString toString() const { return QString::fromUtf8(static_cast(*this).constData(), length()); } inline KDbEscapedString &operator=(const KDbEscapedString& string) { QByteArray::operator=(string); m_valid = string.isValid(); return *this; } inline KDbEscapedString &operator=(const QByteArray& string) { QByteArray::operator=(string); m_valid = true; return *this; } inline KDbEscapedString &operator=(const char *string) { QByteArray::operator=(string); m_valid = true; return *this; } inline bool operator==(const KDbEscapedString &other) const { return isValid() == other.isValid() && static_cast(*this) == other.toByteArray(); } inline int size() const { return QByteArray::size(); } inline bool isEmpty() const { return QByteArray::isEmpty(); } inline void resize(int size) { QByteArray::resize(size); } inline KDbEscapedString &fill(char c, int size = -1) { m_valid = true; QByteArray::fill(c, size); return *this; } inline int capacity() const { return QByteArray::isEmpty(); } inline void reserve(int size) { QByteArray::reserve(size); } inline void squeeze() { QByteArray::squeeze(); } inline char *data() { return QByteArray::data(); } inline const char *data() const { return QByteArray::data(); } inline const char *constData() const { return QByteArray::constData(); } inline void clear() { m_valid = true; QByteArray::clear(); } #ifdef Q_COMPILER_MANGLES_RETURN_TYPE inline const char at(int i) const { return QByteArray::at(i); } inline const char operator[](int i) const { return QByteArray::operator[](i); } inline const char operator[](uint i) const { return QByteArray::operator[](i); } #else inline char at(int i) const { return QByteArray::at(i); } inline char operator[](int i) const { return QByteArray::operator[](i); } inline char operator[](uint i) const { return QByteArray::operator[](i); } #endif inline QByteRef operator[](int i) { return QByteArray::operator[](i); } inline QByteRef operator[](uint i) { return QByteArray::operator[](i); } inline int indexOf(char c, int from = 0) const { return QByteArray::indexOf(c, from); } inline int indexOf(const char *c, int from = 0) const { return QByteArray::indexOf(c, from); } inline int indexOf(const QByteArray &a, int from = 0) const { return QByteArray::indexOf(a, from); } inline int indexOf(const KDbEscapedString &s, int from = 0) const { return s.isValid() ? QByteArray::indexOf(s, from) : -1; } inline int lastIndexOf(char c, int from = -1) const { return QByteArray::lastIndexOf(c, from); } inline int lastIndexOf(const char *c, int from = -1) const { return QByteArray::lastIndexOf(c, from); } inline int lastIndexOf(const QByteArray &a, int from = -1) const { return QByteArray::lastIndexOf(a, from); } inline int lastIndexOf(const KDbEscapedString &s, int from = 0) const { return s.isValid() ? QByteArray::lastIndexOf(s, from) : -1; } inline int count(char c) const { return QByteArray::count(c); } inline int count(const char *a) const { return QByteArray::count(a); } inline int count(const QByteArray &a) const { return QByteArray::count(a); } inline int count(const KDbEscapedString &s) const { return s.isValid() ? QByteArray::count(s) : -1; } inline KDbEscapedString left(int len) const { return m_valid ? KDbEscapedString(QByteArray::left(len)) : KDbEscapedString::invalid(); } inline KDbEscapedString right(int len) const { return m_valid ? KDbEscapedString(QByteArray::right(len)) : KDbEscapedString::invalid(); } inline KDbEscapedString mid(int index, int len = -1) const { return m_valid ? KDbEscapedString(QByteArray::mid(index, len)) : KDbEscapedString::invalid(); } inline bool startsWith(const KDbEscapedString &s) const { return (m_valid && s.isValid()) ? QByteArray::startsWith(s) : false; } inline bool startsWith(const QByteArray &a) const { return m_valid ? QByteArray::startsWith(a) : false; } inline bool startsWith(char c) const { return m_valid ? QByteArray::startsWith(c) : false; } inline bool startsWith(const char *c) const { return m_valid ? QByteArray::startsWith(c) : false; } inline bool endsWith(const KDbEscapedString &s) const { return (m_valid && s.isValid()) ? QByteArray::endsWith(s) : false; } inline bool endsWith(const QByteArray &a) const { return m_valid ? QByteArray::endsWith(a) : false; } inline bool endsWith(char c) const { return m_valid ? QByteArray::endsWith(c) : false; } inline bool endsWith(const char *c) const { return m_valid ? QByteArray::endsWith(c) : false; } inline void truncate(int pos) { QByteArray::truncate(pos); } inline void chop(int n) { QByteArray::chop(n); } inline KDbEscapedString toLower() const { return m_valid ? KDbEscapedString(QByteArray::toLower()) : KDbEscapedString::invalid(); } inline KDbEscapedString toUpper() const { return m_valid ? KDbEscapedString(QByteArray::toUpper()) : KDbEscapedString::invalid(); } inline KDbEscapedString trimmed() const { return m_valid ? KDbEscapedString(QByteArray::trimmed()) : KDbEscapedString::invalid(); } inline KDbEscapedString simplified() const { return m_valid ? KDbEscapedString(QByteArray::simplified()) : KDbEscapedString::invalid(); } inline KDbEscapedString leftJustified(int width, char fill = ' ', bool truncate = false) const { return m_valid ? KDbEscapedString(QByteArray::leftJustified(width, fill, truncate)) : KDbEscapedString::invalid(); } inline KDbEscapedString rightJustified(int width, char fill = ' ', bool truncate = false) const { return m_valid ? KDbEscapedString(QByteArray::rightJustified(width, fill, truncate)) : KDbEscapedString::invalid(); } inline KDbEscapedString &prepend(char c) { if (m_valid) QByteArray::prepend(c); return *this; } inline KDbEscapedString &prepend(const char *s) { if (m_valid) QByteArray::prepend(s); return *this; } inline KDbEscapedString &prepend(const QByteArray &a) { if (m_valid) QByteArray::prepend(a); return *this; } KDbEscapedString &prepend(const KDbEscapedString &s); inline KDbEscapedString &append(char c) { if (m_valid) QByteArray::append(c); return *this; } inline KDbEscapedString &append(const char *s) { if (m_valid) QByteArray::append(s); return *this; } inline KDbEscapedString &append(const char *s, int len) { if (m_valid) QByteArray::append(s, len); return *this; } inline KDbEscapedString &append(const QByteArray &a) { if (m_valid) QByteArray::append(a); return *this; } inline KDbEscapedString &append(const QString &a) { if (m_valid) QByteArray::append(a.toUtf8()); return *this; } KDbEscapedString &append(const KDbEscapedString &s); inline KDbEscapedString &insert(int i, char c) { if (m_valid) QByteArray::insert(i, c); return *this; } inline KDbEscapedString &insert(int i, const char *s) { if (m_valid) QByteArray::insert(i, s); return *this; } inline KDbEscapedString &insert(int i, const QByteArray &a) { if (m_valid) QByteArray::insert(i, a); return *this; } KDbEscapedString &insert(int i, const KDbEscapedString &s); inline KDbEscapedString &remove(int index, int len) { if (m_valid) QByteArray::remove(index, len); return *this; } inline KDbEscapedString &replace(int index, int len, const char *s) { if (m_valid) QByteArray::replace(index, len, s); return *this; } inline KDbEscapedString &replace(int index, int len, const QByteArray &s) { if (m_valid) QByteArray::replace(index, len, s); return *this; } KDbEscapedString &replace(int index, int len, const KDbEscapedString &s); inline KDbEscapedString &replace(char before, const char *after) { if (m_valid) QByteArray::replace(before, after); return *this; } inline KDbEscapedString &replace(char before, const QByteArray &after) { if (m_valid) QByteArray::replace(before, after); return *this; } KDbEscapedString &replace(char before, const KDbEscapedString &after); inline KDbEscapedString &replace(const char *before, const char *after) { if (m_valid) QByteArray::replace(before, after); return *this; } inline KDbEscapedString &replace(const char *before, int bsize, const char *after, int asize) { if (m_valid) QByteArray::replace(before, bsize, after, asize); return *this; } inline KDbEscapedString &replace(const QByteArray &before, const QByteArray &after) { if (m_valid) QByteArray::replace(before, after); return *this; } KDbEscapedString &replace(const KDbEscapedString &before, const QByteArray &after); KDbEscapedString &replace(const QByteArray &before, const KDbEscapedString &after); KDbEscapedString &replace(const KDbEscapedString &before, const KDbEscapedString &after); inline KDbEscapedString &replace(const QByteArray &before, const char *after) { if (m_valid) QByteArray::replace(before, after); return *this; } inline KDbEscapedString &replace(const char *before, const QByteArray &after) { if (m_valid) QByteArray::replace(before, after); return *this; } inline KDbEscapedString &replace(char before, char after) { if (m_valid) QByteArray::replace(before, after); return *this; } inline KDbEscapedString &operator+=(char c) { return append(c); } inline KDbEscapedString &operator+=(const char *s) { return append(s); } inline KDbEscapedString &operator+=(const QByteArray &a) { return append(a); } inline KDbEscapedString &operator+=(const QString &a) { return append(a); } inline KDbEscapedString &operator+=(const KDbEscapedString &s) { return append(s); } //KDbEscapedString operator+(const QVector & other ) const QList split(char sep) const; inline KDbEscapedString repeated(int times) const { return m_valid ? KDbEscapedString(QByteArray::repeated(times)) : KDbEscapedString::invalid(); } - short toShort(bool *ok = 0, int base = 10) const; - ushort toUShort(bool *ok = 0, int base = 10) const; - int toInt(bool *ok = 0, int base = 10) const; - uint toUInt(bool *ok = 0, int base = 10) const; - long toLong(bool *ok = 0, int base = 10) const; - ulong toULong(bool *ok = 0, int base = 10) const; - qlonglong toLongLong(bool *ok = 0, int base = 10) const; - qulonglong toULongLong(bool *ok = 0, int base = 10) const; - float toFloat(bool *ok = 0) const; - double toDouble(bool *ok = 0) const; + short toShort(bool *ok = nullptr, int base = 10) const; + ushort toUShort(bool *ok = nullptr, int base = 10) const; + int toInt(bool *ok = nullptr, int base = 10) const; + uint toUInt(bool *ok = nullptr, int base = 10) const; + long toLong(bool *ok = nullptr, int base = 10) const; + ulong toULong(bool *ok = nullptr, int base = 10) const; + qlonglong toLongLong(bool *ok = nullptr, int base = 10) const; + qulonglong toULongLong(bool *ok = nullptr, int base = 10) const; + float toFloat(bool *ok = nullptr) const; + double toDouble(bool *ok = nullptr) const; inline KDbEscapedString toBase64() const { return m_valid ? KDbEscapedString(QByteArray::toBase64()) : KDbEscapedString::invalid(); } inline KDbEscapedString toHex() const { return m_valid ? KDbEscapedString(QByteArray::toHex()) : KDbEscapedString::invalid(); } inline KDbEscapedString toPercentEncoding(const QByteArray &exclude = QByteArray(), const QByteArray &include = QByteArray(), char percent = '%') const { Q_UNUSED(percent); return m_valid ? KDbEscapedString(QByteArray::toPercentEncoding(exclude, include)) : KDbEscapedString::invalid(); } inline KDbEscapedString &setNum(short val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(ushort val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(int val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(uint val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(qlonglong val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(qulonglong val, int base = 10) { m_valid = true; QByteArray::setNum(val, base); return *this; } inline KDbEscapedString &setNum(float val, char f = 'g', int prec = 6) { m_valid = true; QByteArray::setNum(val, f, prec); return *this; } inline KDbEscapedString &setNum(double val, char f = 'g', int prec = 6) { m_valid = true; QByteArray::setNum(val, f, prec); return *this; } static inline KDbEscapedString number(int val, int base = 10) { return KDbEscapedString(QByteArray::number(val, base)); } static inline KDbEscapedString number(uint val, int base = 10) { return KDbEscapedString(QByteArray::number(val, base)); } static inline KDbEscapedString number(qlonglong val, int base = 10) { return KDbEscapedString(QByteArray::number(val, base)); } static inline KDbEscapedString number(qulonglong val, int base = 10) { return KDbEscapedString(QByteArray::number(val, base)); } static inline KDbEscapedString number(double val, char f = 'g', int prec = 6) { return KDbEscapedString(QByteArray::number(val, f, prec)); } static inline KDbEscapedString fromRawData(const char *s, int size) { return KDbEscapedString(QByteArray::fromRawData(s, size)); } static inline KDbEscapedString fromBase64(const QByteArray &base64) { return KDbEscapedString(QByteArray::fromBase64(base64)); } static inline KDbEscapedString fromBase64(const KDbEscapedString &base64) { return base64.isValid() ? KDbEscapedString(QByteArray::fromBase64(base64)) : KDbEscapedString::invalid(); } static inline KDbEscapedString fromHex(const QByteArray &hexEncoded) { return KDbEscapedString(QByteArray::fromHex(hexEncoded)); } static inline KDbEscapedString fromHex(const KDbEscapedString &hexEncoded) { return hexEncoded.isValid() ? KDbEscapedString(QByteArray::fromHex(hexEncoded)) : KDbEscapedString::invalid(); } static inline KDbEscapedString fromPercentEncoding(const QByteArray &pctEncoded, char percent = '%') { return KDbEscapedString(QByteArray::fromPercentEncoding(pctEncoded, percent)); } static inline KDbEscapedString fromPercentEncoding(const KDbEscapedString &pctEncoded, char percent = '%') { return pctEncoded.isValid() ? KDbEscapedString(QByteArray::fromPercentEncoding(pctEncoded, percent)) : KDbEscapedString::invalid(); } inline int count() const { return QByteArray::count(); } inline int length() const { return QByteArray::length(); } inline bool isNull() const { return QByteArray::isNull(); } KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4, const KDbEscapedString &a5) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4, const KDbEscapedString &a5, const KDbEscapedString &a6) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4, const KDbEscapedString &a5, const KDbEscapedString &a6, const KDbEscapedString &a7) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4, const KDbEscapedString &a5, const KDbEscapedString &a6, const KDbEscapedString &a7, const KDbEscapedString &a8) const; KDbEscapedString arg(const KDbEscapedString &a1, const KDbEscapedString &a2, const KDbEscapedString &a3, const KDbEscapedString &a4, const KDbEscapedString &a5, const KDbEscapedString &a6, const KDbEscapedString &a7, const KDbEscapedString &a8, const KDbEscapedString &a9) const; KDbEscapedString arg(const KDbEscapedString &a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(const QString &a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(const QByteArray &a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(int a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(uint a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(long a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(ulong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(qlonglong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(qulonglong a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(short a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(ushort a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(QChar a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(char a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const; KDbEscapedString arg(double a, int fieldWidth = 0, char format = 'g', int precision = -1, const QChar & fillChar = QLatin1Char( ' ' )) const; typedef QByteArray::DataPtr DataPtr; inline DataPtr &data_ptr() { return QByteArray::data_ptr(); } private: //! Used to create invalid string explicit inline KDbEscapedString(bool) : m_valid(false) {} //! Helper for methods having "bool* ok" argument inline bool checkValid(bool *ok) const { if (!m_valid) { if (ok) *ok = false; return false; } return true; } //! true if the string is valid; true by default bool m_valid; }; #ifndef QT_NO_DATASTREAM KDB_EXPORT QDataStream &operator<<(QDataStream &stream, const KDbEscapedString &string); KDB_EXPORT QDataStream &operator>>(QDataStream &stream, KDbEscapedString &string); #endif inline KDbEscapedString operator+(const KDbEscapedString &a1, const KDbEscapedString &a2) { if (!a1.isValid() || !a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1.toByteArray() + a2.toByteArray()); } inline KDbEscapedString operator+(const KDbEscapedString &a1, const QString &a2) { if (!a1.isValid()) return KDbEscapedString::invalid(); return a1 + KDbEscapedString(a2); } inline KDbEscapedString operator+(const KDbEscapedString &a1, const QByteArray &a2) { if (!a1.isValid()) return KDbEscapedString::invalid(); return a1 + KDbEscapedString(a2); } inline KDbEscapedString operator+(const KDbEscapedString &a1, const char* a2) { if (!a1.isValid()) return KDbEscapedString::invalid(); return a1 + KDbEscapedString(a2); } inline KDbEscapedString operator+(const KDbEscapedString &a1, QChar a2) { if (!a1.isValid()) return KDbEscapedString::invalid(); return a1 + KDbEscapedString(a2.toLatin1()); } inline KDbEscapedString operator+(const KDbEscapedString &a1, char a2) { if (!a1.isValid()) return KDbEscapedString::invalid(); return a1 + KDbEscapedString(a2); } inline KDbEscapedString operator+(const QString &a1, const KDbEscapedString &a2) { if (!a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1) + a2; } inline KDbEscapedString operator+(const QByteArray &a1, const KDbEscapedString &a2) { if (!a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1) + a2; } inline KDbEscapedString operator+(const char* a1, const KDbEscapedString &a2) { if (!a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1) + a2; } inline KDbEscapedString operator+(QChar a1, const KDbEscapedString &a2) { if (!a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1.toLatin1()) + a2; } inline KDbEscapedString operator+(char a1, const KDbEscapedString &a2) { if (!a2.isValid()) return KDbEscapedString::invalid(); return KDbEscapedString(a1) + a2; } inline bool operator==(const KDbEscapedString &a1, const QByteArray &a2) { return a1.isValid() && a1.toByteArray() == a2; } inline bool operator==(const KDbEscapedString &a1, const char *a2) { return a1.isValid() && a1.toByteArray() == a2; } inline bool operator==(const QByteArray &a1, const KDbEscapedString &a2) { return a2.isValid() && a1 == a2.toByteArray(); } inline bool operator==(const char *a1, const KDbEscapedString &a2) { return a2.isValid() && a1 == a2.toByteArray(); } Q_DECLARE_TYPEINFO(KDbEscapedString, Q_MOVABLE_TYPE); //Q_DECLARE_SHARED(KDbEscapedString) //! Sends escaped string @a string to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbEscapedString& string); #endif diff --git a/src/KDbField.cpp b/src/KDbField.cpp index 23cd99d9..6b6f1eef 100644 --- a/src/KDbField.cpp +++ b/src/KDbField.cpp @@ -1,863 +1,863 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2002 Joseph Wenninger Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbField.h" #include "KDbField_p.h" #include "KDbConnection.h" #include "KDbDriver.h" #include "KDbExpression.h" #include "KDbQuerySchema.h" #include "KDb.h" #include "kdb_debug.h" #include #include //! @todo make this configurable static int m_defaultMaxLength = 0; // unlimited //------------------------------------------------------- //! @internal Used in m_typeNames member to handle translated type names class FieldTypeNames : public QVector { public: FieldTypeNames(); QHash str2num; QStringList names; private: Q_DISABLE_COPY(FieldTypeNames) }; //! @internal Used in m_typeGroupNames member to handle translated type group names class FieldTypeGroupNames : public QVector { public: FieldTypeGroupNames(); QHash str2num; QStringList names; private: Q_DISABLE_COPY(FieldTypeGroupNames) }; //! real translated type names (and nontranslated type name strings) Q_GLOBAL_STATIC(FieldTypeNames, s_typeNames) //! real translated type group names (and nontranslated group name strings) Q_GLOBAL_STATIC(FieldTypeGroupNames, s_typeGroupNames) #define ADDTYPE(type, i18, str) \ (*this)[KDbField::type] = i18; \ (*this)[KDbField::type+KDbField::Null+1] = QLatin1String(str); \ str2num[ QString::fromLatin1(str).toLower() ] = KDbField::type; \ names.append(i18) #define ADDGROUP(type, i18, str) \ (*this)[KDbField::type] = i18; \ (*this)[KDbField::type+KDbField::LastTypeGroup+1] = QLatin1String(str); \ str2num[ QString::fromLatin1(str).toLower() ] = KDbField::type; \ names.append(i18) FieldTypeNames::FieldTypeNames() : QVector() { resize((KDbField::Null + 1)*2); ADDTYPE(InvalidType, KDbField::tr("Invalid Type"), "InvalidType"); ADDTYPE(Byte, KDbField::tr("Byte"), "Byte"); ADDTYPE(ShortInteger, KDbField::tr("Short Integer Number"), "ShortInteger"); ADDTYPE(Integer, KDbField::tr("Integer Number"), "Integer"); ADDTYPE(BigInteger, KDbField::tr("Big Integer Number"), "BigInteger"); ADDTYPE(Boolean, KDbField::tr("Yes/No Value"), "Boolean"); ADDTYPE(Date, KDbField::tr("Date"), "Date"); ADDTYPE(DateTime, KDbField::tr("Date and Time"), "DateTime"); ADDTYPE(Time, KDbField::tr("Time"), "Time"); ADDTYPE(Float, KDbField::tr("Single Precision Number"), "Float"); ADDTYPE(Double, KDbField::tr("Double Precision Number"), "Double"); ADDTYPE(Text, KDbField::tr("Text"), "Text"); ADDTYPE(LongText, KDbField::tr("Long Text"), "LongText"); ADDTYPE(BLOB, KDbField::tr("Object"), "BLOB"); ADDTYPE(Null, QLatin1String("NULL")/*don't translate*/, "NULL"); } //------------------------------------------------------- FieldTypeGroupNames::FieldTypeGroupNames() : QVector() { resize((KDbField::LastTypeGroup + 1)*2); ADDGROUP(InvalidGroup, KDbField::tr("Invalid Group"), "InvalidGroup"); ADDGROUP(TextGroup, KDbField::tr("Text"), "TextGroup"); ADDGROUP(IntegerGroup, KDbField::tr("Integer Number"), "IntegerGroup"); ADDGROUP(FloatGroup, KDbField::tr("Floating Point Number"), "FloatGroup"); ADDGROUP(BooleanGroup, KDbField::tr("Yes/No"), "BooleanGroup"); ADDGROUP(DateTimeGroup, KDbField::tr("Date/Time"), "DateTimeGroup"); ADDGROUP(BLOBGroup, KDbField::tr("Object"), "BLOBGroup"); } //------------------------------------------------------- KDbField::KDbField() { init(); setConstraints(NoConstraints); } KDbField::KDbField(KDbTableSchema *tableSchema) { init(); m_parent = tableSchema; m_order = tableSchema->fieldCount(); setConstraints(NoConstraints); } KDbField::KDbField(KDbQuerySchema *querySchema, const KDbExpression& expr) { init(); m_parent = querySchema; m_order = querySchema->fieldCount(); setConstraints(NoConstraints); setExpression(expr); } KDbField::KDbField(KDbQuerySchema *querySchema) { init(); m_parent = querySchema; m_order = querySchema->fieldCount(); setConstraints(NoConstraints); } KDbField::KDbField(const QString& name, Type type, Constraints constr, Options options, int maxLength, int precision, QVariant defaultValue, const QString& caption, const QString& description) - : m_parent(0) + : m_parent(nullptr) , m_name(name.toLower()) , m_precision(precision) , m_visibleDecimalPlaces(-1) , m_options(options) , m_defaultValue(defaultValue) , m_order(-1) , m_caption(caption) , m_desc(description) - , m_customProperties(0) + , m_customProperties(nullptr) , m_type(type) { m_expr = new KDbExpression(); setMaxLength(maxLength); setConstraints(constr); } /*! Copy constructor. */ KDbField::KDbField(const KDbField &f) { (*this) = f; if (f.m_customProperties) m_customProperties = new CustomPropertiesMap(f.customProperties()); if (!f.m_expr->isNull()) {//deep copy the expression //! @todo m_expr = new KDbExpression(*f.m_expr); m_expr = new KDbExpression(f.m_expr->clone()); } else { m_expr = new KDbExpression(); } } KDbField::~KDbField() { delete m_customProperties; delete m_expr; } KDbField* KDbField::copy() { return new KDbField(*this); } void KDbField::init() { - m_parent = 0; + m_parent = nullptr; m_type = InvalidType; m_precision = 0; m_visibleDecimalPlaces = -1; m_options = NoOptions; m_defaultValue = QVariant(QString()); m_order = -1; - m_customProperties = 0; + m_customProperties = nullptr; m_expr = new KDbExpression(); setMaxLength(0); // do not move this line up! setMaxLengthStrategy(DefinedMaxLength); // do not move this line up! } KDbField::Type KDbField::type() const { if (!m_expr->isNull()) return m_expr->type(); return m_type; } QVariant::Type KDbField::variantType(Type type) { switch (type) { case Byte: case ShortInteger: case Integer: case BigInteger: return QVariant::Int; case Boolean: return QVariant::Bool; case Date: return QVariant::Date; case DateTime: return QVariant::DateTime; case Time: return QVariant::Time; case Float: case Double: return QVariant::Double; case Text: case LongText: return QVariant::String; case BLOB: return QVariant::ByteArray; default: break; } return QVariant::Invalid; } template static inline QVariant tryConvert(const QVariant &value) { return value.canConvert() ? value.value() : value; } //static //! @todo use an array of functions? QVariant KDbField::convertToType(const QVariant &value, Type type) { switch (type) { case Byte: case ShortInteger: case Integer: return tryConvert(value); case BigInteger: return tryConvert(value); case Boolean: return tryConvert(value); case Date: return tryConvert(value); case DateTime: return tryConvert(value); case Time: return tryConvert(value); case Float: return tryConvert(value); case Double: return tryConvert(value); case Text: case LongText: return tryConvert(value); case BLOB: return tryConvert(value); default: break; } return QVariant(); } QString KDbField::typeName(Type type) { return s_typeNames->value(type, QString::number(type)); } QStringList KDbField::typeNames() { return s_typeNames->names; } QString KDbField::typeString(Type type) { return (type <= Null) ? s_typeNames->at(int(Null) + 1 + type) : (QLatin1String("Type") + QString::number(type)); } QString KDbField::typeGroupName(TypeGroup typeGroup) { return (typeGroup <= LastTypeGroup) ? s_typeGroupNames->at(typeGroup) : typeGroupString(typeGroup); } QStringList KDbField::typeGroupNames() { return s_typeGroupNames->names; } QString KDbField::typeGroupString(TypeGroup typeGroup) { return s_typeGroupNames->value(int(LastTypeGroup) + 1 + typeGroup, QLatin1String("TypeGroup") + QString::number(typeGroup)); } KDbField::Type KDbField::typeForString(const QString& typeString) { return s_typeNames->str2num.value(typeString.toLower(), InvalidType); } KDbField::TypeGroup KDbField::typeGroupForString(const QString& typeGroupString) { return s_typeGroupNames->str2num.value(typeGroupString.toLower(), InvalidGroup); } bool KDbField::isIntegerType(Type type) { switch (type) { case KDbField::Byte: case KDbField::ShortInteger: case KDbField::Integer: case KDbField::BigInteger: return true; default:; } return false; } bool KDbField::isNumericType(Type type) { switch (type) { case KDbField::Byte: case KDbField::ShortInteger: case KDbField::Integer: case KDbField::BigInteger: case KDbField::Float: case KDbField::Double: return true; default:; } return false; } bool KDbField::isFPNumericType(Type type) { return type == KDbField::Float || type == KDbField::Double; } bool KDbField::isDateTimeType(Type type) { switch (type) { case KDbField::Date: case KDbField::DateTime: case KDbField::Time: return true; default:; } return false; } bool KDbField::isTextType(Type type) { switch (type) { case KDbField::Text: case KDbField::LongText: return true; default:; } return false; } bool KDbField::hasEmptyProperty(Type type) { return KDbField::isTextType(type) || type == BLOB; } bool KDbField::isAutoIncrementAllowed(Type type) { return KDbField::isIntegerType(type); } KDbField::TypeGroup KDbField::typeGroup(Type type) { if (KDbField::isTextType(type)) return TextGroup; else if (KDbField::isIntegerType(type)) return IntegerGroup; else if (KDbField::isFPNumericType(type)) return FloatGroup; else if (type == Boolean) return BooleanGroup; else if (KDbField::isDateTimeType(type)) return DateTimeGroup; else if (type == BLOB) return BLOBGroup; return InvalidGroup; //unknown } KDbTableSchema* KDbField::table() { return dynamic_cast(m_parent); } const KDbTableSchema* KDbField::table() const { return dynamic_cast(m_parent); } void KDbField::setTable(KDbTableSchema *tableSchema) { m_parent = tableSchema; } KDbQuerySchema* KDbField::query() { return dynamic_cast(m_parent); } const KDbQuerySchema* KDbField::query() const { return dynamic_cast(m_parent); } void KDbField::setQuery(KDbQuerySchema *querySchema) { m_parent = querySchema; } void KDbField::setName(const QString& name) { m_name = name.toLower(); } void KDbField::setType(Type t) { if (!m_expr->isNull()) { kdbWarning() << "could not set type" << KDbField::typeName(t) << "because the field has expression assigned!"; return; } m_type = t; } void KDbField::setConstraints(Constraints c) { m_constraints = c; //pkey must be unique notnull if (isPrimaryKey()) { setPrimaryKey(true); } if (isIndexed()) { setIndexed(true); } if (isAutoIncrement() && !isAutoIncrementAllowed()) { setAutoIncrement(false); } } int KDbField::defaultMaxLength() { return m_defaultMaxLength; } void KDbField::setDefaultMaxLength(int maxLength) { m_defaultMaxLength = maxLength; } KDbField::MaxLengthStrategy KDbField::maxLengthStrategy() const { return m_maxLengthStrategy; } void KDbField::setMaxLengthStrategy(MaxLengthStrategy strategy) { m_maxLengthStrategy = strategy; } int KDbField::maxLength() const { return m_maxLength; } void KDbField::setMaxLength(int maxLength) { m_maxLength = maxLength; m_maxLengthStrategy = DefinedMaxLength; } void KDbField::setPrecision(int p) { if (!isFPNumericType()) return; m_precision = p; } void KDbField::setScale(int s) { if (!isFPNumericType()) return; m_maxLength = s; } void KDbField::setVisibleDecimalPlaces(int p) { if (!KDb::supportsVisibleDecimalPlacesProperty(type())) return; m_visibleDecimalPlaces = p < 0 ? -1 : p; } void KDbField::setUnsigned(bool u) { if (!isIntegerType()) { return; } m_options |= Unsigned; if (!u) m_options ^= Unsigned; } void KDbField::setDefaultValue(const QVariant& def) { m_defaultValue = def; } bool KDbField::setDefaultValue(const QByteArray& def) { if (def.isNull()) { m_defaultValue = QVariant(); return true; } bool ok; switch (type()) { case Byte: { unsigned int v = def.toUInt(&ok); if (!ok || v > 255) m_defaultValue = QVariant(); else m_defaultValue = QVariant(v); break; } case ShortInteger: { int v = def.toInt(&ok); if (!ok || (!(m_options & Unsigned) && (v < -32768 || v > 32767)) || ((m_options & Unsigned) && (v < 0 || v > 65535))) m_defaultValue = QVariant(); else m_defaultValue = QVariant(v); break; } case Integer: {//4 bytes long v = def.toLong(&ok); //! @todo if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))) || ((m_options & Unsigned) && (v < 0 || v > 0x100000000))) if (!ok || (!(m_options & Unsigned) && (-v > (int)0x07FFFFFFF || v > (int)(0x080000000 - 1)))) m_defaultValue = QVariant(); else m_defaultValue = QVariant((qint64)v); break; } case BigInteger: {//8 bytes //! @todo BigInteger support /* qint64 long v = def.toLongLong(&ok); //! @todo 2-part decoding if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1)))) m_defaultValue = QVariant(); else if (m_options & Unsigned) m_defaultValue=QVariant((quint64) v); else m_defaultValue = QVariant((qint64)v);*/ break; } case Boolean: { unsigned short v = def.toUShort(&ok); if (!ok || v > 1) m_defaultValue = QVariant(); else m_defaultValue = QVariant((bool)v); break; } case Date: {//YYYY-MM-DD QDate date = QDate::fromString(QLatin1String(def), Qt::ISODate); if (!date.isValid()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(date); break; } case DateTime: {//YYYY-MM-DDTHH:MM:SS QDateTime dt = QDateTime::fromString(QLatin1String(def), Qt::ISODate); if (!dt.isValid()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(dt); break; } case Time: {//HH:MM:SS QTime time = QTime::fromString(QLatin1String(def), Qt::ISODate); if (!time.isValid()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(time); break; } case Float: { float v = def.toFloat(&ok); if (!ok || ((m_options & Unsigned) && (v < 0.0))) m_defaultValue = QVariant(); else m_defaultValue = QVariant(v); break; } case Double: { double v = def.toDouble(&ok); if (!ok || ((m_options & Unsigned) && (v < 0.0))) m_defaultValue = QVariant(); else m_defaultValue = QVariant(v); break; } case Text: { if (def.isNull() || def.length() > maxLength()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(QLatin1String(def)); break; } case LongText: { if (def.isNull()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(QLatin1String(def)); break; } case BLOB: { //! @todo if (def.isNull()) m_defaultValue = QVariant(); else m_defaultValue = QVariant(def); break; } default: m_defaultValue = QVariant(); } return m_defaultValue.isNull(); } void KDbField::setAutoIncrement(bool a) { if (a && !isAutoIncrementAllowed()) return; if (isAutoIncrement() != a) m_constraints ^= KDbField::AutoInc; } void KDbField::setPrimaryKey(bool p) { if (isPrimaryKey() != p) m_constraints ^= KDbField::PrimaryKey; if (p) {//also set implied constraints setUniqueKey(true); setNotNull(true); setNotEmpty(true); setIndexed(true); } else { //! @todo is this ok for all engines? setAutoIncrement(false); } } void KDbField::setUniqueKey(bool u) { if (isUniqueKey() != u) { m_constraints ^= KDbField::Unique; if (u) { //also set implied constraints setNotNull(true); setIndexed(true); } } } void KDbField::setForeignKey(bool f) { if (isForeignKey() != f) m_constraints ^= KDbField::ForeignKey; } void KDbField::setNotNull(bool n) { if (isNotNull() != n) m_constraints ^=KDbField::NotNull; } void KDbField::setNotEmpty(bool n) { if (isNotEmpty() != n) m_constraints ^= KDbField::NotEmpty; } void KDbField::setIndexed(bool s) { if (isIndexed() != s) m_constraints ^= KDbField::Indexed; if (!s) {//also set implied constraints setPrimaryKey(false); setUniqueKey(false); setNotNull(false); setNotEmpty(false); } } void debug(QDebug dbg, const KDbField& field, KDbFieldDebugOptions options) { - KDbConnection *conn = field.table() ? field.table()->connection() : 0; + KDbConnection *conn = field.table() ? field.table()->connection() : nullptr; if (options & KDbFieldDebugAddName) { if (field.name().isEmpty()) { dbg.nospace() << " "; } else { dbg.nospace() << field.name() << ' '; } } if (field.options() & KDbField::Unsigned) dbg.nospace() << " UNSIGNED"; dbg.nospace() << ' ' << qPrintable((conn && conn->driver()) ? conn->driver()->sqlTypeName(field.type(), field) : KDbDriver::defaultSQLTypeName(field.type())); if (field.isFPNumericType() && field.precision() > 0) { if (field.scale() > 0) dbg.nospace() << QString::fromLatin1("(%1,%2)").arg(field.precision()).arg(field.scale()); else dbg.nospace() << QString::fromLatin1("(%1)").arg(field.precision()); } else if (field.type() == KDbField::Text && field.maxLength() > 0) dbg.space() << QString::fromLatin1("(%1)").arg(field.maxLength()); if (field.constraints() & KDbField::AutoInc) dbg.nospace() << " AUTOINC"; if (field.constraints() & KDbField::Unique) dbg.nospace() << " UNIQUE"; if (field.constraints() & KDbField::PrimaryKey) dbg.nospace() << " PKEY"; if (field.constraints() & KDbField::ForeignKey) dbg.nospace() << " FKEY"; if (field.constraints() & KDbField::NotNull) dbg.nospace() << " NOTNULL"; if (field.constraints() & KDbField::NotEmpty) dbg.nospace() << " NOTEMPTY"; if (!field.defaultValue().isNull()) { dbg.nospace() << qPrintable(QString::fromLatin1(" DEFAULT=[%1]") .arg(QLatin1String(field.defaultValue().typeName()))); dbg.nospace() << qPrintable(KDb::variantToString(field.defaultValue())); } if (field.isExpression()) { dbg.nospace() << " EXPRESSION="; dbg.nospace() << field.expression(); } const KDbField::CustomPropertiesMap customProperties(field.customProperties()); if (!customProperties.isEmpty()) { dbg.space() << QString::fromLatin1("CUSTOM PROPERTIES (%1): ").arg(customProperties.count()); bool first = true; for (KDbField::CustomPropertiesMap::ConstIterator it(customProperties.constBegin()); it != customProperties.constEnd(); ++it) { if (first) first = false; else dbg.nospace() << ','; dbg.space() << qPrintable(QString::fromLatin1("%1 = %2 (%3)") .arg(QLatin1String(it.key()), it.value().toString(), QLatin1String(it.value().typeName()))); } } } KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbField& field) { debug(dbg, field, KDbFieldDebugAddName); return dbg.space(); } KDB_EXPORT QDebug operator<<(QDebug dbg, KDbField::Type type) { return dbg.space() << qPrintable(KDbField::typeString(type)); } KDB_EXPORT QDebug operator<<(QDebug dbg, KDbField::TypeGroup typeGroup) { return dbg.space() << qPrintable(KDbField::typeGroupString(typeGroup)); } bool KDbField::isExpression() const { return !m_expr->isNull(); } KDbExpression KDbField::expression() { return *m_expr; } const KDbExpression KDbField::expression() const { return *m_expr; } void KDbField::setExpression(const KDbExpression& expr) { Q_ASSERT(!m_parent || dynamic_cast(m_parent)); if (*m_expr == expr) return; *m_expr = expr; } QVariant KDbField::customProperty(const QByteArray& propertyName, const QVariant& defaultValue) const { if (!m_customProperties) return defaultValue; return m_customProperties->value(propertyName, defaultValue); } void KDbField::setCustomProperty(const QByteArray& propertyName, const QVariant& value) { if (propertyName.isEmpty()) return; if (!m_customProperties) m_customProperties = new CustomPropertiesMap(); m_customProperties->insert(propertyName, value); } diff --git a/src/KDbFieldList.cpp b/src/KDbFieldList.cpp index 4615559d..f075b4d3 100644 --- a/src/KDbFieldList.cpp +++ b/src/KDbFieldList.cpp @@ -1,373 +1,373 @@ /* This file is part of the KDE project Copyright (C) 2003-2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbFieldList.h" #include "KDbConnection.h" #include "kdb_debug.h" KDbFieldList::KDbFieldList(bool owner) : m_fields(owner) - , m_autoinc_fields(0) + , m_autoinc_fields(nullptr) { } //! @todo IMPORTANT: (API) improve deepCopyFields KDbFieldList::KDbFieldList(const KDbFieldList& fl, bool deepCopyFields) : m_fields(fl.m_fields.autoDelete()) - , m_autoinc_fields(0) + , m_autoinc_fields(nullptr) { if (deepCopyFields) { //deep copy for the fields foreach(KDbField *origField, fl.m_fields) { KDbField *f = origField->copy(); if (origField->m_parent == &fl) f->m_parent = this; const bool addFieldOk = addField(f); Q_ASSERT(addFieldOk); } } } KDbFieldList::~KDbFieldList() { delete m_autoinc_fields; } int KDbFieldList::fieldCount() const { return m_fields.count(); } bool KDbFieldList::isEmpty() const { return m_fields.isEmpty(); } void KDbFieldList::clear() { m_fields_by_name.clear(); delete m_autoinc_fields; - m_autoinc_fields = 0; + m_autoinc_fields = nullptr; m_fields.clear(); m_sqlFields.clear(); } bool KDbFieldList::insertField(int index, KDbField *field) { Q_ASSERT(field); if (!field) { return false; } if (index > m_fields.count()) { kdbCritical() << "index (" << index << ") out of range"; return false; } m_fields.insert(index, field); if (!field->name().isEmpty()) m_fields_by_name.insert(field->name().toLower(), field); m_sqlFields.clear(); delete m_autoinc_fields; - m_autoinc_fields = 0; + m_autoinc_fields = nullptr; return true; } void KDbFieldList::renameField(const QString& oldName, const QString& newName) { KDbField *field = m_fields_by_name.value(oldName.toLower()); if (!field) { kdbCritical() << "no field found" << QString::fromLatin1("\"%1\"").arg(oldName); return; } renameFieldInternal(field, newName.toLower()); } void KDbFieldList::renameField(KDbField *field, const QString& newName) { if (!field || field != m_fields_by_name.value(field->name().toLower())) { kdbCritical() << "no field found" << QString::fromLatin1("\"%1\"").arg(field ? field->name() : QString()); return; } renameFieldInternal(field, newName.toLower()); } void KDbFieldList::renameFieldInternal(KDbField *field, const QString& newNameLower) { m_fields_by_name.remove(field->name().toLower()); field->setName(newNameLower); m_fields_by_name.insert(newNameLower, field); } bool KDbFieldList::addField(KDbField *field) { return insertField(m_fields.count(), field); } bool KDbFieldList::removeField(KDbField *field) { Q_ASSERT(field); if (!field) return false; if (m_fields_by_name.remove(field->name().toLower()) < 1) return false; m_fields.removeAt(m_fields.indexOf(field)); m_sqlFields.clear(); delete m_autoinc_fields; - m_autoinc_fields = 0; + m_autoinc_fields = nullptr; return true; } bool KDbFieldList::moveField(KDbField *field, int newIndex) { Q_ASSERT(field); if (!field || !m_fields.removeOne(field)) { return false; } if (newIndex > m_fields.count()) { newIndex = m_fields.count(); } m_fields.insert(newIndex, field); m_sqlFields.clear(); delete m_autoinc_fields; - m_autoinc_fields = 0; + m_autoinc_fields = nullptr; return true; } KDbField* KDbFieldList::field(int id) { return m_fields.value(id); } const KDbField* KDbFieldList::field(int id) const { return m_fields.value(id); } KDbField* KDbFieldList::field(const QString& name) { return m_fields_by_name.value(name.toLower()); } const KDbField* KDbFieldList::field(const QString& name) const { return m_fields_by_name.value(name.toLower()); } bool KDbFieldList::hasField(const KDbField& field) const { return m_fields.contains(const_cast(&field)); } int KDbFieldList::indexOf(const KDbField& field) const { return m_fields.indexOf(const_cast(&field)); } KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbFieldList& list) { if (list.fields()->isEmpty()) dbg.nospace() << ""; bool start = true; foreach(const KDbField *field, *list.fields()) { if (!start) dbg.nospace() << '\n'; else start = false; dbg.nospace() << " - " << *field; } return dbg.space(); } #define _ADD_FIELD(fname) \ { \ if (fname.isEmpty()) return fl; \ KDbField *f = m_fields_by_name.value(fname.toLower()); \ if (!f || !fl->addField(f)) { kdbWarning() << subListWarning1(fname); delete fl; return 0; } \ } static QString subListWarning1(const QString& fname) { return QString::fromLatin1("could not find field \"%1\"").arg(fname); } KDbFieldList* KDbFieldList::subList(const QString& n1, const QString& n2, const QString& n3, const QString& n4, const QString& n5, const QString& n6, const QString& n7, const QString& n8, const QString& n9, const QString& n10, const QString& n11, const QString& n12, const QString& n13, const QString& n14, const QString& n15, const QString& n16, const QString& n17, const QString& n18) { if (n1.isEmpty()) - return 0; + return nullptr; KDbFieldList *fl = new KDbFieldList(false); _ADD_FIELD(n1); _ADD_FIELD(n2); _ADD_FIELD(n3); _ADD_FIELD(n4); _ADD_FIELD(n5); _ADD_FIELD(n6); _ADD_FIELD(n7); _ADD_FIELD(n8); _ADD_FIELD(n9); _ADD_FIELD(n10); _ADD_FIELD(n11); _ADD_FIELD(n12); _ADD_FIELD(n13); _ADD_FIELD(n14); _ADD_FIELD(n15); _ADD_FIELD(n16); _ADD_FIELD(n17); _ADD_FIELD(n18); return fl; } KDbFieldList* KDbFieldList::subList(const QStringList& list) { KDbFieldList *fl = new KDbFieldList(false); for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { _ADD_FIELD((*it)); } return fl; } #undef _ADD_FIELD #define _ADD_FIELD(fname) \ { \ if (fname.isEmpty()) return fl; \ KDbField *f = m_fields_by_name.value(QLatin1String(fname.toLower())); \ if (!f || !fl->addField(f)) { kdbWarning() << subListWarning1(QLatin1String(fname)); delete fl; return 0; } \ } KDbFieldList* KDbFieldList::subList(const QList& list) { KDbFieldList *fl = new KDbFieldList(false); for (QList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { _ADD_FIELD((*it)); } return fl; } #undef _ADD_FIELD KDbFieldList* KDbFieldList::subList(const QList& list) { QScopedPointer fl(new KDbFieldList(false)); foreach(int index, list) { KDbField *f = field(index); if (!f) { kdbWarning() << QString::fromLatin1("could not find field at position %1").arg(index); - return 0; + return nullptr; } if (!fl->addField(f)) { kdbWarning() << QString::fromLatin1("could not add field at position %1").arg(index); - return 0; + return nullptr; } } return fl.take(); } QStringList KDbFieldList::names() const { QStringList r; foreach(KDbField *f, m_fields) { r += f->name().toLower(); } return r; } KDbField::ListIterator KDbFieldList::fieldsIterator() const { return m_fields.constBegin(); } KDbField::ListIterator KDbFieldList::fieldsIteratorConstEnd() const { return m_fields.constEnd(); } const KDbField::List* KDbFieldList::fields() const { return &m_fields; } bool KDbFieldList::isOwner() const { return m_fields.autoDelete(); } //static KDbEscapedString KDbFieldList::sqlFieldsList(const KDbField::List& list, KDbConnection *conn, const QString& separator, const QString& tableOrAlias, KDb::IdentifierEscapingType escapingType) { KDbEscapedString result; result.reserve(256); bool start = true; QString tableOrAliasAndDot; if (!tableOrAlias.isEmpty()) { tableOrAliasAndDot = ((conn && escapingType == KDb::DriverEscaping) ? conn->escapeIdentifier(tableOrAlias) : KDb::escapeIdentifier(tableOrAlias)) + QLatin1Char('.'); } foreach(KDbField *f, list) { if (!start) result.append(separator); else start = false; result = (result + tableOrAliasAndDot + ((conn && escapingType == KDb::DriverEscaping) ? conn->escapeIdentifier(f->name()) : KDb::escapeIdentifier(f->name())) ); } return result; } KDbEscapedString KDbFieldList::sqlFieldsList(KDbConnection *conn, const QString& separator, const QString& tableOrAlias, KDb::IdentifierEscapingType escapingType) const { if (!m_sqlFields.isEmpty()) return m_sqlFields; m_sqlFields = KDbFieldList::sqlFieldsList(m_fields, conn, separator, tableOrAlias, escapingType); return m_sqlFields; } KDbField::List* KDbFieldList::autoIncrementFields() const { if (m_autoinc_fields) return m_autoinc_fields; m_autoinc_fields = new KDbField::List(); m_autoinc_fields->setAutoDelete(false); foreach(KDbField *f, m_fields) { if (f->isAutoIncrement()) { m_autoinc_fields->append(f); } } return m_autoinc_fields; } diff --git a/src/KDbFieldList.h b/src/KDbFieldList.h index 0c82d891..c43b4def 100644 --- a/src/KDbFieldList.h +++ b/src/KDbFieldList.h @@ -1,192 +1,192 @@ /* This file is part of the KDE project Copyright (C) 2003-2010 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_FIELDLIST_H #define KDB_FIELDLIST_H #include #include #include "KDbGlobal.h" #include "KDbField.h" #include "KDbEscapedString.h" class KDbConnection; /*! Helper class that stores list of fields. */ class KDB_EXPORT KDbFieldList { public: /*! Creates empty list of fields. If @a owner is true, the list will be owner of any added field, what means that these field will be removed on the list destruction. Otherwise, the list just points any field that was added. @see isOwner() */ explicit KDbFieldList(bool owner = false); /*! Copy constructor. If @a deepCopyFields is true, all fields are deeply copied, else only pointer are copied. Reimplemented in KDbQuerySchema constructor. */ explicit KDbFieldList(const KDbFieldList& fl, bool deepCopyFields = true); /*! Destroys the list. If the list owns fields (see constructor), these are also deleted. */ virtual ~KDbFieldList(); /*! @return number of fields in the list. */ int fieldCount() const; /*! @return true if the list is empty. */ bool isEmpty() const; /*! Adds @a field at the and of field list. */ bool addField(KDbField *field); /*! Inserts @a field into a specified position (@a index). Note: You can reimplement this method but you should still call this implementation in your subclass. */ virtual bool insertField(int index, KDbField *field); /*! Removes field from the field list and deletes it. Use with care. Note: You can reimplement this method but you should still call this implementation in your subclass. @return false if this field does not belong to this list. */ virtual bool removeField(KDbField *field); /*! Moves fiels @a field from its current position to new position @a newIndex. If @a newIndex value is greater than fieldCount()-1, it is appended. @return false if this field does not belong to this list. */ virtual bool moveField(KDbField *field, int newIndex); - /*! @return field id or NULL if there is no such a field. */ + /*! @return field id or @c nullptr if there is no such a field. */ virtual KDbField* field(int id); /*! @overload KDbField* field(int id) */ virtual const KDbField* field(int id) const; - /*! @return field with name @a name or NULL if there is no such a field. */ + /*! @return field with name @a name or @c nullptr if there is no such a field. */ virtual KDbField* field(const QString& name); /*! @overload . DbField* field(const QString& name) const */ virtual const KDbField* field(const QString& name) const; /*! @return true if this list contains given @a field. */ bool hasField(const KDbField& field) const; /*! @return first occurrence of @a field in the list or -1 if this list does not contain this field. */ int indexOf(const KDbField& field) const; /*! @return list of field names for this list. */ QStringList names() const; KDbField::ListIterator fieldsIterator() const; KDbField::ListIterator fieldsIteratorConstEnd() const; const KDbField::List* fields() const; /*! @return list of autoincremented fields. The list is owned by this KDbFieldList object. */ KDbField::List* autoIncrementFields() const; /*! @return true if fields in the list are owned by this list. */ bool isOwner() const; /*! Removes all fields from the list. */ virtual void clear(); /*! Creates and returns a list that contain fields selected by name. At least one field (exising on this list) should be selected, otherwise 0 is returned. Returned KDbFieldList object is not owned by any parent (so you need to destroy yourself) and KDbField objects included in it are not owned by it (but still as before, by 'this' object). Returned list can be usable e.g. as argument for KDbConnection::insertRecord(). 0 is returned if at least one name cannot be found. */ KDbFieldList* subList(const QString& n1, const QString& n2 = QString(), const QString& n3 = QString(), const QString& n4 = QString(), const QString& n5 = QString(), const QString& n6 = QString(), const QString& n7 = QString(), const QString& n8 = QString(), const QString& n9 = QString(), const QString& n10 = QString(), const QString& n11 = QString(), const QString& n12 = QString(), const QString& n13 = QString(), const QString& n14 = QString(), const QString& n15 = QString(), const QString& n16 = QString(), const QString& n17 = QString(), const QString& n18 = QString() ) Q_REQUIRED_RESULT; /*! Like above, but for QStringList. */ KDbFieldList* subList(const QStringList& list) Q_REQUIRED_RESULT; /*! @overload subList(const QStringList&) */ KDbFieldList* subList(const QList& list) Q_REQUIRED_RESULT; /*! Like above, but with a list of field indices */ KDbFieldList* subList(const QList& list) Q_REQUIRED_RESULT; /*! @return a string that is a result of all field names concatenated and with @a separator. This is usable e.g. as argument like "field1,field2" for "INSERT INTO (xxx) ..". The result of this method is effectively cached, and it is invalidated when set of fields changes (e.g. using clear() or addField()). @a tableOrAlias, if provided is prepended to each field, so the resulting names will be in form tableOrAlias.fieldName. This option is used for building queries with joins, where fields have to be spicified without ambiguity. See @ref KDbConnection::selectStatement() for example use. @a escapingType can be used to alter default escaping type. If @a conn is not provided for DriverEscaping, no escaping is performed. */ KDbEscapedString sqlFieldsList(KDbConnection *conn, const QString& separator = QLatin1String(","), const QString& tableOrAlias = QString(), KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; /*! Like above, but this is convenient static function, so you can pass any @a list here. */ static KDbEscapedString sqlFieldsList(const KDbField::List& list, KDbConnection *conn, const QString& separator = QLatin1String(","), const QString& tableOrAlias = QString(), KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping); /*! @internal Renames field @a oldName to @a newName. Do not use this for physical renaming columns. Use KDbAlterTableHandler instead. */ void renameField(const QString& oldName, const QString& newName); /*! @internal @overload void renameField(const QString& oldName, const QString& newName) */ void renameField(KDbField *field, const QString& newName); protected: KDbField::List m_fields; mutable QHash m_fields_by_name; //!< Fields collected by name. Not used by KDbQuerySchema. mutable KDbField::List *m_autoinc_fields; private: void renameFieldInternal(KDbField *field, const QString& newNameLower); //! cached mutable KDbEscapedString m_sqlFields; }; //! Sends information about field list @a list to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbFieldList& list); #endif diff --git a/src/KDbIndexSchema.h b/src/KDbIndexSchema.h index 4e8c074d..6e8b1cf6 100644 --- a/src/KDbIndexSchema.h +++ b/src/KDbIndexSchema.h @@ -1,183 +1,183 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_INDEXSCHEMA_H #define KDB_INDEXSCHEMA_H #include #include "KDbObject.h" #include "KDbFieldList.h" class KDbConnection; class KDbTableSchema; class KDbQuerySchema; class KDbRelationship; /*! @short Provides information about database index that can be created for a database table. KDbIndexSchema object stores information about table fields that defines this index and additional properties like: whether index is unique or primary key (requires unique). Single-field index can be also auto generated. */ class KDB_EXPORT KDbIndexSchema : public KDbFieldList, public KDbObject { public: /*! Constructs empty index schema object that not assigned to any @a table. KDbTableSchema::addIndex() should be called afterwards, before adding any fields or attaching relationships. Any fields added with addField() will not be owned by index but by their table. */ explicit KDbIndexSchema(); /*! Deletes the index. Referenced KDbField objects are not deleted. All KDbRelationship objects listed by masterRelationships() are detached from detail-side indices and then deleted. KDbRelationship objects listed by detailsRelationships() are not deleted. */ - virtual ~KDbIndexSchema(); + ~KDbIndexSchema() override; /*! Adds field at the end of field list. KDbField will not be owned by index. KDbField must belong to a table specified by a KDbTableSchema::addIndex() call, otherwise field couldn't be added. @note Do not forget to add the field to a table, because adding it only to the KDbIndexSchema is not enough. */ virtual bool addField(KDbField *field); /*! @return table that index belongs to Index should be assigned to a table using KDbTableSchema::addIndex(). If it is not, table() returns @c nullptr. */ KDbTableSchema* table(); /*! @return table that index is defined for, const version. */ const KDbTableSchema* table() const; /*! @return list of relationships from the table (of this index), i.e. any such relationship in which this table is at 'master' side. See KDbRelationship class documentation for more information. All objects on this list will be automatically deleted when this KDbIndexSchema object is deleted. */ QList masterRelationships() const; /*! @return list of relationships to the table (of this index), i.e. any such relationship in which this table is at 'details' side. See KDbRelationship class documentation for more information. */ QList detailsRelationships() const; /*! Attaches relationship definition @a rel to this KDbIndexSchema object. If @a rel relationship has this KDbIndexSchema defined at the master-side, @a rel is added to the list of master relationships (available with masterRelationships()). If @a rel relationship has this KDbIndexSchema defined at the details-side, @a rel is added to the list of details relationships (available with detailsRelationships()). For the former case, attached @a rel object is now owned by this KDbIndexSchema object. Note: call detachRelationship() for KDbIndexSchema object that @a rel was previously attached to, if any. @note Before using attachRelationship() the index KDbField must already belong to a table specified by a KDbTableSchema::addIndex() call. */ void attachRelationship(KDbRelationship *rel); /*! Detaches relationship definition @a rel for this KDbIndexSchema object from the list of master relationships (available with masterRelationships()), or from details relationships list, depending for which side of the relationship is this IndexSchem object assigned. Note: If @a rel was detached from masterRelationships() list, this object now has no parent, so it must be attached to other index or deleted. */ void detachRelationship(KDbRelationship *rel); /*! @return true if index is auto-generated. Auto-generated index is one-field index that was automatically generated for CREATE TABLE statement when the field has UNIQUE or PRIMARY KEY constraint enabled. Any newly created KDbIndexSchema object has this flag set to false. This flag is handled internally by KDbTableSchema. It can be usable for GUI application if we do not want display implicity/auto generated indices on the indices list or we if want to show these indices to the user in a special way. */ bool isAutoGenerated() const; /*! @return true if this index is primary key of its table. This can be one or multifield. */ bool isPrimaryKey() const; /*! Sets PRIMARY KEY flag. @see isPrimary(). Note: Setting PRIMARY KEY on (true), UNIQUE flag will be also implicity set. */ void setPrimaryKey(bool set); /*! @return true if this is unique index. This can be one or multifield. */ bool isUnique() const; /*! Sets UNIQUE flag. @see isUnique(). Note: Setting UNIQUE off (false), PRIMARY KEY flag will be also implicity set off, because this UNIQUE is the requirement for PRIMARY KEYS. */ void setUnique(bool set); /*! @return true if the index defines a foreign key, Created implicity for KDbRelationship object.*/ bool isForeignKey() const; protected: //! Used by KDbTableSchema::copyIndex(const KDbIndexSchema&) KDbIndexSchema(const KDbIndexSchema& index, KDbTableSchema* parentTable); //! Assigns this index to @a table //! table() must be @c nullptr and @a table must be not @a nullptr //! @since 3.1 void setTable(KDbTableSchema *table); /*! Sets auto-generated flag. This method should be called only from KDbTableSchema code @see isAutoGenerated(). */ void setAutoGenerated(bool set); /*! If @a set is true, declares that the index defines a foreign key, created implicity for KDbRelationship object. Setting this to true, implies clearing 'primary key', 'unique' and 'auto generated' flags. If this index contains just single field, it's 'foreign field' flag will be set to true as well. */ void setForeignKey(bool set); /*! Internal version of attachRelationship(). If @a ownedByMaster is true, attached @a rel object will be owned by this index. */ void attachRelationship(KDbRelationship *rel, bool ownedByMaster); friend class KDbConnection; friend class KDbTableSchema; friend class KDbQuerySchema; friend class KDbRelationship; private: class Private; Private * const d; Q_DISABLE_COPY(KDbIndexSchema) }; //! Sends information about index schema @a index to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbIndexSchema& index); #endif diff --git a/src/KDbLookupFieldSchema.cpp b/src/KDbLookupFieldSchema.cpp index 8c9b87b9..3974f7a1 100644 --- a/src/KDbLookupFieldSchema.cpp +++ b/src/KDbLookupFieldSchema.cpp @@ -1,755 +1,755 @@ /* This file is part of the KDE project Copyright (C) 2006-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbLookupFieldSchema.h" #include "KDb.h" #include "kdb_debug.h" #include #include #include #include #include //! @internal class Q_DECL_HIDDEN KDbLookupFieldSchemaRecordSource::Private { public: Private() : type(KDbLookupFieldSchemaRecordSource::NoType) { } Private(const Private &other) { copy(other); } #define KDbLookupFieldSchemaRecordSourcePrivateArgs(o) std::tie(o.type, o.name, o.values) void copy(const Private &other) { KDbLookupFieldSchemaRecordSourcePrivateArgs((*this)) = KDbLookupFieldSchemaRecordSourcePrivateArgs(other); } bool operator==(const KDbLookupFieldSchemaRecordSource::Private &other) const { return KDbLookupFieldSchemaRecordSourcePrivateArgs((*this)) == KDbLookupFieldSchemaRecordSourcePrivateArgs(other); } KDbLookupFieldSchemaRecordSource::Type type; QString name; QStringList values; }; //! @internal class Q_DECL_HIDDEN KDbLookupFieldSchema::Private { public: Private() : boundColumn(-1) , maxVisibleRecords(KDB_LOOKUP_FIELD_DEFAULT_MAX_VISIBLE_RECORDS) , displayWidget(KDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET) , columnHeadersVisible(KDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE) , limitToList(KDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST) { } Private(const Private &other) { copy(other); } #define KDbLookupFieldSchemaPrivateArgs(o) std::tie(o.recordSource, o.boundColumn, o.visibleColumns, \ o.columnWidths, o.maxVisibleRecords, o.displayWidget, \ o.columnHeadersVisible, o.limitToList) void copy(const Private &other) { KDbLookupFieldSchemaPrivateArgs((*this)) = KDbLookupFieldSchemaPrivateArgs(other); } bool operator==(const KDbLookupFieldSchema::Private &other) const { return KDbLookupFieldSchemaPrivateArgs((*this)) == KDbLookupFieldSchemaPrivateArgs(other); } KDbLookupFieldSchemaRecordSource recordSource; int boundColumn; QList visibleColumns; QList columnWidths; int maxVisibleRecords; DisplayWidget displayWidget; bool columnHeadersVisible; bool limitToList; }; //! Cache class LookupFieldSchemaStatic { public: LookupFieldSchemaStatic() : typeNames({ QString(), // no type QLatin1String("table"), QLatin1String("query"), QLatin1String("sql"), QLatin1String("valuelist"), QLatin1String("fieldlist")}) { typesForNames.insert(QLatin1String("table"), KDbLookupFieldSchemaRecordSource::Table); typesForNames.insert(QLatin1String("query"), KDbLookupFieldSchemaRecordSource::Query); typesForNames.insert(QLatin1String("sql"), KDbLookupFieldSchemaRecordSource::SQLStatement); typesForNames.insert(QLatin1String("valuelist"), KDbLookupFieldSchemaRecordSource::ValueList); typesForNames.insert(QLatin1String("fieldlist"), KDbLookupFieldSchemaRecordSource::KDbFieldList); } const std::vector typeNames; QHash typesForNames; private: Q_DISABLE_COPY(LookupFieldSchemaStatic) }; Q_GLOBAL_STATIC(LookupFieldSchemaStatic, KDb_lookupFieldSchemaStatic) //---------------------------- KDbLookupFieldSchemaRecordSource::KDbLookupFieldSchemaRecordSource() : d(new Private) { } KDbLookupFieldSchemaRecordSource::KDbLookupFieldSchemaRecordSource(const KDbLookupFieldSchemaRecordSource& other) : d(new Private(*other.d)) { } KDbLookupFieldSchemaRecordSource::~KDbLookupFieldSchemaRecordSource() { delete d; } KDbLookupFieldSchemaRecordSource::Type KDbLookupFieldSchemaRecordSource::type() const { return d->type; } void KDbLookupFieldSchemaRecordSource::setType(Type type) { d->type = type; } QString KDbLookupFieldSchemaRecordSource::name() const { return d->name; } void KDbLookupFieldSchemaRecordSource::setName(const QString& name) { d->name = name; d->values.clear(); } QString KDbLookupFieldSchemaRecordSource::typeName() const { Q_ASSERT(size_t(d->type) < KDb_lookupFieldSchemaStatic->typeNames.size()); return KDb_lookupFieldSchemaStatic->typeNames[d->type]; } void KDbLookupFieldSchemaRecordSource::setTypeByName(const QString& typeName) { setType(KDb_lookupFieldSchemaStatic->typesForNames.value(typeName, NoType)); } QStringList KDbLookupFieldSchemaRecordSource::values() const { return d->values; } void KDbLookupFieldSchemaRecordSource::setValues(const QStringList& values) { d->name.clear(); d->values = values; } KDbLookupFieldSchemaRecordSource& KDbLookupFieldSchemaRecordSource::operator=(const KDbLookupFieldSchemaRecordSource & other) { if (this != &other) { *d = *other.d; } return *this; } bool KDbLookupFieldSchemaRecordSource::operator==(const KDbLookupFieldSchemaRecordSource &other) const { return *d == *other.d; } QDebug operator<<(QDebug dbg, const KDbLookupFieldSchemaRecordSource& source) { dbg.nospace() << "LookupFieldSchemaRecordSource TYPE:"; dbg.space() << source.typeName(); dbg.space() << "NAME:"; dbg.space() << source.name(); dbg.space() << "VALUES:"; dbg.space() << source.values().join(QLatin1String("|")) << '\n'; return dbg.nospace(); } //---------------------------- KDbLookupFieldSchema::KDbLookupFieldSchema() : d(new Private) { } KDbLookupFieldSchema::KDbLookupFieldSchema(const KDbLookupFieldSchema &schema) : d(new Private(*schema.d)) { } KDbLookupFieldSchema::~KDbLookupFieldSchema() { delete d; } static bool setBoundColumn(KDbLookupFieldSchema *lookup, const QVariant &val) { if (val.isNull()) { lookup->setBoundColumn(-1); } else { bool ok; const int ival = val.toInt(&ok); if (!ok) return false; lookup->setBoundColumn(ival); } return true; } static bool setVisibleColumns(KDbLookupFieldSchema *lookup, const QVariant &val) { QList variantList; if (val.canConvert(QVariant::Int)) { //! @todo Remove this case: it's for backward compatibility with Kexi's 1.1.2 table designer GUI //! supporting only single lookup column. variantList.append(val); } else { variantList = val.toList(); } QList visibleColumns; foreach(const QVariant& variant, variantList) { bool ok; const int ival = variant.toInt(&ok); if (!ok) { return false; } visibleColumns.append(ival); } lookup->setVisibleColumns(visibleColumns); return true; } static bool setColumnWidths(KDbLookupFieldSchema *lookup, const QVariant &val) { QList widths; foreach(const QVariant& variant, val.toList()) { bool ok; const int ival = variant.toInt(&ok); if (!ok) return false; widths.append(ival); } lookup->setColumnWidths(widths); return true; } static bool setDisplayWidget(KDbLookupFieldSchema *lookup, const QVariant &val) { bool ok; const int ival = val.toInt(&ok); if (!ok || ival > KDbLookupFieldSchema::ListBox) return false; lookup->setDisplayWidget(static_cast(ival)); return true; } KDbLookupFieldSchemaRecordSource KDbLookupFieldSchema::recordSource() const { return d->recordSource; } void KDbLookupFieldSchema::setRecordSource(const KDbLookupFieldSchemaRecordSource& recordSource) { d->recordSource = recordSource; } void KDbLookupFieldSchema::setMaxVisibleRecords(int count) { if (count == 0) d->maxVisibleRecords = KDB_LOOKUP_FIELD_DEFAULT_MAX_VISIBLE_RECORDS; else if (count > KDB_LOOKUP_FIELD_LIMIT_MAX_VISIBLE_RECORDS) d->maxVisibleRecords = KDB_LOOKUP_FIELD_LIMIT_MAX_VISIBLE_RECORDS; else d->maxVisibleRecords = count; } QDebug operator<<(QDebug dbg, const KDbLookupFieldSchema& lookup) { dbg.nospace() << "LookupFieldSchema("; dbg.space() << lookup.recordSource(); dbg.space() << "boundColumn:"; dbg.space() << lookup.boundColumn(); dbg.space() << "visibleColumns:"; bool first = true; foreach(int visibleColumn, lookup.visibleColumns()) { if (first) { first = false; dbg.nospace(); } else dbg.nospace() << ';'; dbg.nospace() << visibleColumn; } dbg.space() << "maxVisibleRecords:"; dbg.space() << lookup.maxVisibleRecords(); dbg.space() << "displayWidget:"; dbg.space() << (lookup.displayWidget() == KDbLookupFieldSchema::ComboBox ? "ComboBox" : "ListBox"); dbg.space() << "columnHeadersVisible:"; dbg.space() << lookup.columnHeadersVisible(); dbg.space() << "limitToList:"; dbg.space() << lookup.limitToList(); dbg.space() << "columnWidths:"; first = true; const QList columnWidths(lookup.columnWidths()); for (int width : columnWidths) { if (first) first = false; else dbg.nospace() << ';'; dbg.space() << width; } dbg.nospace() << ')'; return dbg.space(); } /* static */ KDbLookupFieldSchema *KDbLookupFieldSchema::loadFromDom(const QDomElement& lookupEl) { KDbLookupFieldSchema *lookupFieldSchema = new KDbLookupFieldSchema(); KDbLookupFieldSchemaRecordSource recordSource; for (QDomNode node = lookupEl.firstChild(); !node.isNull(); node = node.nextSibling()) { QDomElement el = node.toElement(); const QByteArray name(el.tagName().toLatin1()); if (name == "row-source") { /* empty | table|query|sql|valuelist|fieldlist #required because there can be #table and query with the same name #"fieldlist" (basically a list of #column names of a table/query, #"Field List" as in MSA) string #table/query name, etc. or KEXISQL SELECT QUERY ... #for "valuelist" type ... */ for (el = el.firstChild().toElement(); !el.isNull(); el = el.nextSibling().toElement()) { const QByteArray childName(el.tagName().toLatin1()); if (childName == "type") { recordSource.setTypeByName(el.text()); } else if (childName == "name") { recordSource.setName(el.text()); //! @todo handle fieldlist (retrieve from external table or so?), use KDbLookupFieldSchemaRecordSource::setValues() } } } else if (name == "bound-column") { /* number #in later implementation there can be more columns */ bool ok; const QVariant val = KDb::loadPropertyValueFromDom(el.firstChild(), &ok); if (!ok || !::setBoundColumn(lookupFieldSchema, val)) { delete lookupFieldSchema; - return 0; + return nullptr; } } else if (name == "visible-column") { /* #a column that has to be visible in the combo box number 1 number 2 [..] */ QVariantList list; for (QDomNode childNode = el.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) { bool ok; const QVariant val = KDb::loadPropertyValueFromDom(childNode, &ok); if (!ok) { delete lookupFieldSchema; - return 0; + return nullptr; } list.append(val); } if (!::setVisibleColumns(lookupFieldSchema, list)) { delete lookupFieldSchema; - return 0; + return nullptr; } } else if (name == "column-widths") { /* #column widths, -1 means 'default' int ... int */ QVariantList columnWidths; for (el = el.firstChild().toElement(); !el.isNull(); el = el.nextSibling().toElement()) { bool ok; QVariant val = KDb::loadPropertyValueFromDom(el, &ok); if (!ok) { delete lookupFieldSchema; - return 0; + return nullptr; } columnWidths.append(val); } if (!::setColumnWidths(lookupFieldSchema, columnWidths)) { delete lookupFieldSchema; - return 0; + return nullptr; } } else if (name == "show-column-headers") { /* true/false */ bool ok; const QVariant val = KDb::loadPropertyValueFromDom(el.firstChild(), &ok); if (!ok) { delete lookupFieldSchema; - return 0; + return nullptr; } if (val.type() == QVariant::Bool) lookupFieldSchema->setColumnHeadersVisible(val.toBool()); } else if (name == "list-rows") { /* 1..100 */ bool ok; const QVariant val = KDb::loadPropertyValueFromDom(el.firstChild(), &ok); if (!ok) { delete lookupFieldSchema; - return 0; + return nullptr; } if (val.type() == QVariant::Int) lookupFieldSchema->setMaxVisibleRecords(val.toInt()); } else if (name == "limit-to-list") { /* true/false */ bool ok; const QVariant val = KDb::loadPropertyValueFromDom(el.firstChild(), &ok); if (!ok) { delete lookupFieldSchema; - return 0; + return nullptr; } if (val.type() == QVariant::Bool) lookupFieldSchema->setLimitToList(val.toBool()); } else if (name == "display-widget") { const QByteArray displayWidgetName(el.text().toLatin1()); if (displayWidgetName == "combobox") { lookupFieldSchema->setDisplayWidget(KDbLookupFieldSchema::ComboBox); } else if (displayWidgetName == "listbox") { lookupFieldSchema->setDisplayWidget(KDbLookupFieldSchema::ListBox); } } } lookupFieldSchema->setRecordSource(recordSource); return lookupFieldSchema; } void KDbLookupFieldSchema::saveToDom(QDomDocument *doc, QDomElement *parentEl) { Q_ASSERT(doc); Q_ASSERT(parentEl); QDomElement lookupColumnEl, recordSourceEl, recordSourceTypeEl, nameEl; if (!recordSource().name().isEmpty()) { lookupColumnEl = doc->createElement(QLatin1String("lookup-column")); parentEl->appendChild(lookupColumnEl); recordSourceEl = doc->createElement(QLatin1String("row-source")); lookupColumnEl.appendChild(recordSourceEl); recordSourceTypeEl = doc->createElement(QLatin1String("type")); recordSourceEl.appendChild(recordSourceTypeEl); recordSourceTypeEl.appendChild(doc->createTextNode(recordSource().typeName())); //can be empty nameEl = doc->createElement(QLatin1String("name")); recordSourceEl.appendChild(nameEl); nameEl.appendChild(doc->createTextNode(recordSource().name())); } const QStringList& values(recordSource().values()); if (!values.isEmpty()) { QDomElement valuesEl(doc->createElement(QLatin1String("values"))); recordSourceEl.appendChild(valuesEl); for (QStringList::ConstIterator it = values.constBegin(); it != values.constEnd(); ++it) { QDomElement valueEl(doc->createElement(QLatin1String("value"))); valuesEl.appendChild(valueEl); valueEl.appendChild(doc->createTextNode(*it)); } } if (boundColumn() >= 0) { KDb::saveNumberElementToDom(doc, &lookupColumnEl, QLatin1String("bound-column"), boundColumn()); } QList visibleColumns(this->visibleColumns()); if (!visibleColumns.isEmpty()) { QDomElement visibleColumnEl(doc->createElement(QLatin1String("visible-column"))); lookupColumnEl.appendChild(visibleColumnEl); foreach(int visibleColumn, visibleColumns) { QDomElement numberEl(doc->createElement(QLatin1String("number"))); visibleColumnEl.appendChild(numberEl); numberEl.appendChild(doc->createTextNode(QString::number(visibleColumn))); } } const QList columnWidths(this->columnWidths()); if (!columnWidths.isEmpty()) { QDomElement columnWidthsEl(doc->createElement(QLatin1String("column-widths"))); lookupColumnEl.appendChild(columnWidthsEl); foreach(int columnWidth, columnWidths) { QDomElement columnWidthEl(doc->createElement(QLatin1String("number"))); columnWidthsEl.appendChild(columnWidthEl); columnWidthEl.appendChild(doc->createTextNode(QString::number(columnWidth))); } } if (columnHeadersVisible() != KDB_LOOKUP_FIELD_DEFAULT_HEADERS_VISIBLE) KDb::saveBooleanElementToDom(doc, &lookupColumnEl, QLatin1String("show-column-headers"), columnHeadersVisible()); if (maxVisibleRecords() != KDB_LOOKUP_FIELD_DEFAULT_MAX_VISIBLE_RECORDS) KDb::saveNumberElementToDom(doc, &lookupColumnEl, QLatin1String("list-rows"), maxVisibleRecords()); if (limitToList() != KDB_LOOKUP_FIELD_DEFAULT_LIMIT_TO_LIST) KDb::saveBooleanElementToDom(doc, &lookupColumnEl, QLatin1String("limit-to-list"), limitToList()); if (displayWidget() != KDB_LOOKUP_FIELD_DEFAULT_DISPLAY_WIDGET) { QDomElement displayWidgetEl(doc->createElement(QLatin1String("display-widget"))); lookupColumnEl.appendChild(displayWidgetEl); displayWidgetEl.appendChild( doc->createTextNode( QLatin1String((displayWidget() == ListBox) ? "listbox" : "combobox"))); } } void KDbLookupFieldSchema::getProperties(QMap *values) const { values->clear(); KDb::getProperties(this, values); } bool KDbLookupFieldSchema::setProperty(const QByteArray& propertyName, const QVariant& value) { bool ok; if ( "rowSource" == propertyName || "rowSourceType" == propertyName || "rowSourceValues" == propertyName) { KDbLookupFieldSchemaRecordSource recordSource(this->recordSource()); if ("rowSource" == propertyName) recordSource.setName(value.toString()); else if ("rowSourceType" == propertyName) recordSource.setTypeByName(value.toString()); else if ("rowSourceValues" == propertyName) { recordSource.setValues(value.toStringList()); } else { kdbCritical() << "impl. error: unsupported property" << propertyName; } this->setRecordSource(recordSource); } else if ("boundColumn" == propertyName) { if (!::setBoundColumn(this, value)) { return false; } } else if ("visibleColumn" == propertyName) { if (!::setVisibleColumns(this, value)) { return false; } } else if ("columnWidths" == propertyName) { if (!::setColumnWidths(this, value)) { return false; } } else if ("showColumnHeaders" == propertyName) { setColumnHeadersVisible(value.toBool()); } else if ("listRows" == propertyName) { const int ival = value.toInt(&ok); if (!ok) return false; setMaxVisibleRecords(ival); } else if ("limitToList" == propertyName) { setLimitToList(value.toBool()); } else if ("displayWidget" == propertyName) { if (!::setDisplayWidget(this, value)) { return false; } } return true; } bool KDbLookupFieldSchema::setProperties(const QMap& values) { QMap::ConstIterator it; KDbLookupFieldSchemaRecordSource recordSource(this->recordSource()); bool ok; bool updateRecordSource = false; if ((it = values.find("rowSource")) != values.constEnd()) { recordSource.setName(it.value().toString()); updateRecordSource = true; } if ((it = values.find("rowSourceType")) != values.constEnd()) { recordSource.setTypeByName(it.value().toString()); updateRecordSource = true; } if ((it = values.find("rowSourceValues")) != values.constEnd()) { if (!it.value().isNull()) { recordSource.setValues(it.value().toStringList()); updateRecordSource = true; } } if (updateRecordSource) { setRecordSource(recordSource); } if ((it = values.find("boundColumn")) != values.constEnd()) { if (!::setBoundColumn(this, it.value())) { return false; } } if ((it = values.find("visibleColumn")) != values.constEnd()) { if (!::setVisibleColumns(this, it.value())) { return false; } } if ((it = values.find("columnWidths")) != values.constEnd()) { if (!::setColumnWidths(this, it.value())) { return false; } } if ((it = values.find("showColumnHeaders")) != values.constEnd()) { setColumnHeadersVisible(it.value().toBool()); } if ((it = values.find("listRows")) != values.constEnd()) { int ival = it.value().toInt(&ok); if (!ok) return false; setMaxVisibleRecords(ival); } if ((it = values.find("limitToList")) != values.constEnd()) { setLimitToList(it.value().toBool()); } if ((it = values.find("displayWidget")) != values.constEnd()) { if (!::setDisplayWidget(this, it.value())) { return false; } } return true; } int KDbLookupFieldSchema::boundColumn() const { return d->boundColumn; } void KDbLookupFieldSchema::setBoundColumn(int column) { d->boundColumn = column >= 0 ? column : -1; } QList KDbLookupFieldSchema::visibleColumns() const { return d->visibleColumns; } void KDbLookupFieldSchema::setVisibleColumns(const QList& list) { d->visibleColumns = list; } int KDbLookupFieldSchema::visibleColumn(int index) const { if (index >= d->visibleColumns.count()) { return -1; } return index; } QList KDbLookupFieldSchema::columnWidths() const { return d->columnWidths; } void KDbLookupFieldSchema::setColumnWidths(const QList& widths) { d->columnWidths = widths; } bool KDbLookupFieldSchema::columnHeadersVisible() const { return d->columnHeadersVisible; } void KDbLookupFieldSchema::setColumnHeadersVisible(bool set) { d->columnHeadersVisible = set; } int KDbLookupFieldSchema::maxVisibleRecords() const { return d->maxVisibleRecords; } bool KDbLookupFieldSchema::limitToList() const { return d->limitToList; } void KDbLookupFieldSchema::setLimitToList(bool set) { d->limitToList = set; } KDbLookupFieldSchema::DisplayWidget KDbLookupFieldSchema::displayWidget() const { return d->displayWidget; } void KDbLookupFieldSchema::setDisplayWidget(DisplayWidget widget) { d->displayWidget = widget; } KDbLookupFieldSchema& KDbLookupFieldSchema::operator=(const KDbLookupFieldSchema & other) { if (this != &other) { *d = *other.d; } return *this; } bool KDbLookupFieldSchema::operator==(const KDbLookupFieldSchema &other) const { return *d == *other.d; } diff --git a/src/KDbMessageHandler.cpp b/src/KDbMessageHandler.cpp index 62e10e55..99e94af8 100644 --- a/src/KDbMessageHandler.cpp +++ b/src/KDbMessageHandler.cpp @@ -1,182 +1,182 @@ /* This file is part of the KDE project Copyright (C) 2004-2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbMessageHandler.h" #include "KDbResult.h" class Q_DECL_HIDDEN KDbMessageGuard::Private { public: Private() {} const KDbResult *result; KDbResultable *resultable; KDbMessageHandler *handler; private: Q_DISABLE_COPY(Private) }; KDbMessageGuard::KDbMessageGuard(KDbResultable *resultable) : d(new Private) { Q_ASSERT(resultable); - d->result = 0; + d->result = nullptr; d->resultable = resultable; - d->handler = 0; + d->handler = nullptr; } KDbMessageGuard::KDbMessageGuard(const KDbResult &result, KDbMessageHandler *handler) : d(new Private) { Q_ASSERT(handler); d->result = &result; - d->resultable = 0; + d->resultable = nullptr; d->handler = handler; } KDbMessageGuard::~KDbMessageGuard() { if (d->handler && d->result && d->result->isError()) { // variant 1 d->handler->showErrorMessage(*d->result); } else if (d->resultable && d->resultable->messageHandler() && d->resultable->result().isError()){ // variant 2 d->resultable->messageHandler()->showErrorMessage(d->resultable->result()); } delete d; } //------------------------------------------------ KDbMessageTitleSetter::KDbMessageTitleSetter(KDbResult* result, const QString& message) : m_result(result) , m_prevMsgTitle(result->messageTitle()) { m_result->setMessageTitle(message); } KDbMessageTitleSetter::KDbMessageTitleSetter(KDbResultable* resultable, const QString& message) : m_result(&resultable->m_result) , m_prevMsgTitle(resultable->result().messageTitle()) { m_result->setMessageTitle(message); } KDbMessageTitleSetter::~KDbMessageTitleSetter() { m_result->setMessageTitle(m_prevMsgTitle); } //------------------------------------------------ class Q_DECL_HIDDEN KDbGuiItem::Private { public: Private() {} bool dummy; }; KDbGuiItem::KDbGuiItem() : d(new Private) { } KDbGuiItem::~KDbGuiItem() { delete d; } //------------------------------------------------ class Q_DECL_HIDDEN KDbMessageHandler::Private { public: Private() - : messageRedirection(0) + : messageRedirection(nullptr) , enableMessages(true) { } QPointer messageHandlerParentWidget; KDbMessageHandler *messageRedirection; bool enableMessages; private: Q_DISABLE_COPY(Private) }; //------------------------------------------------ KDbMessageHandler::KDbMessageHandler(QWidget *parent) : d(new Private) { d->messageHandlerParentWidget = parent; } KDbMessageHandler::~KDbMessageHandler() { delete d; } bool KDbMessageHandler::messagesEnabled() const { return d->enableMessages; } void KDbMessageHandler::setMessagesEnabled(bool enable) { d->enableMessages = enable; } KDbMessageHandler::ButtonCode KDbMessageHandler::askQuestion( KDbMessageHandler::QuestionType messageType, const QString& message, const QString &caption, KDbMessageHandler::ButtonCode defaultResult, const KDbGuiItem &buttonYes, const KDbGuiItem &buttonNo, const QString &dontShowAskAgainName, KDbMessageHandler::Options options, KDbMessageHandler* msgHandler) { if (d->enableMessages && d->messageRedirection) { return d->messageRedirection->askQuestion(messageType, message, caption, defaultResult, buttonYes, buttonNo, dontShowAskAgainName, options, msgHandler); } return defaultResult; } KDbMessageHandler* KDbMessageHandler::redirection() { return d->messageRedirection; } const KDbMessageHandler* KDbMessageHandler::redirection() const { return d->messageRedirection; } void KDbMessageHandler::setRedirection(KDbMessageHandler *otherHandler) { d->messageRedirection = otherHandler; } QWidget* KDbMessageHandler::parentWidget() { return d->messageHandlerParentWidget; } diff --git a/src/KDbMessageHandler.h b/src/KDbMessageHandler.h index ed252e54..22598afc 100644 --- a/src/KDbMessageHandler.h +++ b/src/KDbMessageHandler.h @@ -1,249 +1,249 @@ /* This file is part of the KDE project Copyright (C) 2004-2015 Jarosław Staniek Contains parts of kmessagebox.h Copyright (C) 1999 Waldo Bastian (bastian@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MSGHANDLER_H #define KDB_MSGHANDLER_H #include #include #include #include #include "kdb_export.h" class KDbResult; class KDbResultable; class KDbMessageHandler; //! A guard class for transmitting messages based on KDbResult /*! It's intended use is for top-level public methods in applications that have to display messages. Create it's instance on stack; at the end of the block, on KDbMessageGuard's destruction result will be checked. If it's not empty, error is passed to the associated message handler. The example below emits error message if result is not empty before . @code class MyClass : ... public KDbResultable { [..] MyClass(KDbMessageHandler *handler) { setMessageHandler(handler); // need ... } bool connectToProject() { KDbMessageGuard mg(this); // MyClass is KDbResultable so this easy notation is possible if (... something failed ...) { m_result = KDbResult(tr("Operation failed.")); return false; // ~KDbMessageGuard called here, m_result is passed to messageHandler() } // ... return true; // ~KDbMessageGuard called here is a no-op because there's no error in m_result } }; @endcode There are two equivalent variants of usage: - using the KDbResultable object as in the example above (recommended) - using a reference to a KDbResult and a KDbMessageHandler @note instantiating KDbMessageGuard objects on the heap makes not much sense. */ class KDB_EXPORT KDbMessageGuard { public: //! Builds a guard in the current code block using @a resultable //! Infromation from @a resultable will be used in ~KDbMessageGuard() to pass message //! to the resultable->messageHandler() handler if the handler is present //! and resultable->result().isError() == true. //! @note @a resultable is required explicit KDbMessageGuard(KDbResultable *resultable); //! Builds a guard in the current code block using a reference to @a result and @a handler //! These will be used in ~KDbMessageGuard() is result.isError() == true. //! @note @a handler is required KDbMessageGuard(const KDbResult &result, KDbMessageHandler *handler); ~KDbMessageGuard(); protected: Q_DISABLE_COPY(KDbMessageGuard) class Private; Private * const d; }; /*! Helper for setting temporary message title for an KDbResult object. Message title is a text prepended to error or warning messages. Use it as follows: @code KDbMessageTitleSetter ts(&m_result, tr("Terrible error occurred")); @endcode After leaving the current code block, myResultableObject's message title will be set back to the previous value. */ class KDB_EXPORT KDbMessageTitleSetter { public: explicit KDbMessageTitleSetter(KDbResult* result, const QString& message = QString()); explicit KDbMessageTitleSetter(KDbResultable* resultable, const QString& message = QString()); ~KDbMessageTitleSetter(); protected: KDbResult* m_result; QString m_prevMsgTitle; private: Q_DISABLE_COPY(KDbMessageTitleSetter) }; //! An abstract class used to specify GUI information such as button texts tooltips and icons. class KDbGuiItem : private QHash { public: KDbGuiItem(); ~KDbGuiItem(); inline KDbGuiItem& setProperty(const QByteArray& name, const QVariant& value) { insert(name, value); return *this; } void removeProperty(const QByteArray& name) { remove(name); } inline bool isEmpty() const { return QHash::isEmpty(); } inline QVariant property(const QByteArray& name, const QVariant& defaultValue = QVariant()) const { return value(name, defaultValue); } inline bool hasProperty(const QByteArray& name) const { return contains(name); } inline QList propertyNames() const { return keys(); } inline void clear() { QHash::clear(); } private: class Private; Private * const d; Q_DISABLE_COPY(KDbGuiItem) }; /*! A prototype for Message Handler usable for reacting on messages sent by KDbObject object(s). */ class KDB_EXPORT KDbMessageHandler { public: //! Message types enum MessageType { Information = 1, Error = 2, Warning = 3, Sorry = 4, Fatal = 5 }; //! Question types enum QuestionType { QuestionYesNo = 1, QuestionYesNoCancel = 2, WarningYesNo = 3, WarningContinueCancel = 4, WarningYesNoCancel = 5 }; //! Button codes enum ButtonCode { Ok = 1, Cancel = 2, Yes = Ok, No = 3, Continue = 4 }; //! Message options enum Option { Notify = 1, ///< Emit a KNotify event AllowLink = 2, ///< The message may contain links. Dangerous = 4 ///< The action to be confirmed by the dialog is a potentially destructive one }; Q_DECLARE_FLAGS(Options, Option) /*! Constructs message handler, @a parent is a widget that will be a parent for displaying gui elements (e.g. message boxes). Can be 0 for non-gui usage. */ - explicit KDbMessageHandler(QWidget *parent = 0); + explicit KDbMessageHandler(QWidget *parent = nullptr); virtual ~KDbMessageHandler(); /*! @return true if the handler is enables so messages are not blocked. @see setEnabled(bool) */ bool messagesEnabled() const; /*! Enables or disabled the handler to block/unblock its messages. Sometimes both lower- and higher-level messages are received, what is not optimal as only one of them should be displayed (e.g. a higher level with details). This can be solved by calling setEnabled(false) shortly before an action that can send the unwanted message. Afterwards messages can be enabled again by calling setEnabled(true). By default messages are enabled. */ void setMessagesEnabled(bool enable); /*! Shows error message with @a title (it is not caption) and details. */ virtual void showErrorMessage( KDbMessageHandler::MessageType messageType, const QString &message, const QString &details = QString(), const QString &caption = QString() ) = 0; /*! Shows error message with @a message text. Existing error message from @a obj object is also copied, if present. */ virtual void showErrorMessage( const KDbResult& result, KDbMessageHandler::MessageType messageType = Error, const QString& message = QString(), const QString& caption = QString() ) = 0; /*! Interactively asks a question. For GUI version, message boxes are used. @a defaultResult is returned in case when no message handler is installed. @a message should contain translated string. Value of ButtonCode is returned. Reimplement this. This implementation does nothing, just returns @a defaultResult. */ virtual KDbMessageHandler::ButtonCode askQuestion( KDbMessageHandler::QuestionType messageType, const QString& message, const QString &caption = QString(), KDbMessageHandler::ButtonCode defaultResult = KDbMessageHandler::Yes, const KDbGuiItem &buttonYes = KDbGuiItem(), const KDbGuiItem &buttonNo = KDbGuiItem(), const QString &dontShowAskAgainName = QString(), - KDbMessageHandler::Options options = 0, - KDbMessageHandler* msgHandler = 0); + KDbMessageHandler::Options options = nullptr, + KDbMessageHandler* msgHandler = nullptr); //! @return message redirection for this handler or 0 if there is no redirection. KDbMessageHandler* redirection(); //! @overload KDbMessageHandler* redirection() const KDbMessageHandler* redirection() const; /*! Sets redirection of all messages for this handler to @a otherHandler. Passing 0 removes redirection. Setting new redirection replaces previous. */ void setRedirection(KDbMessageHandler *otherHandler); protected: /*! @return a widget that will be parent for displaying gui elements (e.g. message boxes). Can be 0 for non-gui cases. */ QWidget *parentWidget(); class Private; Private * const d; private: Q_DISABLE_COPY(KDbMessageHandler) }; #endif diff --git a/src/KDbNativeStatementBuilder.cpp b/src/KDbNativeStatementBuilder.cpp index 49d940af..9f0c8b7c 100644 --- a/src/KDbNativeStatementBuilder.cpp +++ b/src/KDbNativeStatementBuilder.cpp @@ -1,505 +1,505 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbNativeStatementBuilder.h" #include "KDbConnection.h" #include "kdb_debug.h" #include "KDbDriverBehavior.h" #include "KDbExpression.h" #include "KDbLookupFieldSchema.h" #include "KDbOrderByColumn.h" #include "KDbQueryAsterisk.h" #include "KDbQuerySchema.h" #include "KDbQuerySchemaParameter.h" #include "KDbRelationship.h" KDbSelectStatementOptions::KDbSelectStatementOptions() : alsoRetrieveRecordId(false) , addVisibleLookupColumns(true) { } //================================================ class Q_DECL_HIDDEN KDbNativeStatementBuilder::Private { public: Private() {} //! @todo use equivalent of QPointer KDbConnection *connection; inline KDbDriver *driver() { return connection ? connection->driver() : nullptr; } private: Q_DISABLE_COPY(Private) }; //================================================ KDbNativeStatementBuilder::KDbNativeStatementBuilder(KDbConnection *connection) : d(new Private) { d->connection = connection; } KDbNativeStatementBuilder::~KDbNativeStatementBuilder() { delete d; } static bool selectStatementInternal(KDbEscapedString *target, KDbConnection *connection, KDbQuerySchema* querySchema, const KDbSelectStatementOptions& options, const QList& parameters) { Q_ASSERT(target); Q_ASSERT(querySchema); //"SELECT FROM ..." is theoretically allowed " //if (querySchema.fieldCount()<1) // return QString(); // Each SQL identifier needs to be escaped in the generated query. - const KDbDriver *driver = connection ? connection->driver() : 0; + const KDbDriver *driver = connection ? connection->driver() : nullptr; if (!querySchema->statement().isEmpty()) { //! @todo replace with KDbNativeQuerySchema? It shouldn't be here. *target = querySchema->statement(); return true; } //! @todo looking at singleTable is visually nice but a field name can conflict //! with function or variable name... int number = 0; bool singleTable = querySchema->tables()->count() <= 1; if (singleTable) { //make sure we will have single table: foreach(KDbField *f, *querySchema->fields()) { if (querySchema->isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema(*f)) { //uups, no, there's at least one left join singleTable = false; break; } number++; } } KDbEscapedString sql; //final sql string sql.reserve(4096); KDbEscapedString s_additional_joins; //additional joins needed for lookup fields KDbEscapedString s_additional_fields; //additional fields to append to the fields list int internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases int internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases number = 0; QList subqueries_for_lookup_data; // subqueries will be added to FROM section KDbEscapedString kdb_subquery_prefix("__kdb_subquery_"); foreach(KDbField *f, *querySchema->fields()) { if (querySchema->isColumnVisible(number)) { if (!sql.isEmpty()) sql += ", "; if (f->isQueryAsterisk()) { if (!singleTable && static_cast(f)->isSingleTableAsterisk()) { //single-table * sql.append(KDb::escapeIdentifier(driver, f->table()->name())).append(".*"); } else { //all-tables * (or simplified table.* when there's only one table) sql += '*'; } } else { if (f->isExpression()) { sql += f->expression().toString(driver); } else { if (!f->table()) {//sanity check return false; } QString tableName; int tablePosition = querySchema->tableBoundToColumn(number); if (tablePosition >= 0) { tableName = KDb::iifNotEmpty(querySchema->tableAlias(tablePosition), f->table()->name()); } if (options.addVisibleLookupColumns) { // try to find table/alias name harder if (tableName.isEmpty()) { tableName = querySchema->tableAlias(f->table()->name()); } if (tableName.isEmpty()) { tableName = f->table()->name(); } } if (!singleTable && !tableName.isEmpty()) { sql.append(KDb::escapeIdentifier(driver, tableName)).append('.'); } sql += KDb::escapeIdentifier(driver, f->name()); } const QString aliasString(querySchema->columnAlias(number)); if (!aliasString.isEmpty()) { sql.append(" AS ").append(aliasString); } //! @todo add option that allows to omit "AS" keyword } KDbLookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns && f->table()) - ? f->table()->lookupFieldSchema(*f) : 0; + ? f->table()->lookupFieldSchema(*f) : nullptr; if (lookupFieldSchema && lookupFieldSchema->boundColumn() >= 0) { // Lookup field schema found // Now we also need to fetch "visible" value from the lookup table, not only the value of binding. // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken) // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField" KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Table) { KDbTableSchema *lookupTable = querySchema->connection()->tableSchema(recordSource.name()); - KDbFieldList* visibleColumns = 0; - KDbField *boundField = 0; + KDbFieldList* visibleColumns = nullptr; + KDbField *boundField = nullptr; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns())) && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) { //add LEFT OUTER JOIN if (!s_additional_joins.isEmpty()) s_additional_joins += ' '; const QString internalUniqueTableAlias( QLatin1String("__kdb_") + lookupTable->name() + QLatin1Char('_') + QString::number(internalUniqueTableAliasNumber++)); s_additional_joins += KDbEscapedString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6") .arg(KDb::escapeIdentifier(driver, lookupTable->name())) .arg(internalUniqueTableAlias) .arg(KDb::escapeIdentifier(driver, querySchema->tableAliasOrName(f->table()->name()))) .arg(KDb::escapeIdentifier(driver, f->name())) .arg(internalUniqueTableAlias) .arg(KDb::escapeIdentifier(driver, boundField->name())); //add visibleField to the list of SELECTed fields //if it is not yet present there if (!s_additional_fields.isEmpty()) s_additional_fields += ", "; //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" //! @todo Add possibility for joining the values at client side. s_additional_fields += visibleColumns->sqlFieldsList( connection, QLatin1String(" || ' ' || "), internalUniqueTableAlias, driver ? KDb::DriverEscaping : KDb::KDbEscaping); } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Query) { KDbQuerySchema *lookupQuery = querySchema->connection()->querySchema(recordSource.name()); if (!lookupQuery) { kdbWarning() << "!lookupQuery"; return false; } const KDbQueryColumnInfo::Vector fieldsExpanded(lookupQuery->fieldsExpanded()); if (lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) { kdbWarning() << "lookupFieldSchema->boundColumn() >= fieldsExpanded.count()"; return false; } KDbQueryColumnInfo *boundColumnInfo = fieldsExpanded.at(lookupFieldSchema->boundColumn()); if (!boundColumnInfo) { kdbWarning() << "!boundColumnInfo"; return false; } KDbField *boundField = boundColumnInfo->field(); if (!boundField) { kdbWarning() << "!boundField"; return false; } //add LEFT OUTER JOIN if (!s_additional_joins.isEmpty()) s_additional_joins += ' '; KDbEscapedString internalUniqueQueryAlias = kdb_subquery_prefix + connection->escapeString(lookupQuery->name()) + '_' + QString::number(internalUniqueQueryAliasNumber++); KDbNativeStatementBuilder builder(connection); KDbEscapedString subSql; if (!builder.generateSelectStatement(&subSql, lookupQuery, options, parameters)) { return false; } s_additional_joins += KDbEscapedString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6") .arg(subSql) .arg(internalUniqueQueryAlias) .arg(KDb::escapeIdentifier(driver, f->table()->name())) .arg(KDb::escapeIdentifier(driver, f->name())) .arg(internalUniqueQueryAlias) .arg(KDb::escapeIdentifier(driver, boundColumnInfo->aliasOrName())); if (!s_additional_fields.isEmpty()) s_additional_fields += ", "; const QList visibleColumns(lookupFieldSchema->visibleColumns()); KDbEscapedString expression; foreach(int visibleColumnIndex, visibleColumns) { //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" //! @todo Add possibility for joining the values at client side. if (fieldsExpanded.count() <= visibleColumnIndex) { kdbWarning() << "fieldsExpanded.count() <= (*visibleColumnsIt) : " << fieldsExpanded.count() << " <= " << visibleColumnIndex; return false; } if (!expression.isEmpty()) expression += " || ' ' || "; expression += ( internalUniqueQueryAlias + '.' + KDb::escapeIdentifier(driver, fieldsExpanded.value(visibleColumnIndex)->aliasOrName()) ); } s_additional_fields += expression; } else { kdbWarning() << "unsupported record source type" << recordSource.typeName(); return false; } } } number++; } //add lookup fields if (!s_additional_fields.isEmpty()) sql += (", " + s_additional_fields); if (driver && options.alsoRetrieveRecordId) { //append rowid column KDbEscapedString s; if (!sql.isEmpty()) s = ", "; if (querySchema->masterTable()) s += KDbEscapedString(querySchema->tableAliasOrName(querySchema->masterTable()->name())) + '.'; s += driver->behavior()->ROW_ID_FIELD_NAME; sql += s; } sql.prepend("SELECT "); QList* tables = querySchema->tables(); if ((tables && !tables->isEmpty()) || !subqueries_for_lookup_data.isEmpty()) { sql += " FROM "; KDbEscapedString s_from; if (tables) { number = 0; foreach(KDbTableSchema *table, *tables) { if (!s_from.isEmpty()) s_from += ", "; s_from += KDb::escapeIdentifier(driver, table->name()); const QString aliasString(querySchema->tableAlias(number)); if (!aliasString.isEmpty()) s_from.append(" AS ").append(aliasString); number++; } } // add subqueries for lookup data int subqueries_for_lookup_data_counter = 0; foreach(KDbQuerySchema* subQuery, subqueries_for_lookup_data) { if (!s_from.isEmpty()) s_from += ", "; KDbEscapedString subSql; if (!selectStatementInternal(&subSql, connection, subQuery, options, parameters)) { return false; } s_from += '(' + subSql + ") AS " + kdb_subquery_prefix + KDbEscapedString::number(subqueries_for_lookup_data_counter++); } sql += s_from; } KDbEscapedString s_where; s_where.reserve(4096); //JOINS if (!s_additional_joins.isEmpty()) { sql += ' ' + s_additional_joins + ' '; } //! @todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later //WHERE bool wasWhere = false; //for later use foreach(KDbRelationship *rel, *querySchema->relationships()) { if (s_where.isEmpty()) { wasWhere = true; } else s_where += " AND "; KDbEscapedString s_where_sub; foreach(const KDbField::Pair &pair, *rel->fieldPairs()) { if (!s_where_sub.isEmpty()) s_where_sub += " AND "; s_where_sub += KDbEscapedString(KDb::escapeIdentifier(driver, pair.first->table()->name())) + '.' + KDb::escapeIdentifier(driver, pair.first->name()) + " = " + KDb::escapeIdentifier(driver, pair.second->table()->name()) + '.' + KDb::escapeIdentifier(driver, pair.second->name()); } if (rel->fieldPairs()->count() > 1) { s_where_sub.prepend('('); s_where_sub += ')'; } s_where += s_where_sub; } //EXPLICITLY SPECIFIED WHERE EXPRESSION if (!querySchema->whereExpression().isNull()) { KDbQuerySchemaParameterValueListIterator paramValuesIt(parameters); - KDbQuerySchemaParameterValueListIterator *paramValuesItPtr = parameters.isEmpty() ? 0 : ¶mValuesIt; + KDbQuerySchemaParameterValueListIterator *paramValuesItPtr = parameters.isEmpty() ? nullptr : ¶mValuesIt; if (wasWhere) { //! @todo () are not always needed s_where = '(' + s_where + ") AND (" + querySchema->whereExpression().toString(driver, paramValuesItPtr) + ')'; } else { s_where = querySchema->whereExpression().toString(driver, paramValuesItPtr); } } if (!s_where.isEmpty()) sql += " WHERE " + s_where; //! @todo (js) add other sql parts //(use wasWhere here) // ORDER BY KDbEscapedString orderByString( querySchema->orderByColumnList()->toSQLString( !singleTable/*includeTableName*/, connection, driver ? KDb::DriverEscaping : KDb::KDbEscaping) ); const QVector pkeyFieldsOrder(querySchema->pkeyFieldsOrder()); if (orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) { //add automatic ORDER BY if there is no explicitly defined (especially helps when there are complex JOINs) KDbOrderByColumnList automaticPKOrderBy; const KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded()); foreach(int pkeyFieldsIndex, pkeyFieldsOrder) { if (pkeyFieldsIndex < 0) // no field mentioned in this query continue; if (pkeyFieldsIndex >= fieldsExpanded.count()) { kdbWarning() << "ORDER BY: (*it) >= fieldsExpanded.count() - " << pkeyFieldsIndex << " >= " << fieldsExpanded.count(); continue; } KDbQueryColumnInfo *ci = fieldsExpanded[ pkeyFieldsIndex ]; automaticPKOrderBy.appendColumn(ci); } orderByString = automaticPKOrderBy.toSQLString(!singleTable/*includeTableName*/, connection, driver ? KDb::DriverEscaping : KDb::KDbEscaping); } if (!orderByString.isEmpty()) sql += (" ORDER BY " + orderByString); //kdbDebug() << sql; *target = sql; return true; } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbQuerySchema* querySchema, const KDbSelectStatementOptions& options, const QList& parameters) const { return selectStatementInternal(target, d->connection, querySchema, options, parameters); } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbQuerySchema* querySchema, const QList& parameters) const { return selectStatementInternal(target, d->connection, querySchema, KDbSelectStatementOptions(), parameters); } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbTableSchema* tableSchema, const KDbSelectStatementOptions& options) const { return generateSelectStatement(target, tableSchema->query(), options); } bool KDbNativeStatementBuilder::generateCreateTableStatement(KDbEscapedString *target, const KDbTableSchema& tableSchema) const { Q_ASSERT(target); // Each SQL identifier needs to be escaped in the generated query. const KDbDriver *driver = d->connection ? d->connection->driver() : nullptr; KDbEscapedString sql; sql.reserve(4096); sql = KDbEscapedString("CREATE TABLE ") + KDb::escapeIdentifier(driver, tableSchema.name()) + " ("; bool first = true; foreach(KDbField *field, tableSchema.m_fields) { if (first) first = false; else sql += ", "; KDbEscapedString v = KDbEscapedString(KDb::escapeIdentifier(driver, field->name())) + ' '; const bool autoinc = field->isAutoIncrement(); const bool pk = field->isPrimaryKey() || (autoinc && driver && driver->beh->AUTO_INCREMENT_REQUIRES_PK); //! @todo warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true! const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive if (autoinc && d->driver()->beh->SPECIAL_AUTO_INCREMENT_DEF) { if (pk) v.append(d->driver()->beh->AUTO_INCREMENT_TYPE).append(' ') .append(d->driver()->beh->AUTO_INCREMENT_PK_FIELD_OPTION); else v.append(d->driver()->beh->AUTO_INCREMENT_TYPE).append(' ') .append(d->driver()->beh->AUTO_INCREMENT_FIELD_OPTION); } else { if (autoinc && !d->driver()->beh->AUTO_INCREMENT_TYPE.isEmpty()) v += d->driver()->beh->AUTO_INCREMENT_TYPE; else v += d->driver()->sqlTypeName(type, *field); if (KDbField::isIntegerType(type) && field->isUnsigned()) { v.append(' ').append(d->driver()->beh->UNSIGNED_TYPE_KEYWORD); } if (KDbField::isFPNumericType(type) && field->precision() > 0) { if (field->scale() > 0) v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale()); else v += QString::fromLatin1("(%1)").arg(field->precision()); } else if (type == KDbField::Text) { int realMaxLen; if (d->driver()->beh->TEXT_TYPE_MAX_LENGTH == 0) { realMaxLen = field->maxLength(); // allow to skip (N) } else { // max length specified by driver if (field->maxLength() == 0) { // as long as possible realMaxLen = d->driver()->beh->TEXT_TYPE_MAX_LENGTH; } else { // not longer than specified by driver realMaxLen = qMin(d->driver()->beh->TEXT_TYPE_MAX_LENGTH, field->maxLength()); } } if (realMaxLen > 0) { v += QString::fromLatin1("(%1)").arg(realMaxLen); } } if (autoinc) { v.append(' ').append(pk ? d->driver()->beh->AUTO_INCREMENT_PK_FIELD_OPTION : d->driver()->beh->AUTO_INCREMENT_FIELD_OPTION); } else { //! @todo here is automatically a single-field key created if (pk) v += " PRIMARY KEY"; } if (!pk && field->isUniqueKey()) v += " UNIQUE"; ///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull()) if (!autoinc && !pk && field->isNotNull()) v += " NOT NULL"; //only add not null option if no autocommit is set if (d->driver()->supportsDefaultValue(*field) && field->defaultValue().isValid()) { KDbEscapedString valToSQL(d->driver()->valueToSQL(field, field->defaultValue())); if (!valToSQL.isEmpty()) //for sanity v += " DEFAULT " + valToSQL; } } sql += v; } sql += ')'; *target = sql; return true; } diff --git a/src/KDbOrderByColumn.cpp b/src/KDbOrderByColumn.cpp index 24595604..a0929fd9 100644 --- a/src/KDbOrderByColumn.cpp +++ b/src/KDbOrderByColumn.cpp @@ -1,375 +1,375 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbOrderByColumn.h" #include "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbConnection.h" #include "kdb_debug.h" class Q_DECL_HIDDEN KDbOrderByColumn::Private { public: Private() : column(nullptr) , pos(-1) , field(nullptr) , order(KDbOrderByColumn::SortOrder::Ascending) { } Private(const Private &other) { copy(other); } #define KDbOrderByColumnPrivateArgs(o) std::tie(o.column, o.pos, o.field, o.order) Private(KDbQueryColumnInfo* aColumn, int aPos, KDbField* aField, KDbOrderByColumn::SortOrder aOrder) { KDbOrderByColumnPrivateArgs((*this)) = std::tie(aColumn, aPos, aField, aOrder); } void copy(const Private &other) { KDbOrderByColumnPrivateArgs((*this)) = KDbOrderByColumnPrivateArgs(other); } bool operator==(const Private &other) const { return KDbOrderByColumnPrivateArgs((*this)) == KDbOrderByColumnPrivateArgs(other); } //! Column to sort, @c nullptr if field is non-0. KDbQueryColumnInfo* column; //! A helper for d->column that allows to know that sorting column //! was defined by providing its position. -1 by default. //! e.g. SELECT a, b FROM T ORDER BY 2 int pos; //! Used only in case when the second contructor is used. KDbField* field; //! Sort order KDbOrderByColumn::SortOrder order; }; //---- KDbOrderByColumn::KDbOrderByColumn() : d(new Private) { } KDbOrderByColumn::KDbOrderByColumn(KDbQueryColumnInfo* column, SortOrder order, int pos) : d(new Private(column, pos, nullptr, order)) { Q_ASSERT(column); } KDbOrderByColumn::KDbOrderByColumn(KDbField* field, SortOrder order) : d(new Private(nullptr, -1, field, order)) { Q_ASSERT(field); } KDbOrderByColumn::KDbOrderByColumn(const KDbOrderByColumn &other) : d(new Private(*other.d)) { } KDbOrderByColumn::~KDbOrderByColumn() { delete d; } KDbOrderByColumn* KDbOrderByColumn::copy(KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) const { if (d->field) { return new KDbOrderByColumn(d->field, d->order); } if (d->column) { KDbQueryColumnInfo* columnInfo; if (fromQuery && toQuery) { int columnIndex = fromQuery->columnsOrder().value(d->column); if (columnIndex < 0) { kdbWarning() << "Index not found for column" << *d->column; return nullptr; } columnInfo = toQuery->expandedOrInternalField(columnIndex); if (!columnInfo) { kdbWarning() << "Column info not found at index" << columnIndex << "in toQuery"; return nullptr; } } else { columnInfo = d->column; } return new KDbOrderByColumn(columnInfo, d->order, d->pos); } Q_ASSERT(d->field || d->column); - return 0; + return nullptr; } KDbQueryColumnInfo* KDbOrderByColumn::column() const { return d->column; } int KDbOrderByColumn::position() const { return d->pos; } KDbField* KDbOrderByColumn::field() const { return d->field; } KDbOrderByColumn::SortOrder KDbOrderByColumn::sortOrder() const { return d->order; } KDbOrderByColumn& KDbOrderByColumn::operator=(const KDbOrderByColumn &other) { if (this != &other) { *d = *other.d; } return *this; } bool KDbOrderByColumn::operator==(const KDbOrderByColumn& col) const { return *d == *col.d; } QDebug operator<<(QDebug dbg, const KDbOrderByColumn& order) { const QLatin1String orderString( order.sortOrder() == KDbOrderByColumn::SortOrder::Ascending ? "ASCENDING" : "DESCENDING"); if (order.column()) { if (order.position() > -1) { dbg.nospace() << QString::fromLatin1("COLUMN_AT_POSITION_%1(").arg(order.position() + 1); dbg.space() << *order.column() << ','; dbg.space() << orderString << ')'; return dbg.space(); } else { dbg.nospace() << "COLUMN(" << *order.column() << ','; dbg.space() << orderString << ')'; return dbg.space(); } } if (order.field()) { dbg.nospace() << "FIELD(" << *order.field() << ','; dbg.space() << orderString << ')'; return dbg.space(); } dbg.nospace() << "NONE"; return dbg.space(); } KDbEscapedString KDbOrderByColumn::toSQLString(bool includeTableName, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) const { const QByteArray orderString(d->order == KDbOrderByColumn::SortOrder::Ascending ? "" : " DESC"); KDbEscapedString fieldName, tableName, collationString; if (d->column) { if (d->pos > -1) return KDbEscapedString::number(d->pos + 1) + orderString; else { if (includeTableName && d->column->alias().isEmpty()) { tableName = KDbEscapedString(escapeIdentifier(d->column->field()->table()->name(), conn, escapingType)); tableName += '.'; } fieldName = KDbEscapedString(escapeIdentifier(d->column->aliasOrName(), conn, escapingType)); } if (d->column->field()->isTextType()) { collationString = conn->driver()->collationSQL(); } } else { if (d->field && includeTableName) { tableName = KDbEscapedString(escapeIdentifier(d->field->table()->name(), conn, escapingType)); tableName += '.'; } fieldName = KDbEscapedString(escapeIdentifier( d->field ? d->field->name() : QLatin1String("??")/*error*/, conn, escapingType)); if (d->field && d->field->isTextType()) { collationString = conn->driver()->collationSQL(); } } return tableName + fieldName + collationString + orderString; } //======================================= KDbOrderByColumnList::KDbOrderByColumnList() : QList() { } KDbOrderByColumnList::KDbOrderByColumnList(const KDbOrderByColumnList& other, KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) : QList() { for (QList::ConstIterator it(other.constBegin()); it != other.constEnd(); ++it) { KDbOrderByColumn* order = (*it)->copy(fromQuery, toQuery); if (order) { append(order); } } } bool KDbOrderByColumnList::appendFields(KDbQuerySchema* querySchema, const QString& field1, KDbOrderByColumn::SortOrder order1, const QString& field2, KDbOrderByColumn::SortOrder order2, const QString& field3, KDbOrderByColumn::SortOrder order3, const QString& field4, KDbOrderByColumn::SortOrder order4, const QString& field5, KDbOrderByColumn::SortOrder order5) { Q_ASSERT(querySchema); int numAdded = 0; #define ADD_COL(fieldName, order) \ if (ok && !fieldName.isEmpty()) { \ if (!appendField( querySchema, fieldName, order )) \ ok = false; \ else \ numAdded++; \ } bool ok = true; ADD_COL(field1, order1) ADD_COL(field2, order2) ADD_COL(field3, order3) ADD_COL(field4, order4) ADD_COL(field5, order5) #undef ADD_COL if (ok) { return true; } for (int i = 0; i < numAdded; i++) { removeLast(); } return false; } KDbOrderByColumnList::~KDbOrderByColumnList() { qDeleteAll(begin(), end()); } void KDbOrderByColumnList::appendColumn(KDbQueryColumnInfo* columnInfo, KDbOrderByColumn::SortOrder order) { Q_ASSERT(columnInfo); append(new KDbOrderByColumn(columnInfo, order)); } bool KDbOrderByColumnList::appendColumn(KDbQuerySchema* querySchema, KDbOrderByColumn::SortOrder order, int pos) { Q_ASSERT(querySchema); KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded()); if (pos < 0 || pos >= fieldsExpanded.size()) { return false; } KDbQueryColumnInfo* ci = fieldsExpanded[pos]; append(new KDbOrderByColumn(ci, order, pos)); return true; } void KDbOrderByColumnList::appendField(KDbField* field, KDbOrderByColumn::SortOrder order) { Q_ASSERT(field); append(new KDbOrderByColumn(field, order)); } bool KDbOrderByColumnList::appendField(KDbQuerySchema* querySchema, const QString& fieldName, KDbOrderByColumn::SortOrder order) { Q_ASSERT(querySchema); KDbQueryColumnInfo *columnInfo = querySchema->columnInfo(fieldName); if (columnInfo) { append(new KDbOrderByColumn(columnInfo, order)); return true; } KDbField *field = querySchema->findTableField(fieldName); if (field) { append(new KDbOrderByColumn(field, order)); return true; } kdbWarning() << "no such field" << fieldName; return false; } bool KDbOrderByColumnList::isEmpty() const { return QList::isEmpty(); } int KDbOrderByColumnList::count() const { return QList::count(); } KDbOrderByColumnList::iterator KDbOrderByColumnList::begin() { return QList::begin(); } KDbOrderByColumnList::iterator KDbOrderByColumnList::end() { return QList::end(); } KDbOrderByColumnList::const_iterator KDbOrderByColumnList::constBegin() const { return QList::constBegin(); } KDbOrderByColumnList::const_iterator KDbOrderByColumnList::constEnd() const { return QList::constEnd(); } QDebug operator<<(QDebug dbg, const KDbOrderByColumnList& list) { if (list.isEmpty()) { dbg.nospace() << "NONE"; return dbg.space(); } bool first = true; for (QList::ConstIterator it(list.constBegin()); it != list.constEnd(); ++it) { if (first) first = false; else dbg.nospace() << '\n'; dbg.nospace() << *(*it); } return dbg.space(); } KDbEscapedString KDbOrderByColumnList::toSQLString(bool includeTableNames, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) const { KDbEscapedString string; for (QList::ConstIterator it(constBegin()); it != constEnd(); ++it) { if (!string.isEmpty()) string += ", "; string += (*it)->toSQLString(includeTableNames, conn, escapingType); } return string; } void KDbOrderByColumnList::clear() { qDeleteAll(begin(), end()); QList::clear(); } diff --git a/src/KDbPreparedStatement.cpp b/src/KDbPreparedStatement.cpp index 55671a1d..7824fc01 100644 --- a/src/KDbPreparedStatement.cpp +++ b/src/KDbPreparedStatement.cpp @@ -1,232 +1,232 @@ /* This file is part of the KDE project Copyright (C) 2005-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbPreparedStatement.h" #include "KDbPreparedStatementInterface.h" #include "KDbSqlResult.h" #include "KDbTableSchema.h" #include "kdb_debug.h" KDbPreparedStatement::Data::Data() : Data(InvalidStatement, nullptr, nullptr, QStringList()) { } KDbPreparedStatement::Data::Data(Type _type, KDbPreparedStatementInterface* _iface, KDbFieldList* _fields, const QStringList& _whereFieldNames) : type(_type), fields(_fields), whereFieldNames(_whereFieldNames) - , fieldsForParameters(0), whereFields(0), dirty(true), iface(_iface) + , fieldsForParameters(nullptr), whereFields(nullptr), dirty(true), iface(_iface) , lastInsertRecordId(std::numeric_limits::max()) { } KDbPreparedStatement::Data::~Data() { delete iface; delete whereFields; } KDbPreparedStatement::KDbPreparedStatement() : d( new Data() ) { } KDbPreparedStatement::KDbPreparedStatement(KDbPreparedStatementInterface* iface, Type type, KDbFieldList* fields, const QStringList& whereFieldNames) : d( new Data(type, iface, fields, whereFieldNames) ) { } KDbPreparedStatement::~KDbPreparedStatement() { } bool KDbPreparedStatement::execute(const KDbPreparedStatementParameters& parameters) { if (d->dirty) { KDbEscapedString s; if (!generateStatementString(&s)) { // sets d->fieldsForParameters too m_result.setCode(ERR_OTHER); return false; } //! @todo error message? if (s.isEmpty()) { m_result.setCode(ERR_OTHER); return false; } if (!d->iface->prepare(s)) { m_result.setCode(ERR_OTHER); return false; } d->dirty = false; } bool resultOwned = true; QScopedPointer result(d->iface->execute(d->type, *d->fieldsForParameters, d->fields, parameters, &resultOwned)); if (!result) { return false; } d->lastInsertRecordId = result->lastInsertRecordId(); if (resultOwned) { result.take(); } return true; } bool KDbPreparedStatement::generateStatementString(KDbEscapedString * s) { s->reserve(1024); switch (d->type) { case SelectStatement: return generateSelectStatementString(s); case InsertStatement: return generateInsertStatementString(s); default:; } kdbCritical() << "Unsupported type" << d->type; return false; } bool KDbPreparedStatement::generateSelectStatementString(KDbEscapedString * s) { //! @todo only tables and trivial queries supported for select... *s = "SELECT "; bool first = true; foreach(KDbField *f, *d->fields->fields()) { if (first) first = false; else s->append(", "); s->append(f->name()); } // create WHERE first = true; delete d->whereFields; d->whereFields = new KDbField::List(); foreach(const QString& whereItem, d->whereFieldNames) { if (first) { s->append(" WHERE "); first = false; } else s->append(" AND "); KDbField *f = d->fields->field(whereItem); if (!f) { kdbWarning() << "field" << whereItem << "not found, aborting"; s->clear(); return false; } d->whereFields->append(f); s->append(whereItem.toUtf8() + "=?"); } d->fieldsForParameters = d->whereFields; return true; } bool KDbPreparedStatement::generateInsertStatementString(KDbEscapedString * s) { //! @todo only tables supported for insert; what about views? - KDbTableSchema *table = d->fields->isEmpty() ? 0 : d->fields->field(0)->table(); + KDbTableSchema *table = d->fields->isEmpty() ? nullptr : d->fields->field(0)->table(); if (!table) return false; //err KDbEscapedString namesList; bool first = true; //we are using a selection of fields only const bool allTableFieldsUsed = dynamic_cast(d->fields); foreach(const KDbField* f, *d->fields->fields()) { if (first) { s->append("?"); if (!allTableFieldsUsed) namesList = KDbEscapedString(f->name()); first = false; } else { s->append(",?"); if (!allTableFieldsUsed) { namesList.append(", "); namesList.append(f->name()); } } } s->append(")"); s->prepend(KDbEscapedString("INSERT INTO ") + table->name() + (allTableFieldsUsed ? KDbEscapedString() : (KDbEscapedString(" (") + namesList + ')')) + " VALUES ("); d->fieldsForParameters = d->fields->fields(); return true; } bool KDbPreparedStatement::isValid() const { return d->type != InvalidStatement; } KDbPreparedStatement::Type KDbPreparedStatement::type() const { return d->type; } void KDbPreparedStatement::setType(KDbPreparedStatement::Type type) { d->type = type; d->dirty = true; } const KDbFieldList* KDbPreparedStatement::fields() const { return d->fields; } void KDbPreparedStatement::setFields(KDbFieldList* fields) { Q_ASSERT(fields); d->fields = fields; d->dirty = true; } QStringList KDbPreparedStatement::whereFieldNames() const { return d->whereFieldNames; } void KDbPreparedStatement::setWhereFieldNames(const QStringList& whereFieldNames) { d->whereFieldNames = whereFieldNames; d->dirty = true; } quint64 KDbPreparedStatement::lastInsertRecordId() const { return d->lastInsertRecordId; } /*bool KDbPreparedStatement::insert() { const bool res = m_conn->drv_prepareStatement(this); const bool res = m_conn->drv_insertRecord(this); clearArguments(); return res; } bool KDbPreparedStatement::select() { const bool res = m_conn->drv_bindArgumentForPreparedStatement(this, m_args.count()-1); }*/ diff --git a/src/KDbPreparedStatement.h b/src/KDbPreparedStatement.h index e146ed31..7b47e5ce 100644 --- a/src/KDbPreparedStatement.h +++ b/src/KDbPreparedStatement.h @@ -1,154 +1,154 @@ /* This file is part of the KDE project Copyright (C) 2005-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_PREPAREDSTATEMENT_H #define KDB_PREPAREDSTATEMENT_H #include #include #include #include "KDbField.h" #include "KDbResult.h" class KDbFieldList; class KDbPreparedStatementInterface; //! Prepared statement paraneters used in KDbPreparedStatement::execute() typedef QList KDbPreparedStatementParameters; /*! @short Prepared database command for optimizing sequences of multiple database actions Currently INSERT and SELECT statements are supported. For example when using KDbPreparedStatement for INSERTs, you can gain about 30% speedup compared to using multiple connection.insertRecord(*tabelSchema, dbRecordBuffer). To use KDbPreparedStatement, create is using KDbConnection:prepareStatement(), providing table schema; set up parameters using operator << ( const QVariant& value ); and call execute() when ready. KDbPreparedStatement objects are accessed using KDE shared pointers, i.e KDbPreparedStatement, so you do not need to remember about destroying them. However, when underlying KDbConnection object is destroyed, KDbPreparedStatement should not be used. Let's assume tableSchema contains two columns NUMBER integer and TEXT text. Following code inserts 10000 records with random numbers and text strings obtained elsewhere using getText(i). @code bool insertMultiple(KDbConnection* conn, KDbTableSchema* tableSchema) { KDbPreparedStatement statement = conn->prepareStatement( KDbPreparedStatement::Insert, tableSchema); for (i=0; i<10000; i++) { KDbPreparedStatementParameters parameters; parameters << qrand() << getText(i); if (!statement.execute(parameters)) return false; } return true; } @endcode If you do not call clearParameters() after every insert, you can insert the same value multiple times using execute() what increases efficiency even more. Another use case is inserting large objects (BLOBs or CLOBs). Depending on database backend, you can avoid escaping BLOBs. See KexiFormView::storeData() for example use. */ class KDB_EXPORT KDbPreparedStatement : public KDbResultable { public: //! Defines type of the prepared statement. enum Type { InvalidStatement, //!< Used only in invalid statements SelectStatement, //!< SELECT statement will be prepared end executed InsertStatement //!< INSERT statement will be prepared end executed }; //! @internal class KDB_EXPORT Data : public QSharedData { public: Data(); Data(Type _type, KDbPreparedStatementInterface* _iface, KDbFieldList* _fields, const QStringList& _whereFieldNames); ~Data(); Type type; KDbFieldList *fields; QStringList whereFieldNames; const KDbField::List* fieldsForParameters; //!< fields where we'll put the inserted parameters KDbField::List* whereFields; //!< temporary, used for select statements, based on whereFieldNames bool dirty; //!< true if the statement has to be internally //!< prepared (possible again) before calling executeInternal() KDbPreparedStatementInterface *iface; quint64 lastInsertRecordId; }; //! Creates an invalid prepared statement. KDbPreparedStatement(); - virtual ~KDbPreparedStatement(); + ~KDbPreparedStatement() override; bool isValid() const; KDbPreparedStatement::Type type() const; void setType(KDbPreparedStatement::Type type); const KDbFieldList* fields() const; void setFields(KDbFieldList* fields); QStringList whereFieldNames() const; void setWhereFieldNames(const QStringList& whereFieldNames); /*! Executes the prepared statement using @a parameters parameters. A number parameters set up for the statement must be the same as a number of fields defined in the underlying database table. @return false on failure. Detailed error status can be obtained from KDbConnection object that was used to create this statement object. */ bool execute(const KDbPreparedStatementParameters& parameters); /*! @return unique identifier of the most recently inserted record. Typically this is just primary key value. This identifier could be reused when we want to reference just inserted record. If there was no insertion recently performed, std::numeric_limits::max() is returned. */ quint64 lastInsertRecordId() const; protected: //! Creates a new prepared statement. In your code use //! Users call KDbConnection:prepareStatement() instead. KDbPreparedStatement(KDbPreparedStatementInterface* iface, Type type, KDbFieldList* fields, const QStringList& whereFieldNames = QStringList()); friend class KDbConnection; private: //! @todo is this portable across backends? bool generateStatementString(KDbEscapedString* s); bool generateSelectStatementString(KDbEscapedString * s); bool generateInsertStatementString(KDbEscapedString * s); QSharedDataPointer d; }; #endif diff --git a/src/KDbProperties.h b/src/KDbProperties.h index 4f6f1f3f..e62a0578 100644 --- a/src/KDbProperties.h +++ b/src/KDbProperties.h @@ -1,68 +1,68 @@ /* This file is part of the KDE project Copyright (C) 2005 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_DBPROPERTIES_H #define KDB_DBPROPERTIES_H #include "KDbResult.h" class KDbConnection; //! @todo implement KConfigBase interface here? //! A set of storable database properties. /*! This is a convenience class that allows to store global database properties without a need for creating and maintain custom table. KDbProperties object is accessible only using KDbConnection::databaseProperties() method. */ class KDB_EXPORT KDbProperties : public KDbResultable { Q_DECLARE_TR_FUNCTIONS(KDbProperties) public: - ~KDbProperties(); + ~KDbProperties() override; /*! Sets @a value for property @a name. Optional caption can be also set. If there's no such property defined, it will be added. Existing value will be overwritten. Note that to execute this method, database must be opened in read-write mode. @return true on successful data. KDbConnection */ bool setValue(const QString& name, const QVariant& value); /*! Sets @a caption for for property @a name. Usually it shouldn't be translated: trnaslation can be performed before displaying. */ bool setCaption(const QString& name, const QString& caption); //! @return property value for @a propeName available for this driver. //! If there's no such property defined for driver, Null QVariant value is returned. QVariant value(const QString& name); //! @return translated property caption for @a name. //! If there's no such property defined for driver, empty string value is returned. QString caption(const QString& name); //! @return a list of available property names. QStringList names(); protected: explicit KDbProperties(KDbConnection *conn); KDbConnection* m_conn; friend class KDbConnectionPrivate; }; #endif diff --git a/src/KDbQueryAsterisk.h b/src/KDbQueryAsterisk.h index 4930e50c..e0607db8 100644 --- a/src/KDbQueryAsterisk.h +++ b/src/KDbQueryAsterisk.h @@ -1,115 +1,115 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_QUERYASTERISK_H #define KDB_QUERYASTERISK_H #include "KDbField.h" class KDbQuerySchema; //! @short KDbQueryAsterisk class encapsulates information about single asterisk in query definition /*! There are two types of query asterisks: 1. "Single-table" asterisk, that references all fields of given table used in the query. Example SQL statement: @code SELECT staff.*, cars.model from staff, cars WHERE staff.car = cars.number; @endcode The "staff.*" element is our "single-table" asterisk; this tells us that we want to get all fields of table "staff". 2. "All-tables" asterisk, that references all fields of all tables used in the query. Example SQL statement: @code SELECT * from staff, cars WHERE staff.car = cars.number; @endcode The "*" is our "all-tables" asterisk; this tells us that we want to get all fields of all used tables (here: "staff" and "cars"). There can be many asterisks of 1st type defined for given single query. There can be one asterisk of 2nd type defined for given single query. */ class KDB_EXPORT KDbQueryAsterisk : public KDbField { public: /*! Constructs an "all-tables" query asterisk definition object ("*" in SQL notation). KDbQueryAsterisk objects are owned by KDbQuerySchema object (not by KDbTableSchema object like for ordinary KDbField objects) for that the KDbQueryAsterisk object was added (using KDbQuerySchema::addField()). */ explicit KDbQueryAsterisk(KDbQuerySchema *query); /*! Constructs a "single-table" query asterisk definition object ("T.*" in SQL notation). @a table schema is the single table for the asterisk. KDbQueryAsterisk objects are owned by KDbQuerySchema object (not by KDbTableSchema object like for ordinary KDbField objects) for that the KDbQueryAsterisk object was added (using KDbQuerySchema::addField()). */ KDbQueryAsterisk(KDbQuerySchema *query, const KDbTableSchema &table); /*! Constructs a deep copy of query asterisk definition object @a asterisk. */ KDbQueryAsterisk(const KDbQueryAsterisk &asterisk); - virtual ~KDbQueryAsterisk(); + ~KDbQueryAsterisk() override; /*! @return Query object for that this asterisk object is defined */ KDbQuerySchema *query(); /*! @overload KDbQuerySchema *query() */ const KDbQuerySchema *query() const; /*! @return table schema object for that this asterisk object is defined. If this is a "all-tables" asterisk, @c nullptr is returned. */ const KDbTableSchema* table(); /*! @overload const KDbTableSchema* table() */ const KDbTableSchema* table() const; /*! Sets table schema for this asterisk. If table is supplied, the asterisk become a "single-table" asterisk. If @a table is @c nullptr the asterisk becames "all-tables" asterisk. */ void setTable(const KDbTableSchema *table); /*! This is convenience method that returns @c true if the asterisk has "all-tables" type (2nd type).*/ bool isSingleTableAsterisk() const; /*! This is convenience method that returns @c true if the asterisk has "single-table" type (2nd type).*/ bool isAllTableAsterisk() const; protected: //! @return a deep copy of this object. Used in KDbFieldList(const KDbFieldList& fl). KDbField* copy() override; KDbQueryAsterisk(KDbQuerySchema *query, const KDbTableSchema *table); private: class Private; Private * const d; KDbQueryAsterisk& operator=(const KDbQueryAsterisk &) = delete; void setTable(KDbTableSchema *table); // protect }; //! Sends query asterisk information @a asterisk to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbQueryAsterisk& asterisk); #endif diff --git a/src/KDbQueryColumnInfo.cpp b/src/KDbQueryColumnInfo.cpp index 867b86bb..123ea45b 100644 --- a/src/KDbQueryColumnInfo.cpp +++ b/src/KDbQueryColumnInfo.cpp @@ -1,145 +1,145 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbQueryColumnInfo.h" #include "KDbTableSchema.h" #include "KDbField.h" #include "KDbField_p.h" #include "kdb_debug.h" //! @internal class Q_DECL_HIDDEN KDbQueryColumnInfo::Private { public: Private(KDbField *f, const QString& a, bool v, KDbQueryColumnInfo *foreign) : field(f) , alias(a) , visible(v) , indexForVisibleLookupValue(-1) , foreignColumn(foreign) { } KDbField *field; QString alias; //! @c true if this column is visible to the user (and its data is fetched by the engine) bool visible; /*! Index of column with visible lookup value within the 'fields expanded' vector. @see KDbQueryColumnInfo::indexForVisibleLookupValue() */ int indexForVisibleLookupValue; //! Non-nullptr if this column is a visible column for @a foreignColumn KDbQueryColumnInfo *foreignColumn; }; KDbQueryColumnInfo::KDbQueryColumnInfo(KDbField *f, const QString& alias, bool visible, KDbQueryColumnInfo *foreignColumn) : d(new Private(f, alias, visible, foreignColumn)) { } KDbQueryColumnInfo::~KDbQueryColumnInfo() { delete d; } KDbField* KDbQueryColumnInfo::field() { return d->field; } const KDbField* KDbQueryColumnInfo::field() const { return d->field; } void KDbQueryColumnInfo::setField(KDbField *field) { d->field = field; } QString KDbQueryColumnInfo::alias() const { return d->alias; } void KDbQueryColumnInfo::setAlias(const QString &alias) { d->alias = alias; } QString KDbQueryColumnInfo::aliasOrName() const { return d->alias.isEmpty() ? d->field->name() : d->alias; } QString KDbQueryColumnInfo::captionOrAliasOrName() const { return d->field->caption().isEmpty() ? aliasOrName() : d->field->caption(); } bool KDbQueryColumnInfo::isVisible() const { return d->visible; } void KDbQueryColumnInfo::setVisible(bool set) { d->visible = set; } int KDbQueryColumnInfo::indexForVisibleLookupValue() const { return d->indexForVisibleLookupValue; } void KDbQueryColumnInfo::setIndexForVisibleLookupValue(int index) { d->indexForVisibleLookupValue = index; } KDbQueryColumnInfo *KDbQueryColumnInfo::foreignColumn() { return d->foreignColumn; } const KDbQueryColumnInfo *KDbQueryColumnInfo::foreignColumn() const { return d->foreignColumn; } QDebug operator<<(QDebug dbg, const KDbQueryColumnInfo& info) { QString fieldName; if (info.field()->name().isEmpty()) { fieldName = QLatin1String(""); } else { fieldName = info.field()->name(); } dbg.nospace() << (info.field()->table() ? (info.field()->table()->name() + QLatin1Char('.')) : QString()) + fieldName; debug(dbg, *info.field(), KDbFieldDebugNoOptions); dbg.nospace() << qPrintable(info.alias().isEmpty() ? QString() : (QLatin1String(" AS ") + info.alias())) - << qPrintable(QLatin1String(info.isVisible() ? 0 : " [INVISIBLE]")); + << qPrintable(QLatin1String(info.isVisible() ? nullptr : " [INVISIBLE]")); return dbg.space(); } diff --git a/src/KDbQuerySchema.cpp b/src/KDbQuerySchema.cpp index e5831704..080628e1 100644 --- a/src/KDbQuerySchema.cpp +++ b/src/KDbQuerySchema.cpp @@ -1,1415 +1,1415 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbQueryAsterisk.h" #include "KDbConnection.h" #include "kdb_debug.h" #include "KDbLookupFieldSchema.h" #include "KDbOrderByColumn.h" #include "KDbParser_p.h" #include "KDbQuerySchemaParameter.h" #include "KDbRelationship.h" QString escapeIdentifier(const QString& name, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) { switch (escapingType) { case KDb::DriverEscaping: if (conn) return conn->escapeIdentifier(name); break; case KDb::KDbEscaping: return KDb::escapeIdentifier(name); } return QLatin1Char('"') + name + QLatin1Char('"'); } KDbQuerySchema::KDbQuerySchema() : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { init(); } KDbQuerySchema::KDbQuerySchema(KDbTableSchema *tableSchema) : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { Q_ASSERT(tableSchema); d->masterTable = tableSchema; d->conn = tableSchema->connection(); init(); /*if (!d->masterTable) { kdbWarning() << "!d->masterTable"; m_name.clear(); return; }*/ addTable(d->masterTable); //defaults: //inherit name from a table setName(d->masterTable->name()); //inherit caption from a table setCaption(d->masterTable->caption()); // add explicit field list to avoid problems (e.g. with fields added outside of the app): foreach(KDbField* f, *d->masterTable->fields()) { addField(f); } } KDbQuerySchema::KDbQuerySchema(const KDbQuerySchema& querySchema) : KDbFieldList(querySchema, false /* !deepCopyFields */) , KDbObject(querySchema) , d(new Private(this, querySchema.d)) { //only deep copy query asterisks foreach(KDbField* f, *querySchema.fields()) { KDbField *copiedField; if (dynamic_cast(f)) { copiedField = f->copy(); if (static_cast(f->m_parent) == &querySchema) { copiedField->m_parent = this; } } else { copiedField = f; } addField(copiedField); } // this deep copy must be after the 'd' initialization because fieldsExpanded() is used there d->orderByColumnList = new KDbOrderByColumnList(*querySchema.d->orderByColumnList, const_cast(&querySchema), this); } KDbQuerySchema::KDbQuerySchema(KDbConnection *conn) : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { Q_ASSERT(conn); init(); d->conn = conn; } KDbQuerySchema::~KDbQuerySchema() { delete d; } void KDbQuerySchema::init() { //m_fields_by_name.setAutoDelete( true ); //because we're using QueryColumnInfoEntry objects } void KDbQuerySchema::clear() { KDbFieldList::clear(); KDbObject::clear(); d->clear(); } /*virtual*/ bool KDbQuerySchema::insertField(int position, KDbField *field) { return insertFieldInternal(position, field, -1/*don't bind*/, true); } bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field) { return insertFieldInternal(position, field, -1/*don't bind*/, false); } bool KDbQuerySchema::insertField(int position, KDbField *field, int bindToTable) { return insertFieldInternal(position, field, bindToTable, true); } bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field, int bindToTable) { return insertFieldInternal(position, field, bindToTable, false); } bool KDbQuerySchema::insertFieldInternal(int position, KDbField *field, int bindToTable, bool visible) { if (!field) { kdbWarning() << "!field"; return false; } if (position > m_fields.count()) { kdbWarning() << "position" << position << "out of range"; return false; } if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) { kdbWarning() << "field" << field->name() << "must contain table information!"; return false; } if (fieldCount() >= d->visibility.size()) { d->visibility.resize(d->visibility.size()*2); d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2); } d->clearCachedData(); if (!KDbFieldList::insertField(position, field)) { return false; } if (field->isQueryAsterisk()) { d->asterisks.append(field); //if this is single-table asterisk, //add a table to list if doesn't exist there: if (field->table() && !d->tables.contains(field->table())) d->tables.append(field->table()); } else if (field->table()) { //add a table to list if doesn't exist there: if (!d->tables.contains(field->table())) d->tables.append(field->table()); } //update visibility //--move bits to make a place for a new one for (int i = fieldCount() - 1; i > position; i--) d->visibility.setBit(i, d->visibility.testBit(i - 1)); d->visibility.setBit(position, visible); //bind to table if (bindToTable < -1 || bindToTable > d->tables.count()) { kdbWarning() << "bindToTable" << bindToTable << "out of range"; bindToTable = -1; } //--move items to make a place for a new one for (int i = fieldCount() - 1; i > position; i--) d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1]; d->tablesBoundToColumns[position] = bindToTable; kdbDebug() << "bound to table" << bindToTable; if (bindToTable == -1) kdbDebug() << " "; else kdbDebug() << " name=" << d->tables.at(bindToTable)->name() << " alias=" << tableAlias(bindToTable); QString s; for (int i = 0; i < fieldCount();i++) s += (QString::number(d->tablesBoundToColumns[i]) + QLatin1Char(' ')); kdbDebug() << "tablesBoundToColumns == [" << s << "]"; if (field->isExpression()) d->regenerateExprAliases = true; return true; } int KDbQuerySchema::tableBoundToColumn(int columnPosition) const { int res = d->tablesBoundToColumns.value(columnPosition, -99); if (res == -99) { kdbWarning() << "columnPosition" << columnPosition << "out of range"; return -1; } return res; } bool KDbQuerySchema::addField(KDbField* field) { return insertField(m_fields.count(), field); } bool KDbQuerySchema::addField(KDbField* field, int bindToTable) { return insertField(m_fields.count(), field, bindToTable); } bool KDbQuerySchema::addInvisibleField(KDbField* field) { return insertInvisibleField(m_fields.count(), field); } bool KDbQuerySchema::addInvisibleField(KDbField* field, int bindToTable) { return insertInvisibleField(m_fields.count(), field, bindToTable); } bool KDbQuerySchema::removeField(KDbField *field) { int indexOfAsterisk = -1; if (field->isQueryAsterisk()) { indexOfAsterisk = d->asterisks.indexOf(field); } if (!KDbFieldList::removeField(field)) { return false; } d->clearCachedData(); if (indexOfAsterisk >= 0) { //kdbDebug() << "d->asterisks.removeAt:" << field; //field->debug(); d->asterisks.removeAt(indexOfAsterisk); //this will destroy this asterisk } //! @todo should we also remove table for this field or asterisk? return true; } bool KDbQuerySchema::addExpressionInternal(const KDbExpression& expr, bool visible) { KDbField *field = new KDbField(this, expr); bool ok; if (visible) { ok = addField(field); } else { ok = addInvisibleField(field); } if (!ok) { delete field; } return ok; } bool KDbQuerySchema::addExpression(const KDbExpression& expr) { return addExpressionInternal(expr, true); } bool KDbQuerySchema::addInvisibleExpression(const KDbExpression& expr) { return addExpressionInternal(expr, false); } bool KDbQuerySchema::isColumnVisible(int position) const { return (position < fieldCount()) ? d->visibility.testBit(position) : false; } void KDbQuerySchema::setColumnVisible(int position, bool visible) { if (position < fieldCount()) d->visibility.setBit(position, visible); } bool KDbQuerySchema::addAsteriskInternal(KDbQueryAsterisk *asterisk, bool visible) { if (!asterisk) { return false; } //make unique name asterisk->setName((asterisk->table() ? (asterisk->table()->name() + QLatin1String(".*")) : QString(QLatin1Char('*'))) + QString::number(asterisks()->count())); return visible ? addField(asterisk) : addInvisibleField(asterisk); } bool KDbQuerySchema::addAsterisk(KDbQueryAsterisk *asterisk) { return addAsteriskInternal(asterisk, true); } bool KDbQuerySchema::addInvisibleAsterisk(KDbQueryAsterisk *asterisk) { return addAsteriskInternal(asterisk, false); } KDbConnection* KDbQuerySchema::connection() const { if (d->conn) { return d->conn; } if (!d->tables.isEmpty()) { return d->tables.first()->connection(); } - return 0; + return nullptr; } QDebug operator<<(QDebug dbg, const KDbQuerySchema& query) { //fields KDbTableSchema *mt = query.masterTable(); dbg.nospace() << "QUERY"; dbg.space() << static_cast(query) << '\n'; dbg.nospace() << " - MASTERTABLE=" << (mt ? mt->name() : QLatin1String("")) << "\n - COLUMNS:\n"; if (query.fieldCount() > 0) dbg.nospace() << static_cast(query) << '\n'; else dbg.nospace() << "\n"; if (query.fieldCount() == 0) dbg.nospace() << " - NO FIELDS\n"; else dbg.nospace() << " - FIELDS EXPANDED ("; int fieldsExpandedCount = 0; bool first; if (query.fieldCount() > 0) { const KDbQueryColumnInfo::Vector fe(query.fieldsExpanded()); fieldsExpandedCount = fe.size(); dbg.nospace() << fieldsExpandedCount << "):\n"; first = true; for (int i = 0; i < fieldsExpandedCount; i++) { KDbQueryColumnInfo *ci = fe[i]; if (first) first = false; else dbg.nospace() << ",\n"; dbg.nospace() << *ci; } dbg.nospace() << '\n'; } //it's safer to delete fieldsExpanded for now // (debugString() could be called before all fields are added) //bindings dbg.nospace() << " - BINDINGS:\n"; bool bindingsExist = false; for (int i = 0; i < query.fieldCount(); i++) { const int tablePos = query.tableBoundToColumn(i); if (tablePos >= 0) { const QString tAlias(query.tableAlias(tablePos)); if (!tAlias.isEmpty()) { bindingsExist = true; dbg.space() << "FIELD"; dbg.space() << static_cast(query).field(i)->name(); dbg.space() << "USES ALIAS"; dbg.space() << tAlias; dbg.space() << "OF TABLE"; dbg.space() << query.tables()->at(tablePos)->name() << '\n'; } } } if (!bindingsExist) { dbg.nospace() << "\n"; } //tables dbg.nospace() << " - TABLES:\n"; first = true; foreach(KDbTableSchema *table, *query.tables()) { if (first) first = false; else dbg.nospace() << ","; dbg.space() << table->name(); } if (query.tables()->isEmpty()) dbg.nospace() << ""; //aliases dbg.nospace() << "\n - COLUMN ALIASES:\n"; if (query.columnAliasesCount() == 0) { dbg.nospace() << "\n"; } else { int i = -1; foreach(KDbField *f, *query.fields()) { i++; const QString alias(query.columnAlias(i)); if (!alias.isEmpty()) { dbg.nospace() << QString::fromLatin1("FIELD #%1:").arg(i); dbg.space() << (f->name().isEmpty() ? QLatin1String("") : f->name()) << " -> " << alias << '\n'; } } } dbg.nospace() << "\n- TABLE ALIASES:\n"; if (query.tableAliasesCount() == 0) { dbg.nospace() << "\n"; } else { int i = -1; foreach(KDbTableSchema* table, *query.tables()) { i++; const QString alias(query.tableAlias(i)); if (!alias.isEmpty()) { dbg.nospace() << QString::fromLatin1("table #%1:").arg(i); dbg.space() << (table->name().isEmpty() ? QLatin1String("") : table->name()) << " -> " << alias << '\n'; } } } if (!query.whereExpression().isNull()) { dbg.nospace() << " - WHERE EXPRESSION:\n" << query.whereExpression() << '\n'; } if (!query.orderByColumnList()->isEmpty()) { dbg.space() << QString::fromLatin1(" - ORDER BY (%1):\n").arg(query.orderByColumnList()->count()); dbg.nospace() << *query.orderByColumnList(); } return dbg.nospace(); } KDbTableSchema* KDbQuerySchema::masterTable() const { if (d->masterTable) return d->masterTable; if (d->tables.isEmpty()) - return 0; + return nullptr; //try to find master table if there's only one table (with possible aliasses) QString tableNameLower; int num = -1; foreach(KDbTableSchema *table, d->tables) { num++; if (!tableNameLower.isEmpty() && table->name().toLower() != tableNameLower) { //two or more different tables - return 0; + return nullptr; } tableNameLower = tableAlias(num); } return d->tables.first(); } void KDbQuerySchema::setMasterTable(KDbTableSchema *table) { if (table) d->masterTable = table; } QList* KDbQuerySchema::tables() const { return &d->tables; } void KDbQuerySchema::addTable(KDbTableSchema *table, const QString& alias) { kdbDebug() << (void *)table << "alias=" << alias; if (!table) return; // only append table if: it has alias or it has no alias but there is no such table on the list if (alias.isEmpty() && d->tables.contains(table)) { int num = -1; foreach(KDbTableSchema *t, d->tables) { num++; if (0 == t->name().compare(table->name(), Qt::CaseInsensitive)) { if (tableAlias(num).isEmpty()) { kdbDebug() << "table" << table->name() << "without alias already added"; return; } } } } d->tables.append(table); if (!alias.isEmpty()) setTableAlias(d->tables.count() - 1, alias); } void KDbQuerySchema::removeTable(KDbTableSchema *table) { if (!table) return; if (d->masterTable == table) - d->masterTable = 0; + d->masterTable = nullptr; d->tables.removeAt(d->tables.indexOf(table)); //! @todo remove fields! } KDbTableSchema* KDbQuerySchema::table(const QString& tableName) const { //! @todo maybe use tables_byname? foreach(KDbTableSchema *table, d->tables) { if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { return table; } } - return 0; + return nullptr; } bool KDbQuerySchema::contains(KDbTableSchema *table) const { return d->tables.contains(table); } KDbField* KDbQuerySchema::findTableField(const QString &tableOrTableAndFieldName) const { QString tableName, fieldName; if (!KDb::splitToTableAndFieldParts(tableOrTableAndFieldName, &tableName, &fieldName, KDb::SetFieldNameIfNoTableName)) { - return 0; + return nullptr; } if (tableName.isEmpty()) { foreach(KDbTableSchema *table, d->tables) { if (table->field(fieldName)) return table->field(fieldName); } - return 0; + return nullptr; } KDbTableSchema *tableSchema = table(tableName); if (!tableSchema) - return 0; + return nullptr; return tableSchema->field(fieldName); } int KDbQuerySchema::columnAliasesCount() const { return d->columnAliasesCount(); } QString KDbQuerySchema::columnAlias(int position) const { return d->columnAlias(position); } bool KDbQuerySchema::hasColumnAlias(int position) const { return d->hasColumnAlias(position); } void KDbQuerySchema::setColumnAlias(int position, const QString& alias) { if (position >= m_fields.count()) { kdbWarning() << "position" << position << "out of range!"; return; } const QString fixedAlias(alias.trimmed()); KDbField *f = KDbFieldList::field(position); if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) { kdbWarning() << "position" << position << "could not remove alias when no name is specified for expression column!"; return; } d->setColumnAlias(position, fixedAlias); } int KDbQuerySchema::tableAliasesCount() const { return d->tableAliases.count(); } QString KDbQuerySchema::tableAlias(int position) const { return d->tableAliases.value(position); } QString KDbQuerySchema::tableAlias(const QString& tableName) const { const int pos = tablePosition(tableName); if (pos == -1) { return QString(); } return d->tableAliases.value(pos); } QString KDbQuerySchema::tableAliasOrName(const QString& tableName) const { const int pos = tablePosition(tableName); if (pos == -1) { return QString(); } return KDb::iifNotEmpty(d->tableAliases.value(pos), tableName); } int KDbQuerySchema::tablePositionForAlias(const QString& name) const { return d->tablePositionForAlias(name); } int KDbQuerySchema::tablePosition(const QString& tableName) const { int num = -1; foreach(KDbTableSchema* table, d->tables) { num++; if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { return num; } } return -1; } QList KDbQuerySchema::tablePositions(const QString& tableName) const { QList result; int num = -1; foreach(KDbTableSchema* table, d->tables) { num++; if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { result += num; } } return result; } bool KDbQuerySchema::hasTableAlias(int position) const { return d->tableAliases.contains(position); } int KDbQuerySchema::columnPositionForAlias(const QString& name) const { return d->columnPositionForAlias(name); } void KDbQuerySchema::setTableAlias(int position, const QString& alias) { if (position >= d->tables.count()) { kdbWarning() << "position" << position << "out of range!"; return; } const QString fixedAlias(alias.trimmed()); if (fixedAlias.isEmpty()) { const QString oldAlias(d->tableAliases.take(position)); if (!oldAlias.isEmpty()) { d->removeTablePositionForAlias(oldAlias); } } else { d->setTableAlias(position, fixedAlias); } } QList* KDbQuerySchema::relationships() const { return &d->relations; } KDbField::List* KDbQuerySchema::asterisks() const { return &d->asterisks; } KDbEscapedString KDbQuerySchema::statement() const { return d->sql; } void KDbQuerySchema::setStatement(const KDbEscapedString& sql) { d->sql = sql; } const KDbField* KDbQuerySchema::field(const QString& identifier) const { KDbQueryColumnInfo *ci = columnInfo(identifier, true /*expanded*/); return ci ? ci->field() : nullptr; } KDbField* KDbQuerySchema::field(const QString& identifier) { return const_cast(static_cast(this)->field(identifier)); } const KDbField* KDbQuerySchema::unexpandedField(const QString& identifier) const { KDbQueryColumnInfo *ci = columnInfo(identifier, false /*unexpanded*/); return ci ? ci->field() : nullptr; } KDbField* KDbQuerySchema::unexpandedField(const QString& identifier) { return const_cast(static_cast(this)->unexpandedField(identifier)); } KDbField* KDbQuerySchema::field(int id) { return KDbFieldList::field(id); } const KDbField* KDbQuerySchema::field(int id) const { return KDbFieldList::field(id); } KDbQueryColumnInfo* KDbQuerySchema::columnInfo(const QString& identifier, bool expanded) const { computeFieldsExpanded(); return expanded ? d->columnInfosByNameExpanded.value(identifier) : d->columnInfosByName.value(identifier); } KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpandedInternal( FieldsExpandedOptions options, bool onlyVisible) const { computeFieldsExpanded(); KDbQueryColumnInfo::Vector *realFieldsExpanded = onlyVisible ? d->visibleFieldsExpanded : d->fieldsExpanded; if (options == WithInternalFields || options == WithInternalFieldsAndRecordId) { //a ref to a proper pointer (as we cache the vector for two cases) KDbQueryColumnInfo::Vector*& tmpFieldsExpandedWithInternal = (options == WithInternalFields) ? (onlyVisible ? d->visibleFieldsExpandedWithInternal : d->fieldsExpandedWithInternal) : (onlyVisible ? d->visibleFieldsExpandedWithInternalAndRecordId : d->fieldsExpandedWithInternalAndRecordId); //special case if (!tmpFieldsExpandedWithInternal) { //glue expanded and internal fields and cache it const int internalFieldCount = d->internalFields ? d->internalFields->size() : 0; const int fieldsExpandedVectorSize = realFieldsExpanded->size(); const int size = fieldsExpandedVectorSize + internalFieldCount + ((options == WithInternalFieldsAndRecordId) ? 1 : 0) /*ROWID*/; tmpFieldsExpandedWithInternal = new KDbQueryColumnInfo::Vector(size); for (int i = 0; i < fieldsExpandedVectorSize; ++i) { (*tmpFieldsExpandedWithInternal)[i] = realFieldsExpanded->at(i); } if (internalFieldCount > 0) { for (int i = 0; i < internalFieldCount; ++i) { KDbQueryColumnInfo *info = d->internalFields->at(i); (*tmpFieldsExpandedWithInternal)[fieldsExpandedVectorSize + i] = info; } } if (options == WithInternalFieldsAndRecordId) { if (!d->fakeRecordIdField) { d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), KDbField::BigInteger); d->fakeRecordIdCol = new KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true); } (*tmpFieldsExpandedWithInternal)[fieldsExpandedVectorSize + internalFieldCount] = d->fakeRecordIdCol; } } return *tmpFieldsExpandedWithInternal; } if (options == Default) { return *realFieldsExpanded; } //options == Unique: QSet columnsAlreadyFound; const int fieldsExpandedCount(realFieldsExpanded->count()); KDbQueryColumnInfo::Vector result(fieldsExpandedCount); //initial size is set //compute unique list int uniqueListCount = 0; for (int i = 0; i < fieldsExpandedCount; i++) { KDbQueryColumnInfo *ci = realFieldsExpanded->at(i); if (!columnsAlreadyFound.contains(ci->aliasOrName())) { columnsAlreadyFound.insert(ci->aliasOrName()); result[uniqueListCount++] = ci; } } result.resize(uniqueListCount); //update result size return result; } KDbQueryColumnInfo::Vector KDbQuerySchema::internalFields() const { computeFieldsExpanded(); return d->internalFields ? *d->internalFields : KDbQueryColumnInfo::Vector(); } KDbQueryColumnInfo* KDbQuerySchema::expandedOrInternalField(int index) const { return fieldsExpanded(WithInternalFields).value(index); } inline static QString lookupColumnKey(KDbField *foreignField, KDbField* field) { QString res; if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns res = field->table()->name() + QLatin1Char('.'); return res + field->name() + QLatin1Char('_') + foreignField->table()->name() + QLatin1Char('.') + foreignField->name(); } void KDbQuerySchema::computeFieldsExpanded() const { if (d->fieldsExpanded) return; if (!d->columnsOrder) { d->columnsOrder = new QHash(); d->columnsOrderWithoutAsterisks = new QHash(); } else { d->columnsOrder->clear(); d->columnsOrderWithoutAsterisks->clear(); } if (d->ownedVisibleColumns) d->ownedVisibleColumns->clear(); //collect all fields in a list (not a vector yet, because we do not know its size) KDbQueryColumnInfo::List list; //temporary KDbQueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields QHash columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName int i = 0; int numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field int fieldPosition = -1; foreach(KDbField *f, m_fields) { fieldPosition++; if (f->isQueryAsterisk()) { if (static_cast(f)->isSingleTableAsterisk()) { const KDbField::List *ast_fields = static_cast(f)->table()->fields(); foreach(KDbField *ast_f, *ast_fields) { KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); } } else {//all-tables asterisk: iterate through table list foreach(KDbTableSchema *table, d->tables) { //add all fields from this table const KDbField::List *tab_fields = table->fields(); foreach(KDbField *tab_f, *tab_fields) { //! @todo (js): perhaps not all fields should be appended here // d->detailedVisibility += isFieldVisible(fieldPosition); // list.append(tab_f); KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); } } } } else { //a single field KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition)); list.append(ci); columnInfosOutsideAsterisks.insert(ci, true); kdbDebug() << "caching (unexpanded) column's order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); d->columnsOrderWithoutAsterisks->insert(ci, fieldPosition); //handle lookup field schema - KDbLookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema(*f) : 0; + KDbLookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema(*f) : nullptr; if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0) continue; // Lookup field schema found: // Now we also need to fetch "visible" value from the lookup table, not only the value of binding. // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken) // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField" KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Table) { KDbTableSchema *lookupTable = connection()->tableSchema(recordSource.name()); - KDbFieldList* visibleColumns = 0; - KDbField *boundField = 0; + KDbFieldList* visibleColumns = nullptr; + KDbField *boundField = nullptr; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns())) && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) { - KDbField *visibleColumn = 0; + KDbField *visibleColumn = nullptr; // for single visible column, just add it as-is if (visibleColumns->fieldCount() == 1) { visibleColumn = visibleColumns->fields()->first(); } else { // for multiple visible columns, build an expression column // (the expression object will be owned by column info) visibleColumn = new KDbField(); visibleColumn->setName( QString::fromLatin1("[multiple_visible_fields_%1]") .arg(++numberOfColumnsWithMultipleVisibleFields)); visibleColumn->setExpression( KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); if (!d->ownedVisibleColumns) { d->ownedVisibleColumns = new KDbField::List(); } d->ownedVisibleColumns->append(visibleColumn); // remember to delete later } lookup_list.append( new KDbQueryColumnInfo(visibleColumn, QString(), true/*visible*/, ci/*foreign*/)); /* //add visibleField to the list of SELECTed fields if it is not yes present there if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { if (!table( visibleField->table()->name() )) { } if (!sql.isEmpty()) sql += QString::fromLatin1(", "); sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + escapeIdentifier(visibleField->name(), drvEscaping)); }*/ } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Query) { KDbQuerySchema *lookupQuery = connection()->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(lookupQuery->fieldsExpanded()); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; - KDbQueryColumnInfo *boundColumnInfo = 0; + KDbQueryColumnInfo *boundColumnInfo = nullptr; if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn()))) continue; KDbField *boundField = boundColumnInfo->field(); if (!boundField) continue; const QList visibleColumns(lookupFieldSchema->visibleColumns()); bool ok = true; // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1] foreach(int visibleColumn, visibleColumns) { if (visibleColumn >= lookupQueryFieldsExpanded.count()) { ok = false; break; } } if (!ok) continue; - KDbField *visibleColumn = 0; + KDbField *visibleColumn = nullptr; // for single visible column, just add it as-is if (visibleColumns.count() == 1) { visibleColumn = lookupQueryFieldsExpanded.value(visibleColumns.first())->field(); } else { // for multiple visible columns, build an expression column // (the expression object will be owned by column info) visibleColumn = new KDbField(); visibleColumn->setName( QString::fromLatin1("[multiple_visible_fields_%1]") .arg(++numberOfColumnsWithMultipleVisibleFields)); visibleColumn->setExpression( KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); if (!d->ownedVisibleColumns) { d->ownedVisibleColumns = new KDbField::List(); } d->ownedVisibleColumns->append(visibleColumn); // remember to delete later } lookup_list.append( new KDbQueryColumnInfo(visibleColumn, QString(), true/*visible*/, ci/*foreign*/)); /* //add visibleField to the list of SELECTed fields if it is not yes present there if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { if (!table( visibleField->table()->name() )) { } if (!sql.isEmpty()) sql += QString::fromLatin1(", "); sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + escapeIdentifier(visibleField->name(), drvEscaping)); }*/ } } } //prepare clean vector for expanded list, and a map for order information if (!d->fieldsExpanded) { d->fieldsExpanded = new KDbQueryColumnInfo::Vector(list.count()); d->visibleFieldsExpanded = new KDbQueryColumnInfo::Vector(list.count()); d->columnsOrderExpanded = new QHash(); } else {//for future: qDeleteAll(*d->fieldsExpanded); d->fieldsExpanded->clear(); d->fieldsExpanded->resize(list.count()); d->visibleFieldsExpanded->clear(); d->visibleFieldsExpanded->resize(list.count()); d->columnsOrderExpanded->clear(); } /*fill (based on prepared 'list' and 'lookup_list'): -the vector -the map -"fields by name" dictionary */ d->columnInfosByName.clear(); d->columnInfosByNameExpanded.clear(); i = -1; int visibleIndex = -1; foreach(KDbQueryColumnInfo* ci, list) { i++; (*d->fieldsExpanded)[i] = ci; if (ci->isVisible()) { ++visibleIndex; (*d->visibleFieldsExpanded)[visibleIndex] = ci; } d->columnsOrderExpanded->insert(ci, i); //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded if (!ci->alias().isEmpty()) { //store alias and table.alias if (!d->columnInfosByNameExpanded.contains(ci->alias())) { d->columnInfosByNameExpanded.insert(ci->alias(), ci); } QString tableAndAlias(ci->alias()); if (ci->field()->table()) tableAndAlias.prepend(ci->field()->table()->name() + QLatin1Char('.')); if (!d->columnInfosByNameExpanded.contains(tableAndAlias)) { d->columnInfosByNameExpanded.insert(tableAndAlias, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!d->columnInfosByName.contains(ci->alias())) { d->columnInfosByName.insert(ci->alias(), ci); } if (!d->columnInfosByName.contains(tableAndAlias)) { d->columnInfosByName.insert(tableAndAlias, ci); } } } else { //no alias: store name and table.name if (!d->columnInfosByNameExpanded.contains(ci->field()->name())) { d->columnInfosByNameExpanded.insert(ci->field()->name(), ci); } QString tableAndName(ci->field()->name()); if (ci->field()->table()) tableAndName.prepend(ci->field()->table()->name() + QLatin1Char('.')); if (!d->columnInfosByNameExpanded.contains(tableAndName)) { d->columnInfosByNameExpanded.insert(tableAndName, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!d->columnInfosByName.contains(ci->field()->name())) { d->columnInfosByName.insert(ci->field()->name(), ci); } if (!d->columnInfosByName.contains(tableAndName)) { d->columnInfosByName.insert(tableAndName, ci); } } } } d->visibleFieldsExpanded->resize(visibleIndex + 1); //remove duplicates for lookup fields QHash lookup_dict; //used to fight duplicates and to update KDbQueryColumnInfo::indexForVisibleLookupValue() // (a mapping from table.name string to int* lookupFieldIndex i = 0; for (QMutableListIterator it(lookup_list); it.hasNext();) { KDbQueryColumnInfo* ci = it.next(); const QString key(lookupColumnKey(ci->foreignColumn()->field(), ci->field())); if (lookup_dict.contains(key)) { // this table.field is already fetched by this query it.remove(); delete ci; } else { lookup_dict.insert(key, i); i++; } } //create internal expanded list with lookup fields if (d->internalFields) { qDeleteAll(*d->internalFields); d->internalFields->clear(); d->internalFields->resize(lookup_list.count()); } delete d->fieldsExpandedWithInternal; //clear cache delete d->fieldsExpandedWithInternalAndRecordId; //clear cache - d->fieldsExpandedWithInternal = 0; - d->fieldsExpandedWithInternalAndRecordId = 0; + d->fieldsExpandedWithInternal = nullptr; + d->fieldsExpandedWithInternalAndRecordId = nullptr; if (!lookup_list.isEmpty() && !d->internalFields) {//create on demand d->internalFields = new KDbQueryColumnInfo::Vector(lookup_list.count()); } i = -1; foreach(KDbQueryColumnInfo *ci, lookup_list) { i++; //add it to the internal list (*d->internalFields)[i] = ci; d->columnsOrderExpanded->insert(ci, list.count() + i); } //update KDbQueryColumnInfo::indexForVisibleLookupValue() cache for columns numberOfColumnsWithMultipleVisibleFields = 0; for (i = 0; i < d->fieldsExpanded->size(); i++) { KDbQueryColumnInfo* ci = d->fieldsExpanded->at(i); //! @todo KDbQuerySchema itself will also support lookup fields... KDbLookupFieldSchema *lookupFieldSchema = ci->field()->table() ? ci->field()->table()->lookupFieldSchema(*ci->field()) : nullptr; if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0) continue; const KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Table) { KDbTableSchema *lookupTable = connection()->tableSchema(recordSource.name()); - KDbFieldList* visibleColumns = 0; + KDbFieldList* visibleColumns = nullptr; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))) { // for single visible column, just add it as-is if (visibleColumns->fieldCount() == 1) { KDbField *visibleColumn = visibleColumns->fields()->first(); const QString key(lookupColumnKey(ci->field(), visibleColumn)); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } else { const QString key(QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") .arg(++numberOfColumnsWithMultipleVisibleFields) .arg(ci->field()->table()->name(), ci->field()->name())); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Query) { KDbQuerySchema *lookupQuery = connection()->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(lookupQuery->fieldsExpanded()); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; - KDbQueryColumnInfo *boundColumnInfo = 0; + KDbQueryColumnInfo *boundColumnInfo = nullptr; if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn()))) continue; KDbField *boundField = boundColumnInfo->field(); if (!boundField) continue; const QList visibleColumns(lookupFieldSchema->visibleColumns()); // for single visible column, just add it as-is if (visibleColumns.count() == 1) { if (lookupQueryFieldsExpanded.count() > visibleColumns.first()) { // sanity check KDbField *visibleColumn = lookupQueryFieldsExpanded.at(visibleColumns.first())->field(); const QString key(lookupColumnKey(ci->field(), visibleColumn)); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } } else { const QString key(QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") .arg(++numberOfColumnsWithMultipleVisibleFields) .arg(ci->field()->table()->name(), ci->field()->name())); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } } else { kdbWarning() << "unsupported record source type" << recordSource.typeName(); } } } QHash KDbQuerySchema::columnsOrder(ColumnsOrderOptions options) const { if (!d->columnsOrder) computeFieldsExpanded(); if (options == UnexpandedList) return *d->columnsOrder; else if (options == UnexpandedListWithoutAsterisks) return *d->columnsOrderWithoutAsterisks; return *d->columnsOrderExpanded; } QVector KDbQuerySchema::pkeyFieldsOrder() const { if (d->pkeyFieldsOrder) return *d->pkeyFieldsOrder; KDbTableSchema *tbl = masterTable(); if (!tbl || !tbl->primaryKey()) return QVector(); //get order of PKEY fields (e.g. for records updating or inserting ) KDbIndexSchema *pkey = tbl->primaryKey(); kdbDebug() << *pkey; d->pkeyFieldsOrder = new QVector(pkey->fieldCount(), -1); const int fCount = fieldsExpanded().count(); d->pkeyFieldCount = 0; for (int i = 0; i < fCount; i++) { KDbQueryColumnInfo *fi = d->fieldsExpanded->at(i); const int fieldIndex = fi->field()->table() == tbl ? pkey->indexOf(*fi->field()) : -1; if (fieldIndex != -1/* field found in PK */ && d->pkeyFieldsOrder->at(fieldIndex) == -1 /* first time */) { kdbDebug() << "FIELD" << fi->field()->name() << "IS IN PKEY AT POSITION #" << fieldIndex; (*d->pkeyFieldsOrder)[fieldIndex] = i; d->pkeyFieldCount++; } } kdbDebug() << d->pkeyFieldCount << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name(); return *d->pkeyFieldsOrder; } int KDbQuerySchema::pkeyFieldCount() { (void)pkeyFieldsOrder(); /* rebuild information */ return d->pkeyFieldCount; } KDbRelationship* KDbQuerySchema::addRelationship(KDbField *field1, KDbField *field2) { //@todo: find existing global db relationships KDbRelationship *r = new KDbRelationship(this, field1, field2); if (r->isEmpty()) { delete r; - return 0; + return nullptr; } d->relations.append(r); return r; } KDbQueryColumnInfo::List* KDbQuerySchema::autoIncrementFields() const { if (!d->autoincFields) { d->autoincFields = new KDbQueryColumnInfo::List(); } KDbTableSchema *mt = masterTable(); if (!mt) { kdbWarning() << "no master table!"; return d->autoincFields; } if (d->autoincFields->isEmpty()) {//no cache KDbQueryColumnInfo::Vector fexp = fieldsExpanded(); for (int i = 0; i < fexp.count(); i++) { KDbQueryColumnInfo *ci = fexp[i]; if (ci->field()->table() == mt && ci->field()->isAutoIncrement()) { d->autoincFields->append(ci); } } } return d->autoincFields; } // static KDbEscapedString KDbQuerySchema::sqlColumnsList(const KDbQueryColumnInfo::List& infolist, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) { KDbEscapedString result; result.reserve(256); bool start = true; foreach(KDbQueryColumnInfo* ci, infolist) { if (!start) result += ","; else start = false; result += escapeIdentifier(ci->field()->name(), conn, escapingType); } return result; } KDbEscapedString KDbQuerySchema::autoIncrementSQLFieldsList(KDbConnection *conn) const { // QWeakPointer driverWeakPointer // = DriverManagerInternal::self()->driverWeakPointer(*conn->driver()); if ( /*d->lastUsedDriverForAutoIncrementSQLFieldsList != driverWeakPointer ||*/ d->autoIncrementSQLFieldsList.isEmpty()) { d->autoIncrementSQLFieldsList = KDbQuerySchema::sqlColumnsList(*autoIncrementFields(), conn); //d->lastUsedDriverForAutoIncrementSQLFieldsList = driverWeakPointer; } return d->autoIncrementSQLFieldsList; } static void setResult(const KDbParseInfoInternal &parseInfo, QString *errorMessage, QString *errorDescription) { if (errorMessage) { *errorMessage = parseInfo.errorMessage(); } if (errorDescription) { *errorDescription = parseInfo.errorDescription(); } } bool KDbQuerySchema::setWhereExpression(const KDbExpression &expr, QString *errorMessage, QString *errorDescription) { KDbExpression newWhereExpr = expr.clone(); KDbParseInfoInternal parseInfo(this); QString tempErrorMessage; QString tempErrorDescription; QString *errorMessagePointer = errorMessage ? errorMessage : &tempErrorMessage; QString *errorDescriptionPointer = errorDescription ? errorDescription : &tempErrorDescription; if (!newWhereExpr.validate(&parseInfo)) { setResult(parseInfo, errorMessagePointer, errorDescription); kdbWarning() << "message=" << *errorMessagePointer << "description=" << *errorDescriptionPointer; kdbWarning() << newWhereExpr; d->whereExpr = KDbExpression(); return false; } errorMessagePointer->clear(); errorDescriptionPointer->clear(); Private::setWhereExpressionInternal(this, newWhereExpr); return true; } bool KDbQuerySchema::addToWhereExpression(KDbField *field, const QVariant &value, KDbToken relation, QString *errorMessage, QString *errorDescription) { KDbToken token; if (value.isNull()) { token = KDbToken::SQL_NULL; } else { const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isIntegerType(type)) { token = KDbToken::INTEGER_CONST; } else if (KDbField::isFPNumericType(type)) { token = KDbToken::REAL_CONST; } else { token = KDbToken::CHARACTER_STRING_LITERAL; } //! @todo date, time } KDbBinaryExpression newExpr( KDbConstExpression(token, value), relation, KDbVariableExpression((field->table() ? (field->table()->name() + QLatin1Char('.')) : QString()) + field->name()) ); const KDbExpression origWhereExpr = d->whereExpr; if (!d->whereExpr.isNull()) { newExpr = KDbBinaryExpression( d->whereExpr, KDbToken::AND, newExpr ); } const bool result = setWhereExpression(newExpr, errorMessage, errorDescription); if (!result) { // revert, setWhereExpression() cleared it d->whereExpr = origWhereExpr; } return result; } /* void KDbQuerySchema::addToWhereExpression(KDbField *field, const QVariant& value) switch (value.type()) { case Int: case UInt: case Bool: case LongLong: case ULongLong: token = INTEGER_CONST; break; case Double: token = REAL_CONST; break; default: token = CHARACTER_STRING_LITERAL; } //! @todo date, time */ KDbExpression KDbQuerySchema::whereExpression() const { return d->whereExpr; } void KDbQuerySchema::setOrderByColumnList(const KDbOrderByColumnList& list) { delete d->orderByColumnList; - d->orderByColumnList = new KDbOrderByColumnList(list, 0, 0); + d->orderByColumnList = new KDbOrderByColumnList(list, nullptr, nullptr); // all field names should be found, exit otherwise ..........? } KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() { return d->orderByColumnList; } const KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() const { return d->orderByColumnList; } QList KDbQuerySchema::parameters() const { if (whereExpression().isNull()) return QList(); QList params; whereExpression().getQueryParameters(¶ms); return params; } bool KDbQuerySchema::validate(QString *errorMessage, QString *errorDescription) { KDbParseInfoInternal parseInfo(this); foreach(KDbField* f, *fields()) { if (f->isExpression()) { if (!f->expression().validate(&parseInfo)) { setResult(parseInfo, errorMessage, errorDescription); return false; } } } if (!whereExpression().validate(&parseInfo)) { setResult(parseInfo, errorMessage, errorDescription); return false; } return true; } diff --git a/src/KDbQuerySchema.h b/src/KDbQuerySchema.h index c94c13c8..1e388f0c 100644 --- a/src/KDbQuerySchema.h +++ b/src/KDbQuerySchema.h @@ -1,745 +1,745 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_QUERYSCHEMA_H #define KDB_QUERYSCHEMA_H #include #include #include "KDbFieldList.h" #include "KDbObject.h" #include "KDbQueryColumnInfo.h" #include "KDbToken.h" class KDbConnection; class KDbTableSchema; class KDbRelationship; class KDbQueryAsterisk; class KDbQuerySchemaParameter; class KDbOrderByColumn; class KDbOrderByColumnList; //! @short KDbQuerySchema provides information about database query /*! The query that can be executed using KDb-compatible SQL database engine or used as an introspection tool. KDb parser builds KDbQuerySchema objects by parsing SQL statements. */ class KDB_EXPORT KDbQuerySchema : public KDbFieldList, public KDbObject { public: /*! Creates empty query object (without columns). */ KDbQuerySchema(); /*! Creates query schema object that is equivalent to "SELECT * FROM table" sql command. Schema of @a table is used to contruct this query -- it is defined by just adding all the fields to the query in natural order. To avoid problems (e.g. with fields added outside of Kexi using ALTER TABLE) we do not use "all-tables query asterisk" (see KDbQueryAsterisk) item to achieve this effect. Properties such as the name and caption of the query are inherited from table schema. We consider that query schema based on @a table is not (a least yet) stored - in a system table, so query connection is set to NULL - (even if @a tableSchema's connection is not NULL). + in a system table, so query connection is set to @c nullptr + (even if @a tableSchema's connection is not @c nullptr). Id of the created query is set to 0. */ explicit KDbQuerySchema(KDbTableSchema *tableSchema); /*! Copy constructor. Creates deep copy of @a querySchema. KDbQueryAsterisk objects are deeply copied while only pointers to KDbField objects are copied. */ KDbQuerySchema(const KDbQuerySchema& querySchema); - virtual ~KDbQuerySchema(); + ~KDbQuerySchema() override; /*! Inserts @a field to the columns list at @a position. Inserted field will not be owned by this KDbQuerySchema object, but by the corresponding KDbTableSchema. KDbQueryAsterisk can be also passed as @a field. See the KDbQueryAsterisk class description. @note After inserting a field, corresponding table will be automatically added to query's tables list if it is not present there (see tables()). KDbField must have its table assigned. The inserted field will be visible. Use insertInvisibleField(position, field) to add an invisible field. The field is not bound to any particular table within the query. */ bool insertField(int position, KDbField *field) override; /** * @overload bool insertField(int position, KDbField *field) * Inserts @a field to the columns list at @a position. * @a bindToTable is a table index within the query for which the field should be bound. * If @a bindToTable is -1, no particular table will be bound. * @see tableBoundToColumn(int columnPosition) */ bool insertField(int position, KDbField *field, int bindToTable); /** * @overload bool insertField(int position, KDbField *field) * Inserts @a field to the columns list at @a position. * In addition sets field's visibility to @c false. It will not be bound to any table in this query. * @since 3.1 */ bool insertInvisibleField(int position, KDbField *field); /** * @overload bool insertInvisibleField(int position, KDbField *field) * Inserts @a field to the columns list at @a position. * In addition sets field's visibility to @c false. * @a bindToTable is a table index within the query for which the field should be bound. * If @a bindToTable is -1, no particular table will be bound. * @see tableBoundToColumn(int columnPosition) * @since 3.1 */ bool insertInvisibleField(int position, KDbField *field, int bindToTable); /** * Appends @a field to the columns list. * The field will be visible. Use addInvisibleField(field) to add an invisible field. * The field is not bound to any particular table within the query. * @see insertField() */ bool addField(KDbField* field); /*! Appends @a field to the columns list. Also binds to a table at @a bindToTable position. * Use bindToTable==-1 if no table should be bound. * The field will be visible. Use addInvisibleField(field, bindToTable) to add * an invisible field. * @see insertField() * @see tableBoundToColumn(int columnPosition) */ bool addField(KDbField* field, int bindToTable); /** * @overload bool addField(KDbField* field) * Appends @a field to the columns list. * The field is not bound to any particular table within the query. * In addition sets field's visibility to @c false. It will not be bound to any table in this query. * @since 3.1 */ bool addInvisibleField(KDbField* field); /** * @overload bool addField(KDbField* field, int bindToTable) * Appends @a field to the columns list. Also binds to a table at @a bindToTable position. * In addition sets field's visibility to @c false. * @see tableBoundToColumn(int columnPosition) * @since 3.1 */ bool addInvisibleField(KDbField* field, int bindToTable); /*! Removes field from the columns list. Use with care. */ bool removeField(KDbField *field) override; /** * Appends a column built on top of @a expr expression. * This creates a new KDbField object and adds it to the query schema using addField(). */ bool addExpression(const KDbExpression& expr); /** * @overload bool addExpression(const KDbExpression& expr) * Appends a column built on top of @a expr expression. * In addition sets column's visibility to @c false. * @since 3.1 */ bool addInvisibleExpression(const KDbExpression& expr); /*! @return visibility flag for column at @a position. By default column is visible. */ bool isColumnVisible(int position) const; //! Sets visibility flag for column at @a position to @a visible. void setColumnVisible(int position, bool visible); /*! Appends @a asterisk at the and of columns list. */ bool addAsterisk(KDbQueryAsterisk *asterisk); /** * @overload bool addAsterisk(KDbQueryAsterisk *asterisk) * Appends @a asterisk at the and of columns list. * Sets the asterisk as invisible. * @since 3.1 */ bool addInvisibleAsterisk(KDbQueryAsterisk *asterisk); /*! Removes all columns and their aliases from the columns list, removes all tables and their aliases from the tables list within this query. - Sets master table information to NULL. + Sets master table information to @c nullptr. Does not destroy any objects though. Clears name and all other properties. @see KDbFieldList::clear() */ void clear() override; /*! If query was created using a connection, - returns this connection object, otherwise NULL. */ + returns this connection object, otherwise @c nullptr. */ KDbConnection* connection() const; /*! @return table that is master to this query. All potentially-editable columns within this query belong just to this table. - This method also can return NULL if there are no tables at all, + This method also can return @c nullptr if there are no tables at all, or if previously assigned master table schema has been removed with removeTable(). Every query that has at least one table defined, should have assigned a master table. If no master table is assigned explicitly, but only one table used in this query, a single table is returned here, even if there are table aliases, (e.g. "T" table is returned for "SELECT T1.A, T2.B FROM T T1, T T2" statement). */ KDbTableSchema* masterTable() const; /*! Sets master table of this query to @a table. This table should be also added to query's tables list - using addTable(). If @a table equals NULL, nothing is performed. + using addTable(). If @a table equals @c nullptr, nothing is performed. @see masterTable() */ void setMasterTable(KDbTableSchema *table); /*! @return list of tables used in a query. This also includes master table. @see masterTable() */ QList* tables() const; /*! Appends @a table schema as one of tables used in a query. If @a alias is not empty, it will be assigned to this table using setTableAlias(position, alias). */ void addTable(KDbTableSchema *table, const QString& alias = QString()); /*! Removes @a table schema from this query. This does not destroy @a table object but only takes it out of the list. If this table was master for the query, master table information is also invalidated. */ void removeTable(KDbTableSchema *table); /*! @return table with name @a tableName or 0 if this query has no such table. */ KDbTableSchema* table(const QString& tableName) const; /*! @return @c true if the query uses @a table. */ bool contains(KDbTableSchema *table) const; /*! Convenience function. @return table field by searching through all tables in this query. The field does not need to be included on the list of query columns. Similarly, query aliases are not taken into account. @a tableOrTableAndFieldName string may contain table name and field name with '.' character between them, e.g. "mytable.myfield". This is recommended way to avoid ambiguity. 0 is returned if the query has no such table defined of the table has no such field defined. If you do not provide a table name, the first field found is returned. KDbQuerySchema::table("mytable")->field("myfield") could be alternative for findTableField("mytable.myfield") but it can crash if "mytable" is not defined in the query. @see KDb::splitToTableAndFieldParts() */ KDbField* findTableField(const QString &tableOrTableAndFieldName) const; /*! @return alias of a column at @a position or null string If there is no alias for this column or if there is no such column within the query defined. If the column is an expression and has no alias defined, a new unique alias will be generated automatically on this call. */ QString columnAlias(int position) const; /*! @return number of column aliases */ int columnAliasesCount() const; /*! Provided for convenience. @return @c true if a column at @a position has non empty alias defined within the query. If there is no alias for this column, or if there is no such column in the query defined, @c false is returned. */ bool hasColumnAlias(int position) const; /*! Sets @a alias for a column at @a position, within the query. Passing empty string to @a alias clears alias for a given column. */ void setColumnAlias(int position, const QString& alias); /*! @return a table position (within FROM section), that is bound to column at @a columnPosition (within SELECT section). This information can be used to find if there is alias defined for a table that is referenced by a given column. For example, for "SELECT t2.id FROM table1 t1, table2 t2" query statement, columnBoundToTable(0) returns 1, what means that table at position 1 (within FROM section) is bound to column at position 0, so we can now call tableAlias(1) to see if we have used alias for this column (t2.id) or just a table name (table2.id). These checks are performed e.g. by KDbConnection::selectStatement() to construct a statement string maximally identical to originally defined query statement. -1 is returned if: - @a columnPosition is out of range (i.e. < 0 or >= fieldCount()) - a column at @a columnPosition is not bound to any table (i.e. no database field is used for this column, e.g. "1" constant for "SELECT 1 from table" query statement) */ int tableBoundToColumn(int columnPosition) const; /*! @return number of table aliases */ int tableAliasesCount() const; /*! @return alias of a table at @a position (within FROM section) or null string if there is no alias for this table or if there is no such table within the query defined. */ QString tableAlias(int position) const; /*! @return alias of a table @a tableName (within FROM section) or empty value if there is no alias for this table or if there is no such table within the query defined. */ QString tableAlias(const QString& tableName) const; /*! @return alias of a table @a tableName (within FROM section). If there is no alias for this table, its name is returned. Empty value is returned if there is no such table within the query defined. */ QString tableAliasOrName(const QString& tableName) const; /*! @return table position (within FROM section) that has attached alias @a name. If there is no such alias, -1 is returned. Only first table's position attached for this alias is returned. It is not especially bad, since aliases rarely can be duplicated, what leads to ambiguity. Duplicated aliases are only allowed for trivial queries that have no database fields used within their columns, e.g. "SELECT 1 from table1 t, table2 t" is ok but "SELECT t.id from table1 t, table2 t" is not. */ int tablePositionForAlias(const QString& name) const; /*! @return position (within the FROM section) of table @a tableName. -1 is returned if there's no such table declared in the FROM section. @see tablePositions() */ int tablePosition(const QString& tableName) const; /*! @return a list of all occurrences of table @a tableName (within the FROM section). E.g. for "SELECT * FROM table t, table t2" tablePositions("table") returns {0, 1} list. Empty list is returned if there's no table @a tableName used in the FROM section at all. @see tablePosition() */ QList tablePositions(const QString& tableName) const; /*! Provided for convenience. @return @c true if a table at @a position (within FROM section of the query) has non empty alias defined. If there is no alias for this table, or if there is no such table in the query defined, @c false is returned. */ bool hasTableAlias(int position) const; /*! @return column position that has defined alias @a name. If there is no such alias, -1 is returned. */ int columnPositionForAlias(const QString& name) const; /*! Sets @a alias for a table at @a position (within FROM section of the query). Passing empty sting to @a alias clears alias for a given table (only for specified @a position). */ void setTableAlias(int position, const QString& alias); /*! @return a list of relationships defined for this query */ QList* relationships() const; /*! Appends a new relationship defined by @a field1 and @a field2. Both fields should belong to two different tables of this query. This is convenience function useful for a typical cases. It automatically creates KDbRelationship object for this query. If one of the fields are primary keys, it will be detected and appropriate master-detail relation will be established. This functiuon does nothing if the arguments are invalid. */ KDbRelationship* addRelationship(KDbField *field1, KDbField *field2); /*! @return list of KDbQueryAsterisk objects defined for this query */ KDbField::List* asterisks() const; /*! @return field for @a identifier or @c nullptr if no field for this name was found within the query. fieldsExpanded() method is used to lookup expanded list of the query fields, so queries with asterisks are processed well. If a field has alias defined, name is not taken into account, but only its alias. If a field has no alias: - field's name is checked - field's table and field's name are checked in a form of "tablename.fieldname", so you can provide @a identifier in this form to avoid ambiguity. If there are more than one fields with the same name equal to @a identifier, first-found is returned (checking is performed from first to last query field). Structures needed to compute result of this method are cached, so only first usage costs o(n) - another usages cost o(1). Example: Let query be defined by "SELECT T.B AS X, T.* FROM T" statement and let T be table containing fields A, B, C. Expanded list of columns for the query is: T.B AS X, T.A, T.B, T.C. - Calling field("B") will return a pointer to third query column (not the first, because it is covered by "X" alias). Additionally, calling field("X") will return the same pointer. - Calling field("T.A") will return the same pointer as field("A"). This method is also a product of inheritance from KDbFieldList. */ const KDbField* field(const QString& identifier) const override; /** * @overload const KDbField* field(const QString& identifier) const */ KDbField* field(const QString& identifier) override; /** * An overloaded method KDbField* field(const QString& identifier) * where unexpanded list of fields is used to find a field. */ const KDbField* unexpandedField(const QString& identifier) const; /** * @overload const KDbField* unexpandedField(const QString& identifier) const */ KDbField* unexpandedField(const QString& identifier); - /*! @return field id or NULL if there is no such a field. */ + /*! @return field id or @c nullptr if there is no such a field. */ KDbField* field(int id) override; /*! @overload KDbField* field(int id) */ const KDbField* field(int id) const override; /*! Like KDbQuerySchema::field(const QString& name) but returns not only KDbField object for @a identifier but entire KDbQueryColumnInfo object. @a identifier can be: - a fieldname - an aliasname - a tablename.fieldname - a tablename.aliasname Note that if there are two occurrrences of the same name, only the first is accessible using this method. For instance, calling columnInfo("name") for "SELECT t1.name, t2.name FROM t1, t2" statement will only return the column related to t1.name and not t2.name, so you'll need to explicitly specify "t2.name" as the identifier to get the second column. */ KDbQueryColumnInfo* columnInfo(const QString& identifier, bool expanded = true) const; /*! Options used in fieldsExpanded() and visibleFieldsExpanded(). */ enum FieldsExpandedOptions { Default, //!< All fields are returned even if duplicated Unique, //!< Unique list of fields is returned WithInternalFields, //!< Like Default but internal fields (for lookup) are appended WithInternalFieldsAndRecordId //!< Like WithInternalFields but record ID (big int type) field //!< is appended after internal fields }; /*! @return fully expanded list of fields. KDbQuerySchema::fields() returns vector of fields used for the query columns, but in a case when there are asterisks defined for the query, it does not expand KDbQueryAsterisk objects to field lists but return every asterisk as-is. This could be inconvenient when you need just a fully expanded list of fields, so this method does the work for you. If @a options is Unique, each field is returned in the vector only once (first found field is selected). Note however, that the same field can be returned more than once if it has attached a different alias. For example, let t be TABLE( a, b ) and let query be defined by "SELECT *, a AS alfa FROM t" statement. Both fieldsExpanded(Default) and fieldsExpanded(Unique) will return [ a, b, a (alfa) ] list. On the other hand, for query defined by "SELECT *, a FROM t" statement, fieldsExpanded(Default) will return [ a, b, a ] list while fieldsExpanded(Unique) will return [ a, b ] list. If @a options is WithInternalFields or WithInternalFieldsAndRecordID, additional internal fields are also appended to the vector. If @a options is WithInternalFieldsAndRecordId, one fake BigInteger column is appended to make space for Record ID column used by KDbCursor implementations. For example, let city_id in TABLE persons(surname, city_id) reference cities.id in TABLE cities(id, name) and let query q be defined by "SELECT * FROM persons" statement. We want to display persons' city names instead of city_id's. To do this, cities.name has to be retrieved as well, so the following statement should be used: "SELECT * FROM persons, cities.name LEFT OUTER JOIN cities ON persons.city_id=cities.id". Thus, calling fieldsExpanded(WithInternalFieldsAndRecordId) will return 4 elements instead of 2: persons.surname, persons.city_id, cities.name, {ROWID}. The {ROWID} item is the placeholder used for fetching ROWID by KDb cursors. By default, all fields are returned in the vector even if there are multiple occurrences of one or more (options == Default). Note: You should assign the resulted vector in your space - it will be shared and implicity copied on any modification. This method's result is cached by KDbQuerySchema object. @todo js: UPDATE CACHE! */ inline KDbQueryColumnInfo::Vector fieldsExpanded( FieldsExpandedOptions options = Default) const { return fieldsExpandedInternal(options, false); } /*! Like fieldsExpanded() but returns only visible fields. */ inline KDbQueryColumnInfo::Vector visibleFieldsExpanded( FieldsExpandedOptions options = Default) const { return fieldsExpandedInternal(options, true); } /*! @return list of internal fields used for lookup columns. */ KDbQueryColumnInfo::Vector internalFields() const; /*! @return info for expanded of internal field at index @a index. The returned field can be either logical or internal (for lookup), the latter case is @c true if @a index >= fieldsExpanded().count(). Equivalent of KDbQuerySchema::fieldsExpanded(WithInternalFields).at(index). */ KDbQueryColumnInfo* expandedOrInternalField(int index) const; /*! Options used in columnsOrder(). */ enum ColumnsOrderOptions { UnexpandedList, //!< A map for unexpanded list is created UnexpandedListWithoutAsterisks, //!< A map for unexpanded list is created, with asterisks skipped ExpandedList //!< A map for expanded list is created }; /*! @return a hash for fast lookup of query columns' order. - If @a options is UnexpandedList, each KDbQueryColumnInfo pointer is mapped to the index within (unexpanded) list of fields, i.e. "*" or "table.*" asterisks are considered to be single items. - If @a options is UnexpandedListWithoutAsterisks, each KDbQueryColumnInfo pointer is mapped to the index within (unexpanded) list of columns that come from asterisks like "*" or "table.*" are not included in the map at all. - If @a options is ExpandedList (the default) this method provides is exactly opposite information compared to vector returned by fieldsExpanded(). This method's result is cached by the KDbQuerySchema object. Note: indices of internal fields (see internalFields()) are also returned here - in this case the index is counted as a sum of size(e) + i (where "e" is the list of expanded fields and i is the column index within internal fields list). This feature is used eg. at the end of KDbConnection::updateRecord() where need indices of fields (including internal) to update all the values in memory. Example use: let t be table (int id, name text, surname text) and q be query defined by a statement "select * from t". - columnsOrder(ExpandedList) will return the following map: KDbQueryColumnInfo(id)->0, KDbQueryColumnInfo(name)->1, KDbQueryColumnInfo(surname)->2. - columnsOrder(UnexpandedList) will return the following map: KDbQueryColumnInfo(id)->0, KDbQueryColumnInfo(name)->0, KDbQueryColumnInfo(surname)->0 because the column list is not expanded. This way you can use the returned index to get KDbField* pointer using field(int) method of KDbFieldList superclass. - columnsOrder(UnexpandedListWithoutAsterisks) will return the following map: KDbQueryColumnInfo(id)->0, */ QHash columnsOrder(ColumnsOrderOptions options = ExpandedList) const; /*! @return table describing order of primary key (PKEY) fields within the query. Indexing is performed against vector returned by fieldsExpanded(). It is usable for e.g. Conenction::updateRecord(), when we need to locate each primary key's field in a constant time. Returned vector is owned and cached by KDbQuerySchema object. When you assign it, it is implicity shared. Its size is equal to number of primary key fields defined for master table (masterTable()->primaryKey()->fieldCount()). Each element of the returned vector: - can belong to [0..fieldsExpanded().count()-1] if there is such primary key's field in the fieldsExpanded() list. - can be equal to -1 if there is no such primary key's field in the fieldsExpanded() list. If there are more than one primary key's field included in the query, only first-found column (oin the fieldsExpanded() list) for each pkey's field is included. Returns empty vector if there is no master table or no master table's pkey. @see example for pkeyFieldCount(). @todo js: UPDATE CACHE! */ QVector pkeyFieldsOrder() const; /*! @return number of master table's primary key fields included in this query. This method is useful to quickly check whether the vector returned by pkeyFieldsOrder() if filled completely. User e.g. in KDbConnection::updateRecord() to check if entire primary key information is specified. Examples: let table T has (ID1 INTEGER, ID2 INTEGER, A INTEGER) fields, and let (ID1, ID2) is T's primary key. -# The query defined by "SELECT * FROM T" statement contains all T's primary key's fields as T is the master table, and thus pkeyFieldCount() will return 2 (both primary key's fields are in the fieldsExpanded() list), and pkeyFieldsOrder() will return vector {0, 1}. -# The query defined by "SELECT A, ID2 FROM T" statement, and thus pkeyFieldCount() will return 1 (only one primary key's field is in the fieldsExpanded() list), and pkeyFieldsOrder() will return vector {-1, 1}, as second primary key's field is at position #1 and first field is not specified at all within the query. */ int pkeyFieldCount(); /*! @return a list of field infos for all auto-incremented fields from master table of this query. This result is cached for efficiency. fieldsExpanded() is used for that. */ KDbQueryColumnInfo::List* autoIncrementFields() const; /*! @return a preset statement (if any). */ KDbEscapedString statement() const; /*! Forces a raw SQL statement @a sql for the query. This means that no statement is composed * from KDbQuerySchema's content. */ void setStatement(const KDbEscapedString& sql); /*! @return a string that is a result of concatenating all column names for @a infolist, with "," between each one. This is usable e.g. as argument like "field1,field2" for "INSERT INTO (xxx) ..". The result of this method is effectively cached, and it is invalidated when set of fields changes (e.g. using clear() or addField()). This method is similar to KDbFieldList::sqlFieldsList() it just uses KDbQueryColumnInfo::List instead of KDbField::List. @a escapingType can be used to alter default escaping type. If @a conn is not provided for DriverEscaping, no escaping is performed. */ - static KDbEscapedString sqlColumnsList(const KDbQueryColumnInfo::List& infolist, KDbConnection *conn = 0, + static KDbEscapedString sqlColumnsList(const KDbQueryColumnInfo::List& infolist, KDbConnection *conn = nullptr, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping); /*! @return cached list of autoincrement fields created using sqlColumnsList() on a list returned by autoIncrementFields(). The field names are escaped using driver escaping. */ KDbEscapedString autoIncrementSQLFieldsList(KDbConnection *conn) const; /** * @brief Sets a WHERE expression @a exp. * * Previously set WHERE expression will be removed. A null expression * (KDbExpression()) can be passed to remove existing WHERE expresssion. * @return @c false if @a expr is not a valid WHERE expression. validate() is called * to check this. On failure the WHERE expression for this query is cleared. In this * case a string pointed by @a errorMessage (if provided) is set to a general error * message and a string pointed by @a errorDescription (if provided) is set to a * detailed description of the error. */ bool setWhereExpression(const KDbExpression &expr, QString *errorMessage = nullptr, QString *errorDescription = nullptr); /*! @return WHERE expression or 0 if this query has no WHERE expression */ KDbExpression whereExpression() const; /** * @brief Appends a part to WHERE expression. * * Simplifies creating of WHERE expression if used instead of setWhereExpression(). * @return @c false if the newly constructed WHERE expression is not valid. * validate() is called to check this. On failure the WHERE expression for this query * is left unchanged. In this case a string pointed by @a errorMessage (if provided) * is set to a general error message and a string pointed by @a errorDescription * (if provided) is set to a detailed description of the error. */ bool addToWhereExpression(KDbField *field, const QVariant &value, KDbToken relation = '=', QString *errorMessage = nullptr, QString *errorDescription = nullptr); /*! Sets a list of columns for ORDER BY section of the query. Each name on the list must be a field or alias present within the query and must not be covered by aliases. If one or more names cannot be found within the query, the method will have no effect. Any previous ORDER BY settings will be removed. Note that this information is cleared whenever you call methods that modify list of columns (KDbQueryColumnInfo), i.e. insertField(), addField(), removeField(), addExpression(), etc. (because KDbOrderByColumn items can point to a KDbQueryColumnInfo that's removed by these methods), so you should use setOrderByColumnList() method after the query is completely built. */ void setOrderByColumnList(const KDbOrderByColumnList& list); /*! @return a list of columns listed in ORDER BY section of the query. Read notes for @ref setOrderByColumnList(). */ KDbOrderByColumnList* orderByColumnList(); /*! @see orderByColumnList() */ const KDbOrderByColumnList* orderByColumnList() const; /*! @return query schema parameters. These are taked from the WHERE section (a tree of expression items). */ QList parameters() const; //! @return @c true if this query is valid /*! Detailed validation is performed in the same way as parsing of query statements * by the KDbParser. * Example :Let the query be "SELECT FROM WHERE ". * First each field from (@see fields()) is validated using * KDbField::expression().validate(). Then the (@see * whereExpression()) * is validated using KDbExpression::validate(). * * On error a string pointed by @a errorMessage (if provided) is set to a general * error message and a string pointed by @a errorDescription (if provided) is set to a * detailed description of the error. */ //! @todo add tests bool validate(QString *errorMessage = nullptr, QString *errorDescription = nullptr); class Private; // Protected not private because of the parser protected: //! @internal associates @a conn with this query so it's possible to find tables explicit KDbQuerySchema(KDbConnection *conn); void init(); void computeFieldsExpanded() const; //! Used by fieldsExpanded(FieldsExpandedOptions) //! and visibleFieldsExpanded(FieldsExpandedOptions options). KDbQueryColumnInfo::Vector fieldsExpandedInternal(FieldsExpandedOptions options, bool onlyVisible) const; /** Internal method used by all insert*Field methods. * The new column can also be explicitly bound to a specific position on tables list. * @a bindToTable is a table index within the query for which the field should be bound. * If @a bindToTable is -1, no particular table will be bound. * @see tableBoundToColumn(int columnPosition) */ bool insertFieldInternal(int position, KDbField *field, int bindToTable, bool visible); /** * Internal method used by add*Asterisk() methods. * Appends @a asterisk at the and of columns list, sets visibility. */ bool addAsteriskInternal(KDbQueryAsterisk *asterisk, bool visible); /** Internal method used by all add*Expression methods. * Appends expression @a expr at the and of columns list, sets visibility. */ bool addExpressionInternal(const KDbExpression& expr, bool visible); /** Internal method used by a query parser. */ void setWhereExpressionInternal(const KDbExpression &expr); Private * const d; }; //! Sends query schema information @a query to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbQuerySchema& query); #endif diff --git a/src/KDbQuerySchema_p.cpp b/src/KDbQuerySchema_p.cpp index 20cc55d0..0eed1ef8 100644 --- a/src/KDbQuerySchema_p.cpp +++ b/src/KDbQuerySchema_p.cpp @@ -1,219 +1,219 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbQuerySchema_p.h" #include "KDbOrderByColumn.h" KDbQuerySchema::Private::Private(KDbQuerySchema* q, Private* copy) : query(q) - , masterTable(0) - , fakeRecordIdField(0) - , fakeRecordIdCol(0) - , conn(0) + , masterTable(nullptr) + , fakeRecordIdField(nullptr) + , fakeRecordIdCol(nullptr) + , conn(nullptr) , maxIndexWithAlias(-1) , visibility(64) - , fieldsExpanded(0) - , visibleFieldsExpanded(0) - , internalFields(0) - , fieldsExpandedWithInternalAndRecordId(0) - , visibleFieldsExpandedWithInternalAndRecordId(0) - , fieldsExpandedWithInternal(0) - , visibleFieldsExpandedWithInternal(0) - , orderByColumnList(0) - , autoincFields(0) - , columnsOrder(0) - , columnsOrderWithoutAsterisks(0) - , columnsOrderExpanded(0) - , pkeyFieldsOrder(0) + , fieldsExpanded(nullptr) + , visibleFieldsExpanded(nullptr) + , internalFields(nullptr) + , fieldsExpandedWithInternalAndRecordId(nullptr) + , visibleFieldsExpandedWithInternalAndRecordId(nullptr) + , fieldsExpandedWithInternal(nullptr) + , visibleFieldsExpandedWithInternal(nullptr) + , orderByColumnList(nullptr) + , autoincFields(nullptr) + , columnsOrder(nullptr) + , columnsOrderWithoutAsterisks(nullptr) + , columnsOrderExpanded(nullptr) + , pkeyFieldsOrder(nullptr) , pkeyFieldCount(0) , tablesBoundToColumns(64, -1) // will be resized if needed - , ownedVisibleColumns(0) + , ownedVisibleColumns(nullptr) , regenerateExprAliases(false) { visibility.fill(false); if (copy) { // deep copy *this = *copy; // - fieldsExpanded = 0; - visibleFieldsExpanded = 0; - internalFields = 0; - columnsOrder = 0; - columnsOrderWithoutAsterisks = 0; - columnsOrderExpanded = 0; - orderByColumnList = 0; - autoincFields = 0; + fieldsExpanded = nullptr; + visibleFieldsExpanded = nullptr; + internalFields = nullptr; + columnsOrder = nullptr; + columnsOrderWithoutAsterisks = nullptr; + columnsOrderExpanded = nullptr; + orderByColumnList = nullptr; + autoincFields = nullptr; autoIncrementSQLFieldsList.clear(); columnInfosByNameExpanded.clear(); columnInfosByName.clear(); - ownedVisibleColumns = 0; - fieldsExpandedWithInternalAndRecordId = 0; - visibleFieldsExpandedWithInternalAndRecordId = 0; - fieldsExpandedWithInternal = 0; - visibleFieldsExpandedWithInternal = 0; - pkeyFieldsOrder = 0; - fakeRecordIdCol = 0; - fakeRecordIdField = 0; - conn = 0; - ownedVisibleColumns = 0; + ownedVisibleColumns = nullptr; + fieldsExpandedWithInternalAndRecordId = nullptr; + visibleFieldsExpandedWithInternalAndRecordId = nullptr; + fieldsExpandedWithInternal = nullptr; + visibleFieldsExpandedWithInternal = nullptr; + pkeyFieldsOrder = nullptr; + fakeRecordIdCol = nullptr; + fakeRecordIdField = nullptr; + conn = nullptr; + ownedVisibleColumns = nullptr; // if (!copy->whereExpr.isNull()) { whereExpr = copy->whereExpr.clone(); } // "*this = *copy" causes copying pointers; pull of them without destroying, // will be deep-copied in the KDbQuerySchema ctor. asterisks.setAutoDelete(false); asterisks.clear(); asterisks.setAutoDelete(true); } else { orderByColumnList = new KDbOrderByColumnList; } } KDbQuerySchema::Private::~Private() { delete orderByColumnList; delete autoincFields; delete columnsOrder; delete columnsOrderWithoutAsterisks; delete columnsOrderExpanded; delete pkeyFieldsOrder; delete fakeRecordIdCol; delete fakeRecordIdField; delete ownedVisibleColumns; if (fieldsExpanded) { qDeleteAll(*fieldsExpanded); delete fieldsExpanded; } if (internalFields) { qDeleteAll(*internalFields); delete internalFields; } delete fieldsExpandedWithInternalAndRecordId; delete visibleFieldsExpandedWithInternalAndRecordId; delete fieldsExpandedWithInternal; delete visibleFieldsExpandedWithInternal; } //static KDbQuerySchema* KDbQuerySchema::Private::createQuery(KDbConnection *conn) { return new KDbQuerySchema(conn); } void KDbQuerySchema::Private::clear() { columnAliases.clear(); tableAliases.clear(); asterisks.clear(); relations.clear(); - masterTable = 0; + masterTable = nullptr; tables.clear(); clearCachedData(); delete pkeyFieldsOrder; - pkeyFieldsOrder = 0; + pkeyFieldsOrder = nullptr; visibility.fill(false); tablesBoundToColumns = QVector(64, -1); // will be resized if needed tablePositionsForAliases.clear(); columnPositionsForAliases.clear(); } void KDbQuerySchema::Private::clearCachedData() { if (orderByColumnList) { orderByColumnList->clear(); } if (fieldsExpanded) { delete columnsOrder; - columnsOrder = 0; + columnsOrder = nullptr; delete columnsOrderWithoutAsterisks; - columnsOrderWithoutAsterisks = 0; + columnsOrderWithoutAsterisks = nullptr; delete columnsOrderExpanded; - columnsOrderExpanded = 0; + columnsOrderExpanded = nullptr; delete autoincFields; - autoincFields = 0; + autoincFields = nullptr; autoIncrementSQLFieldsList.clear(); columnInfosByNameExpanded.clear(); columnInfosByName.clear(); delete ownedVisibleColumns; - ownedVisibleColumns = 0; + ownedVisibleColumns = nullptr; qDeleteAll(*fieldsExpanded); delete fieldsExpanded; - fieldsExpanded = 0; + fieldsExpanded = nullptr; delete visibleFieldsExpanded; // NO qDeleteAll, items not owned - visibleFieldsExpanded = 0; + visibleFieldsExpanded = nullptr; if (internalFields) { qDeleteAll(*internalFields); delete internalFields; - internalFields = 0; + internalFields = nullptr; } delete fieldsExpandedWithInternalAndRecordId; - fieldsExpandedWithInternalAndRecordId = 0; + fieldsExpandedWithInternalAndRecordId = nullptr; delete visibleFieldsExpandedWithInternalAndRecordId; - visibleFieldsExpandedWithInternalAndRecordId = 0; + visibleFieldsExpandedWithInternalAndRecordId = nullptr; delete fieldsExpandedWithInternal; - fieldsExpandedWithInternal = 0; + fieldsExpandedWithInternal = nullptr; delete visibleFieldsExpandedWithInternal; - visibleFieldsExpandedWithInternal = 0; + visibleFieldsExpandedWithInternal = nullptr; } } void KDbQuerySchema::Private::setColumnAlias(int position, const QString& alias) { if (alias.isEmpty()) { columnAliases.remove(position); maxIndexWithAlias = -1; } else { setColumnAliasInternal(position, alias); } } void KDbQuerySchema::Private::tryRegenerateExprAliases() { if (!regenerateExprAliases) return; //regenerate missing aliases for experessions int colNum = 0; //used to generate a name QString columnAlias; int p = -1; foreach(KDbField* f, *query->fields()) { p++; if (f->isExpression() && columnAliases.value(p).isEmpty()) { //missing do { //find 1st unused colNum++; columnAlias = tr("expr%1", "short for 'expression' word, it will expand " "to 'expr1', 'expr2', etc. Please use ONLY latin " "letters and DON'T use '.'").arg(colNum); columnAlias = KDb::stringToIdentifier(columnAlias); // sanity fix, translators make mistakes! } while (-1 != tablePositionForAlias(columnAlias)); setColumnAliasInternal(p, columnAlias); } } regenerateExprAliases = false; } void KDbQuerySchema::Private::setColumnAliasInternal(int position, const QString& alias) { columnAliases.insert(position, alias.toLower()); columnPositionsForAliases.insert(alias.toLower(), position); maxIndexWithAlias = qMax(maxIndexWithAlias, position); } diff --git a/src/KDbQuerySchema_p.h b/src/KDbQuerySchema_p.h index 684d9b1e..2cf863e4 100644 --- a/src/KDbQuerySchema_p.h +++ b/src/KDbQuerySchema_p.h @@ -1,225 +1,225 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_QUERYSCHEMA_P_H #define KDB_QUERYSCHEMA_P_H #include "KDbDriver.h" #include "KDbExpression.h" #include "KDbQuerySchema.h" #include #include class KDbConnection; //! @internal class Q_DECL_HIDDEN KDbQuerySchema::Private { Q_DECLARE_TR_FUNCTIONS(KDbQuerySchema) public: - explicit Private(KDbQuerySchema* q, Private* copy = 0); + explicit Private(KDbQuerySchema* q, Private* copy = nullptr); ~Private(); //! @return a new query that's associated with @a conn. Used internally, e.g. by the parser. //! Uses an internal KDbQuerySchema(KDbConnection*) ctor. static KDbQuerySchema* createQuery(KDbConnection *conn); void clear(); void clearCachedData(); void setColumnAlias(int position, const QString& alias); inline void setTableAlias(int position, const QString& alias) { tableAliases.insert(position, alias.toLower()); tablePositionsForAliases.insert(alias.toLower(), position); } inline int columnAliasesCount() { tryRegenerateExprAliases(); return columnAliases.count(); } inline QString columnAlias(int position) { tryRegenerateExprAliases(); return columnAliases.value(position); } inline bool hasColumnAlias(int position) { tryRegenerateExprAliases(); return columnAliases.contains(position); } inline void removeTablePositionForAlias(const QString& alias) { tablePositionsForAliases.remove(alias.toLower()); } inline int tablePositionForAlias(const QString& alias) const { return tablePositionsForAliases.value(alias.toLower(), -1); } inline int columnPositionForAlias(const QString& alias) const { return columnPositionsForAliases.value(alias.toLower(), -1); } //! Accessor for buildSelectQuery() static void setWhereExpressionInternal(KDbQuerySchema *query, const KDbExpression &expr) { query->d->whereExpr = expr; } KDbQuerySchema *query; - /*! Master table of the query. (may be NULL) + /*! Master table of the query. Can be @c nullptr. Any data modifications can be performed if we know master table. If null, query's records cannot be modified. */ KDbTableSchema *masterTable; /*! List of tables used in this query */ QList tables; KDbField *fakeRecordIdField; //! used to mark a place for record Id KDbQueryColumnInfo *fakeRecordIdCol; //! used to mark a place for record Id //! Connection on which this query operates //! @todo use equivalent of QPointer KDbConnection* conn; protected: void tryRegenerateExprAliases(); void setColumnAliasInternal(int position, const QString& alias); /*! Used to mapping columns to its aliases for this query */ QHash columnAliases; /*! Collects table positions for aliases: used in tablePositionForAlias(). */ QHash tablePositionsForAliases; /*! Collects column positions for aliases: used in columnPositionForAlias(). */ QHash columnPositionsForAliases; public: /*! Used to mapping tables to its aliases for this query */ QHash tableAliases; /*! Helper used with aliases */ int maxIndexWithAlias; /*! Used to store visibility flag for every field */ QBitArray visibility; /*! List of asterisks defined for this query */ KDbField::List asterisks; /*! Temporary field vector for using in fieldsExpanded() */ KDbQueryColumnInfo::Vector *fieldsExpanded; /*! Like fieldsExpanded but only visible column infos; infos are not owned. */ KDbQueryColumnInfo::Vector *visibleFieldsExpanded; /*! Temporary field vector containing internal fields used for lookup columns. */ KDbQueryColumnInfo::Vector *internalFields; /*! Temporary, used to cache sum of expanded fields and internal fields (+record Id) used for lookup columns. Contains not auto-deleted items.*/ KDbQueryColumnInfo::Vector *fieldsExpandedWithInternalAndRecordId; /*! Like fieldsExpandedWithInternalAndRecordId but only contains visible column infos; infos are not owned.*/ KDbQueryColumnInfo::Vector *visibleFieldsExpandedWithInternalAndRecordId; /*! Temporary, used to cache sum of expanded fields and internal fields used for lookup columns. Contains not auto-deleted items.*/ KDbQueryColumnInfo::Vector *fieldsExpandedWithInternal; /*! Like fieldsExpandedWithInternal but only contains visible column infos; infos are not owned.*/ KDbQueryColumnInfo::Vector *visibleFieldsExpandedWithInternal; /*! A list of fields for ORDER BY section. @see KDbQuerySchema::orderByColumnList(). */ KDbOrderByColumnList* orderByColumnList; /*! A cache for autoIncrementFields(). */ KDbQueryColumnInfo::List *autoincFields; /*! A cache for autoIncrementSQLFieldsList(). */ KDbEscapedString autoIncrementSQLFieldsList; QWeakPointer lastUsedDriverForAutoIncrementSQLFieldsList; /*! A hash for fast lookup of query columns' order (unexpanded version). */ QHash *columnsOrder; /*! A hash for fast lookup of query columns' order (unexpanded version without asterisks). */ QHash *columnsOrderWithoutAsterisks; /*! A hash for fast lookup of query columns' order. This is exactly opposite information compared to vector returned by fieldsExpanded() */ QHash *columnsOrderExpanded; /*! order of PKEY fields (e.g. for updateRecord() ) */ QVector *pkeyFieldsOrder; /*! number of PKEY fields within the query */ int pkeyFieldCount; /*! Forced (predefined) raw SQL statement */ KDbEscapedString sql; /*! Relationships defined for this query. */ QList relations; /*! Information about columns bound to tables. Used if table is used in FROM section more than once (using table aliases). This list is updated by insertField(int position, KDbField *field, int bindToTable, bool visible), using bindToTable parameter. Example: for this statement: SELECT t1.a, othertable.x, t2.b FROM table t1, table t2, othertable; tablesBoundToColumns list looks like this: [ 0, -1, 1 ] - first column is bound to table 0 "t1" - second coulmn is not specially bound (othertable.x isn't ambiguous) - third column is bound to table 1 "t2" */ QVector tablesBoundToColumns; /*! WHERE expression */ KDbExpression whereExpr; QHash columnInfosByNameExpanded; QHash columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped //! field schemas created for multiple joined columns like a||' '||b||' '||c KDbField::List *ownedVisibleColumns; /*! Set by insertField(): true, if aliases for expression columns should be generated on next columnAlias() call. */ bool regenerateExprAliases; }; //! @return identifier string @a name escaped using @a conn connection and type @a escapingType QString escapeIdentifier(const QString& name, KDbConnection *conn, KDb::IdentifierEscapingType escapingType); #endif diff --git a/src/KDbRecordData.cpp b/src/KDbRecordData.cpp index 907330b6..3bfbbd37 100644 --- a/src/KDbRecordData.cpp +++ b/src/KDbRecordData.cpp @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #include "KDbRecordData.h" #include "KDbGlobal.h" #include "KDbUtils.h" #include "kdb_debug.h" QVariant KDbRecordData::s_null; QDebug operator<<(QDebug dbg, const KDbRecordData& data) { if (data.isEmpty()) { dbg.nospace() << QLatin1String("EMPTY RECORD DATA"); } else { dbg.nospace() << "RECORD DATA (" << data.size() << " COLUMNS):"; for (int i = 0; i < data.size(); i++) { dbg.nospace() << " " << i << ":" << KDbUtils::squeezedValue(data[i]); } } return dbg.space(); } void KDbRecordData::clear() { if (m_numCols > 0) { for (int i = 0; i < m_numCols; i++) free(m_data[i]); free(m_data); - m_data = 0; + m_data = nullptr; m_numCols = 0; } } QList KDbRecordData::toList() const { QList list; list.reserve(m_numCols); for (int i = 0; i < m_numCols; ++i) { list.append(*m_data[i]); } return list; } diff --git a/src/KDbRecordData.h b/src/KDbRecordData.h index 4ffcd1bb..c97c1f32 100644 --- a/src/KDbRecordData.h +++ b/src/KDbRecordData.h @@ -1,166 +1,166 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #ifndef KDB_RECORDDATA_H #define KDB_RECORDDATA_H #include #include "kdb_export.h" //! @short Structure for storing single record with type information. //! @todo consider forking QVariant to a non-shared Variant class, with type information stored elsewhere. //! @todo Variant should have toQVariant() method //! @todo look if we can have zero-copy strategy for SQLite and other backends //! @todo look if we can use memory allocator for strings, etc. class KDB_EXPORT KDbRecordData { public: /*! Creates a new empty record. */ - inline KDbRecordData() : m_data(0), m_numCols(0) {} + inline KDbRecordData() : m_data(nullptr), m_numCols(0) {} /*! Creates a new record data with @a numCols columns. Values are initialized to null. */ inline explicit KDbRecordData(int numCols) { init(numCols); } inline ~KDbRecordData() { if (m_numCols > 0) { for (int i = 0; i < m_numCols; i++) delete m_data[i]; free(m_data); } } inline bool isEmpty() const { return m_numCols == 0; } inline int size() const { return m_numCols; } inline int count() const { return m_numCols; } /*! @return the value at position @a i. @a i must be a valid index. i.e. 0 <= i < size(). @see value(), operator[](). */ inline const QVariant& at(int i) const { if (!m_data[i]) return s_null; return *m_data[i]; } /*! @return the value at position @a i as a modifiable reference. @a i must be a valid index, i.e. 0 <= i < size(). @see at(), value(). */ inline QVariant& operator[](int i) { if (!m_data[i]) { return *(m_data[i] = new QVariant); } return *m_data[i]; } /*! @return true id value at position @a i is null. @a i must be a valid index, i.e. 0 <= i < size(). @see at(), value(). */ inline bool isNull(int i) const { return !m_data[i] || m_data[i]->isNull(); } /*! Overloaded function.*/ inline const QVariant& operator[](int i) const { if (!m_data[i]) return s_null; return *m_data[i]; } /*! @return the value at index position i in the vector. If the index @a i is out of bounds, the function returns a default-constructed value. If you are certain that i is within bounds, you can use at() instead, which is slightly faster. */ inline QVariant value(int i) const { if (!m_data || i < 0 || i >= m_numCols || !m_data[i]) return QVariant(); return *m_data[i]; } /*! @return the value at index position i in the vector. This is an overloaded function. If the index @a i is out of bounds, the function returns a @a defaultValue. If you are certain that i is within bounds, you can use at() instead, which is slightly faster. */ inline QVariant value(int i, const QVariant& defaultValue) const { if (!m_data || i < 0 || i >= m_numCols) return defaultValue; if (!m_data[i]) return QVariant(); return *m_data[i]; } /*! Sets existing column values to null, current number of columns is preserved. */ inline void clearValues() { for (int i = 0; i < m_numCols; i++) { delete m_data[i]; - m_data[i] = 0; + m_data[i] = nullptr; } } /*! Clears all columns, the record is set empty. */ void clear(); /*! Resize existing record to @a numCols. If @a numCols differ from size(), new with record with all values set to null is created. If @a numCols equals size() nothing is performed. */ inline void resize(int numCols) { if (m_numCols == numCols) return; else if (m_numCols < numCols) { // grow m_data = (QVariant**)realloc(m_data, numCols * sizeof(QVariant*)); memset(m_data + m_numCols, 0, (numCols - m_numCols) * sizeof(QVariant*)); m_numCols = numCols; } else { // shrink for (int i = numCols; i < m_numCols; i++) delete m_data[i]; m_data = (QVariant**)realloc(m_data, numCols * sizeof(QVariant*)); m_numCols = numCols; } } //! Converts this record to QList QList toList() const; private: Q_DISABLE_COPY(KDbRecordData) inline void init(int numCols) { m_numCols = numCols; if (m_numCols > 0) { m_data = (QVariant**)malloc(m_numCols * sizeof(QVariant*)); memset(m_data, 0, m_numCols * sizeof(QVariant*)); } else - m_data = 0; + m_data = nullptr; } QVariant **m_data; int m_numCols; static QVariant s_null; }; //! Sends information about record data @a data to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbRecordData& data); #endif diff --git a/src/KDbRecordEditBuffer.cpp b/src/KDbRecordEditBuffer.cpp index 29191583..663b588f 100644 --- a/src/KDbRecordEditBuffer.cpp +++ b/src/KDbRecordEditBuffer.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2003-2011 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbRecordEditBuffer.h" #include "KDb.h" #include "KDbQueryColumnInfo.h" #include "kdb_debug.h" KDbRecordEditBuffer::KDbRecordEditBuffer(bool dbAwareBuffer) - : m_simpleBuffer(dbAwareBuffer ? 0 : new SimpleMap()) - , m_simpleBufferIt(dbAwareBuffer ? 0 : new SimpleMap::ConstIterator()) - , m_dbBuffer(dbAwareBuffer ? new DbHash() : 0) - , m_dbBufferIt(dbAwareBuffer ? new DbHash::Iterator() : 0) - , m_defaultValuesDbBuffer(dbAwareBuffer ? new QMap() : 0) - , m_defaultValuesDbBufferIt(dbAwareBuffer ? new QMap::ConstIterator() : 0) + : m_simpleBuffer(dbAwareBuffer ? nullptr : new SimpleMap()) + , m_simpleBufferIt(dbAwareBuffer ? nullptr : new SimpleMap::ConstIterator()) + , m_dbBuffer(dbAwareBuffer ? new DbHash() : nullptr) + , m_dbBufferIt(dbAwareBuffer ? new DbHash::Iterator() : nullptr) + , m_defaultValuesDbBuffer(dbAwareBuffer ? new QMap() : nullptr) + , m_defaultValuesDbBufferIt(dbAwareBuffer ? new QMap::ConstIterator() : nullptr) { } KDbRecordEditBuffer::~KDbRecordEditBuffer() { delete m_simpleBuffer; delete m_simpleBufferIt; delete m_dbBuffer; delete m_dbBufferIt; delete m_defaultValuesDbBuffer; delete m_defaultValuesDbBufferIt; } bool KDbRecordEditBuffer::isDBAware() const { - return m_dbBuffer != 0; + return m_dbBuffer != nullptr; } const QVariant* KDbRecordEditBuffer::at(KDbQueryColumnInfo* ci, bool useDefaultValueIfPossible) const { Q_ASSERT(ci); if (!m_dbBuffer) { kdbWarning() << "not db-aware buffer!"; - return 0; + return nullptr; } *m_dbBufferIt = m_dbBuffer->find(ci); - QVariant* result = 0; + QVariant* result = nullptr; if (*m_dbBufferIt != m_dbBuffer->end()) result = &(*m_dbBufferIt).value(); if (useDefaultValueIfPossible && (!result || result->isNull()) && ci->field() && !ci->field()->defaultValue().isNull() && KDb::isDefaultValueAllowed(*ci->field()) && !hasDefaultValueAt(ci)) { //no buffered or stored value: try to get a default value declared in a field, so user can modify it if (!result) m_dbBuffer->insert(ci, ci->field()->defaultValue()); result = &(*m_dbBuffer)[ ci ]; m_defaultValuesDbBuffer->insert(ci, true); } return (const QVariant*)result; } const QVariant* KDbRecordEditBuffer::at(const KDbField &field) const { if (!m_simpleBuffer) { kdbWarning() << "this is db-aware buffer!"; - return 0; + return nullptr; } *m_simpleBufferIt = m_simpleBuffer->constFind(field.name()); if (*m_simpleBufferIt == m_simpleBuffer->constEnd()) - return 0; + return nullptr; return &(*m_simpleBufferIt).value(); } const QVariant* KDbRecordEditBuffer::at(const QString& fname) const { if (!m_simpleBuffer) { kdbWarning() << "this is db-aware buffer!"; - return 0; + return nullptr; } *m_simpleBufferIt = m_simpleBuffer->constFind(fname); if (*m_simpleBufferIt == m_simpleBuffer->constEnd()) - return 0; + return nullptr; return &(*m_simpleBufferIt).value(); } void KDbRecordEditBuffer::removeAt(const KDbQueryColumnInfo& ci) { if (!m_dbBuffer) { kdbWarning() << "not db-aware buffer!"; return; } m_dbBuffer->remove(const_cast(&ci)); // const_cast ok here, we won't modify ci } void KDbRecordEditBuffer::removeAt(const KDbField& field) { if (!m_simpleBuffer) { kdbWarning() << "this is db-aware buffer!"; return; } m_simpleBuffer->remove(field.name()); } void KDbRecordEditBuffer::removeAt(const QString& fname) { if (!m_simpleBuffer) { kdbWarning() << "this is db-aware buffer!"; return; } m_simpleBuffer->remove(fname); } void KDbRecordEditBuffer::clear() { if (m_dbBuffer) { m_dbBuffer->clear(); m_defaultValuesDbBuffer->clear(); } if (m_simpleBuffer) m_simpleBuffer->clear(); } bool KDbRecordEditBuffer::isEmpty() const { if (m_dbBuffer) return m_dbBuffer->isEmpty(); if (m_simpleBuffer) return m_simpleBuffer->isEmpty(); return true; } void KDbRecordEditBuffer::insert(KDbQueryColumnInfo *ci, const QVariant &val) { Q_ASSERT(ci); if (m_dbBuffer) { m_dbBuffer->insert(ci, val); m_defaultValuesDbBuffer->remove(ci); } } void KDbRecordEditBuffer::insert(const QString &fname, const QVariant &val) { if (m_simpleBuffer) { m_simpleBuffer->insert(fname, val); } } bool KDbRecordEditBuffer::hasDefaultValueAt(KDbQueryColumnInfo *ci) const { Q_ASSERT(ci); return m_defaultValuesDbBuffer->value(ci, false); } KDbRecordEditBuffer::SimpleMap KDbRecordEditBuffer::simpleBuffer() const { return *m_simpleBuffer; } KDbRecordEditBuffer::DbHash KDbRecordEditBuffer::dbBuffer() const { return *m_dbBuffer; } KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbRecordEditBuffer& buffer) { if (buffer.isDBAware()) { const KDbRecordEditBuffer::DbHash buf(buffer.dbBuffer()); dbg.space() << "RecordEditBuffer type=DB-AWARE," << buf.count() << "field(s):\n"; for (KDbRecordEditBuffer::DbHash::ConstIterator it = buf.constBegin(); it != buf.constEnd(); ++it) { dbg.nospace() << "* field name=" << qPrintable(it.key()->field()->name()) << " val=" << (it.value().isNull() ? QLatin1String("") : it.value().toString()) << (buffer.hasDefaultValueAt(it.key()) ? " DEFAULT\n" : "\n"); } return dbg.space(); } const KDbRecordEditBuffer::SimpleMap map(buffer.simpleBuffer()); dbg.space() << "RecordEditBuffer type=SIMPLE," << map.count() << "field(s):\n"; for (KDbRecordEditBuffer::SimpleMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) { dbg.nospace() << "* field name=" << qPrintable(it.key()) << " val=" << (it.value().isNull() ? QLatin1String("") : it.value().toString()) << "\n"; } return dbg.space(); } diff --git a/src/KDbRelationship.cpp b/src/KDbRelationship.cpp index e519915f..782490d3 100644 --- a/src/KDbRelationship.cpp +++ b/src/KDbRelationship.cpp @@ -1,207 +1,207 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbRelationship.h" #include "KDbIndexSchema.h" #include "KDbTableSchema.h" #include "KDbQuerySchema.h" #include "KDbDriver.h" #include "kdb_debug.h" KDbRelationship::KDbRelationship() - : m_masterIndex(0) - , m_detailsIndex(0) + : m_masterIndex(nullptr) + , m_detailsIndex(nullptr) , m_masterIndexOwned(false) , m_detailsIndexOwned(false) { } KDbRelationship::KDbRelationship(KDbIndexSchema* masterIndex, KDbIndexSchema* detailsIndex) - : m_masterIndex(0) - , m_detailsIndex(0) + : m_masterIndex(nullptr) + , m_detailsIndex(nullptr) , m_masterIndexOwned(false) , m_detailsIndexOwned(false) { (void)setIndices(masterIndex, detailsIndex); } KDbRelationship::KDbRelationship(KDbQuerySchema *query, KDbField *field1, KDbField *field2) - : m_masterIndex(0) - , m_detailsIndex(0) + : m_masterIndex(nullptr) + , m_detailsIndex(nullptr) , m_masterIndexOwned(false) , m_detailsIndexOwned(false) { createIndices(query, field1, field2); } KDbRelationship::~KDbRelationship() { if (m_masterIndexOwned) delete m_masterIndex; if (m_detailsIndexOwned) delete m_detailsIndex; } void KDbRelationship::createIndices(KDbQuerySchema *query, KDbField *field1, KDbField *field2) { if (!field1 || !field2 || !query) { kdbWarning() << "!masterField || !detailsField || !query"; return; } if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) { kdbWarning() << "relationship's fields cannot be asterisks"; return; } if (field1->table() == field2->table()) { kdbWarning() << "fields cannot belong to the same table"; return; } if (!query->contains(field1->table()) || !query->contains(field2->table())) { kdbWarning() << "fields do not belong to this query"; return; } //! @todo: check more things: -types //! @todo: find existing global db relationships - KDbField *masterField = 0, *detailsField = 0; + KDbField *masterField = nullptr, *detailsField = nullptr; bool p1 = field1->isPrimaryKey(), p2 = field2->isPrimaryKey(); if (p1 && p2) { //2 primary keys masterField = field1; m_masterIndex = masterField->table()->primaryKey(); detailsField = field2; m_detailsIndex = detailsField->table()->primaryKey(); } else if (!p1 && p2) { //foreign + primary: swap KDbField *tmp = field1; field1 = field2; field2 = tmp; p1 = !p1; p2 = !p2; } if (p1 && !p2) { //primary + foreign masterField = field1; m_masterIndex = masterField->table()->primaryKey(); detailsField = field2; //create foreign key //@todo: check if it already exists m_detailsIndex = new KDbIndexSchema; detailsField->table()->addIndex(m_detailsIndex); m_detailsIndexOwned = true; const bool ok = m_detailsIndex->addField(detailsField); Q_ASSERT(ok); m_detailsIndex->setForeignKey(true); } else if (!p1 && !p2) { masterField = field1; m_masterIndex = new KDbIndexSchema; masterField->table()->addIndex(m_masterIndex); m_masterIndexOwned = true; bool ok = m_masterIndex->addField(masterField); Q_ASSERT(ok); m_masterIndex->setForeignKey(true); detailsField = field2; m_detailsIndex = new KDbIndexSchema; detailsField->table()->addIndex(m_detailsIndex); m_detailsIndexOwned = true; ok = m_detailsIndex->addField(detailsField); Q_ASSERT(ok); m_detailsIndex->setForeignKey(true); } if (!m_masterIndex || !m_detailsIndex) return; //failed (void)setIndices(m_masterIndex, m_detailsIndex, false); } KDbTableSchema* KDbRelationship::masterTable() const { - return m_masterIndex ? m_masterIndex->table() : 0; + return m_masterIndex ? m_masterIndex->table() : nullptr; } KDbTableSchema* KDbRelationship::detailsTable() const { - return m_detailsIndex ? m_detailsIndex->table() : 0; + return m_detailsIndex ? m_detailsIndex->table() : nullptr; } bool KDbRelationship::setIndices(KDbIndexSchema* masterIndex, KDbIndexSchema* detailsIndex) { return setIndices(masterIndex, detailsIndex, true); } bool KDbRelationship::setIndices(KDbIndexSchema* masterIndex, KDbIndexSchema* detailsIndex, bool ownedByMaster) { - m_masterIndex = 0; - m_detailsIndex = 0; + m_masterIndex = nullptr; + m_detailsIndex = nullptr; m_pairs.clear(); if (!masterIndex || !detailsIndex || !masterIndex->table() || !detailsIndex->table() || masterIndex->table() == detailsIndex->table() || masterIndex->fieldCount() != detailsIndex->fieldCount()) { return false; } const KDbField::List* masterIndexFields = masterIndex->fields(); const KDbField::List* detailsIndexFields = detailsIndex->fields(); KDbField::ListIterator masterIt(masterIndexFields->constBegin()); KDbField::ListIterator detailsIt(detailsIndexFields->constBegin()); for (;masterIt != masterIndexFields->constEnd() && detailsIt != detailsIndexFields->constEnd(); ++masterIt, ++detailsIt) { KDbField *masterField = *masterIt; KDbField *detailsField = *detailsIt; const KDbField::Type masterType = masterField->type(); // cache: evaluating type of expressions can be expensive const KDbField::Type detailsType = detailsField->type(); if (masterType != detailsType && KDbField::isIntegerType(masterType) != KDbField::isIntegerType(detailsType) && KDbField::isTextType(masterType) != KDbField::isTextType(detailsType)) { kdbWarning() << "INDEX on" << masterIndex->table()->name() << ", INDEX on" << detailsIndex->table()->name() << ": !equal field types:" << KDbDriver::defaultSQLTypeName(masterType) << masterField->name() << "," << KDbDriver::defaultSQLTypeName(detailsType) << detailsField->name(); m_pairs.clear(); return false; } #if 0 //too STRICT! if ((masterField->isUnsigned() && !detailsField->isUnsigned()) || (!masterField->isUnsigned() && detailsField->isUnsigned())) { kdbWarning() << "KDbRelationship::setIndices(INDEX on '" << masterIndex->table()->name() << "',INDEX on " << detailsIndex->table()->name() << "): !equal signedness of field types: " << KDbDriver::defaultSQLTypeName(masterField->type()) << " " << masterField->name() << ", " << KDbDriver::defaultSQLTypeName(detailsField->type()) << " " << detailsField->name(); m_pairs.clear(); return; } #endif m_pairs.append(KDbField::Pair(masterField, detailsField)); } //ok: update information if (m_masterIndex) {//detach yourself m_masterIndex->detachRelationship(this); } if (m_detailsIndex) {//detach yourself m_detailsIndex->detachRelationship(this); } m_masterIndex = masterIndex; m_detailsIndex = detailsIndex; m_masterIndex->attachRelationship(this, ownedByMaster); m_detailsIndex->attachRelationship(this, ownedByMaster); return true; } diff --git a/src/KDbTableOrQuerySchema.cpp b/src/KDbTableOrQuerySchema.cpp index 4ce31e13..ddd6a5ed 100644 --- a/src/KDbTableOrQuerySchema.cpp +++ b/src/KDbTableOrQuerySchema.cpp @@ -1,200 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2004-2015 Jarosław Staniek Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (c) 1997 Matthias Kalle Dalheimer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTableOrQuerySchema.h" #include "KDbConnection.h" #include "KDbQuerySchema.h" #include "kdb_debug.h" class Q_DECL_HIDDEN KDbTableOrQuerySchema::Private { public: Private() {} //! The name is kept here because m_table and m_table can be 0 //! and we still want name() and acptionOrName() work. QByteArray name; KDbTableSchema* table; KDbQuerySchema* query; private: Q_DISABLE_COPY(Private) }; KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbConnection *conn, const QByteArray& name) : d(new Private) { d->name = name; d->table = conn->tableSchema(QLatin1String(name)); - d->query = d->table ? 0 : conn->querySchema(QLatin1String(name)); + d->query = d->table ? nullptr : conn->querySchema(QLatin1String(name)); if (!d->table && !d->query) { kdbWarning() << "tableOrQuery is neither table nor query!"; } } KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbConnection *conn, const QByteArray& name, bool table) : d(new Private) { d->name = name; - d->table = table ? conn->tableSchema(QLatin1String(name)) : 0; - d->query = table ? 0 : conn->querySchema(QLatin1String(name)); + d->table = table ? conn->tableSchema(QLatin1String(name)) : nullptr; + d->query = table ? nullptr : conn->querySchema(QLatin1String(name)); if (table && !d->table) { kdbWarning() << "no table specified!"; } if (!table && !d->query) { kdbWarning() << "no query specified!"; } } KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbFieldList *tableOrQuery) : d(new Private) { d->table = dynamic_cast(tableOrQuery); d->query = dynamic_cast(tableOrQuery); Q_ASSERT(tableOrQuery); if (!d->table && !d->query) { kdbWarning() << "tableOrQuery is neither table nor query!"; } } KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbConnection *conn, int id) : d(new Private) { d->table = conn->tableSchema(id); - d->query = d->table ? 0 : conn->querySchema(id); + d->query = d->table ? nullptr : conn->querySchema(id); if (!d->table && !d->query) { kdbWarning() << "no table or query found for id==" << id; } } KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbTableSchema* table) : d(new Private) { d->table = table; - d->query = 0; + d->query = nullptr; if (!d->table) { kdbWarning() << "no table specified!"; } } KDbTableOrQuerySchema::KDbTableOrQuerySchema(KDbQuerySchema* query) : d(new Private) { - d->table = 0; + d->table = nullptr; d->query = query; if (!d->query) { kdbWarning() << "no query specified!"; } } KDbTableOrQuerySchema::~KDbTableOrQuerySchema() { delete d; } int KDbTableOrQuerySchema::fieldCount() const { if (d->table) return d->table->fieldCount(); if (d->query) return d->query->fieldsExpanded().size(); return 0; } const KDbQueryColumnInfo::Vector KDbTableOrQuerySchema::columns(bool unique) { if (d->table) return d->table->query()->fieldsExpanded(unique ? KDbQuerySchema::Unique : KDbQuerySchema::Default); if (d->query) return d->query->fieldsExpanded(unique ? KDbQuerySchema::Unique : KDbQuerySchema::Default); kdbWarning() << "no query or table specified!"; return KDbQueryColumnInfo::Vector(); } QByteArray KDbTableOrQuerySchema::name() const { if (d->table) return d->table->name().toLatin1(); if (d->query) return d->query->name().toLatin1(); return d->name; } QString KDbTableOrQuerySchema::captionOrName() const { KDbObject *object = d->table ? static_cast(d->table) : static_cast(d->query); if (!object) return QLatin1String(d->name); return object->caption().isEmpty() ? object->name() : object->caption(); } KDbField* KDbTableOrQuerySchema::field(const QString& name) { if (d->table) return d->table->field(name); if (d->query) return d->query->field(name); - return 0; + return nullptr; } KDbQueryColumnInfo* KDbTableOrQuerySchema::columnInfo(const QString& name) { if (d->table) return d->table->query()->columnInfo(name); if (d->query) return d->query->columnInfo(name); - return 0; + return nullptr; } //! Sends information about table or query schema @a schema to debug output @a dbg. QDebug operator<<(QDebug dbg, const KDbTableOrQuerySchema& schema) { if (schema.table()) dbg.nospace() << *schema.table(); else if (schema.query()) dbg.nospace() << *schema.query(); return dbg.space(); } KDbConnection* KDbTableOrQuerySchema::connection() const { if (d->table) return d->table->connection(); else if (d->query) return d->query->connection(); - return 0; + return nullptr; } KDbQuerySchema* KDbTableOrQuerySchema::query() const { return d->query; } KDbTableSchema* KDbTableOrQuerySchema::table() const { return d->table; } diff --git a/src/KDbTableSchema.cpp b/src/KDbTableSchema.cpp index fd9a4c57..0ec5d687 100644 --- a/src/KDbTableSchema.cpp +++ b/src/KDbTableSchema.cpp @@ -1,450 +1,450 @@ /* This file is part of the KDE project Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTableSchema.h" #include "KDbDriver.h" #include "KDbConnection.h" #include "KDbLookupFieldSchema.h" #include "KDbQuerySchema.h" #include "kdb_debug.h" #include //! @internal class Q_DECL_HIDDEN KDbTableSchema::Private { public: Private(KDbTableSchema *t) : q(t) , anyNonPKField(nullptr) , conn(nullptr) , pkey(nullptr) , query(nullptr) { } ~Private() { clearLookupFields(); qDeleteAll(indices); delete query; } void clearLookupFields() { qDeleteAll(lookupFields); lookupFields.clear(); } void addIndex(KDbIndexSchema *index) { indices.append(index); index->setTable(q); } KDbTableSchema * const q; KDbField *anyNonPKField; QHash lookupFields; QVector lookupFieldsList; QList indices; //! @todo IMPORTANT: use something like QPointer conn KDbConnection *conn; KDbIndexSchema *pkey; KDbQuerySchema *query; //!< cached query schema that is defined by "select * from " private: Q_DISABLE_COPY(Private) }; //------------------------------------- KDbTableSchema::KDbTableSchema(const QString& name) : KDbFieldList(true) , KDbObject(KDb::TableObjectType) , d(new Private(this)) { setName(name); - init(0); + init(nullptr); } KDbTableSchema::KDbTableSchema(const KDbObject& other) : KDbFieldList(true) , KDbObject(other) , d(new Private(this)) { - init(0); + init(nullptr); } KDbTableSchema::KDbTableSchema() : KDbFieldList(true) , KDbObject(KDb::TableObjectType) , d(new Private(this)) { - init(0); + init(nullptr); } KDbTableSchema::KDbTableSchema(const KDbTableSchema& ts, bool copyId) : KDbFieldList(static_cast(ts)) , KDbObject(static_cast(ts)) , d(new Private(this)) { init(ts, copyId); } KDbTableSchema::KDbTableSchema(const KDbTableSchema& ts, int id) : KDbFieldList(static_cast(ts)) , KDbObject(static_cast(ts)) , d(new Private(this)) { init(ts, false); setId(id); } // used by KDbConnection KDbTableSchema::KDbTableSchema(KDbConnection *conn, const QString & name) : KDbFieldList(true) , KDbObject(KDb::TableObjectType) , d(new Private(this)) { Q_ASSERT(conn); setName(name); init(conn); } KDbTableSchema::~KDbTableSchema() { if (d->conn) { d->conn->removeMe(this); } delete d; } void KDbTableSchema::init(KDbConnection* conn) { d->conn = conn; d->pkey = new KDbIndexSchema; d->addIndex(d->pkey); } void KDbTableSchema::init(const KDbTableSchema& ts, bool copyId) { d->conn = ts.connection(); setName(ts.name()); d->pkey = nullptr; //will be copied if (!copyId) setId(-1); //deep copy all members foreach(KDbIndexSchema* otherIdx, *ts.indices()) { // fields from _this_ table will be assigned to the index KDbIndexSchema *idx = copyIndexFrom(*otherIdx); if (idx->isPrimaryKey()) {//assign pkey d->pkey = idx; } } KDbField::ListIterator tsIter(ts.fieldsIterator()); KDbField::ListIterator iter(fieldsIterator()); for (; iter != fieldsIteratorConstEnd(); ++tsIter, ++iter) { const KDbLookupFieldSchema *lookup = ts.lookupFieldSchema(**tsIter); if (lookup) { d->lookupFields.insert(*iter, new KDbLookupFieldSchema(*lookup)); } } } KDbIndexSchema* KDbTableSchema::primaryKey() { return d->pkey; } const KDbIndexSchema* KDbTableSchema::primaryKey() const { return d->pkey; } const QList::ConstIterator KDbTableSchema::indicesIterator() const { return QList::ConstIterator (d->indices.constBegin()); } const QList* KDbTableSchema::indices() const { return &d->indices; } bool KDbTableSchema::addIndex(KDbIndexSchema *index) { if (index && !d->indices.contains(index)) { d->addIndex(index); return true; } return false; } bool KDbTableSchema::removeIndex(KDbIndexSchema *index) { if (index) { d->indices.removeOne(index); return true; } return false; } KDbIndexSchema* KDbTableSchema::copyIndexFrom(const KDbIndexSchema& index) { KDbIndexSchema *newIndex = new KDbIndexSchema(index, this); addIndex(newIndex); return newIndex; } bool KDbTableSchema::isInternal() const { return dynamic_cast(this); } void KDbTableSchema::setPrimaryKey(KDbIndexSchema *pkey) { if (pkey && !d->indices.contains(pkey)) { kdbWarning() << *pkey << "index can't be made primary key because it does not belong " "to table schema" << name(); return; } if (d->pkey && d->pkey != pkey) { if (d->pkey->fieldCount() == 0) {//this is empty key, probably default - remove it d->indices.removeOne(d->pkey); delete d->pkey; } else { d->pkey->setPrimaryKey(false); //there can be only one pkey.. //that's ok, the old pkey is still on indices list, if not empty } } if (!pkey) {//clearing - set empty pkey pkey = new KDbIndexSchema; d->addIndex(pkey); } d->pkey = pkey; //!< @todo d->pkey->setPrimaryKey(true); d->anyNonPKField = nullptr; //for safety } bool KDbTableSchema::insertField(int index, KDbField *field) { Q_ASSERT(field); KDbFieldList::insertField(index, field); if (!field || index > m_fields.count()) { return false; } field->setTable(this); field->m_order = index; //update order for next next fields const int fieldCount = m_fields.count(); for (int i = index + 1; i < fieldCount; i++) m_fields.at(i)->m_order = i; //Check for auto-generated indices: - KDbIndexSchema *idx = 0; + KDbIndexSchema *idx = nullptr; if (field->isPrimaryKey()) {// this is auto-generated single-field unique index idx = new KDbIndexSchema; d->addIndex(idx); idx->setAutoGenerated(true); const bool ok = idx->addField(field); Q_ASSERT(ok); setPrimaryKey(idx); } if (field->isUniqueKey()) { if (!idx) { idx = new KDbIndexSchema; d->addIndex(idx); idx->setAutoGenerated(true); const bool ok = idx->addField(field); Q_ASSERT(ok); } idx->setUnique(true); } if (field->isIndexed()) {// this is auto-generated single-field if (!idx) { idx = new KDbIndexSchema; d->addIndex(idx); idx->setAutoGenerated(true); const bool ok = idx->addField(field); Q_ASSERT(ok); } } return true; } bool KDbTableSchema::removeField(KDbField *field) { KDbLookupFieldSchema* lookup = d->lookupFields.take(field); if (!KDbFieldList::removeField(field)) { return false; } if (d->anyNonPKField && field == d->anyNonPKField) //d->anyNonPKField will be removed! - d->anyNonPKField = 0; + d->anyNonPKField = nullptr; delete lookup; return true; } void KDbTableSchema::clear() { d->indices.clear(); d->clearLookupFields(); KDbFieldList::clear(); KDbObject::clear(); d->conn = nullptr; } QDebug KDbTableSchema::debugFields(QDebug dbg) const { dbg.nospace() << static_cast(*this); foreach(const KDbField *f, m_fields) { const KDbLookupFieldSchema *lookupSchema = lookupFieldSchema(*f); if (lookupSchema) dbg.nospace() << '\n' << f->name() << *lookupSchema; } return dbg.space(); } QDebug operator<<(QDebug dbg, const KDbTableSchema& table) { dbg.nospace() << "TABLE"; dbg.space() << static_cast(table) << '\n'; table.debugFields(dbg); return dbg.space(); } QDebug operator<<(QDebug dbg, const KDbInternalTableSchema& table) { dbg.nospace() << "INTERNAL_TABLE"; dbg.space() << static_cast(table) << '\n'; table.debugFields(dbg); return dbg.space(); } KDbConnection* KDbTableSchema::connection() const { return d->conn; } void KDbTableSchema::setConnection(KDbConnection* conn) { d->conn = conn; } KDbQuerySchema* KDbTableSchema::query() { if (d->query) return d->query; d->query = new KDbQuerySchema(this); //it's owned by me return d->query; } KDbField* KDbTableSchema::anyNonPKField() { if (!d->anyNonPKField) { - KDbField *f = 0; + KDbField *f = nullptr; for (QListIterator it(m_fields); it.hasPrevious();) { f = it.previous(); if (!f->isPrimaryKey() && (!d->pkey || !d->pkey->hasField(*f))) break; } d->anyNonPKField = f; } return d->anyNonPKField; } bool KDbTableSchema::setLookupFieldSchema(const QString& fieldName, KDbLookupFieldSchema *lookupFieldSchema) { KDbField *f = field(fieldName); if (!f) { kdbWarning() << "no such field" << fieldName << "in table" << name(); return false; } delete d->lookupFields.take(f); if (lookupFieldSchema) { d->lookupFields.insert(f, lookupFieldSchema); } d->lookupFieldsList.clear(); //this will force to rebuid the internal cache return true; } KDbLookupFieldSchema *KDbTableSchema::lookupFieldSchema(const KDbField& field) const { return d->lookupFields.value(&field); } KDbLookupFieldSchema *KDbTableSchema::lookupFieldSchema(const QString& fieldName) { KDbField *f = KDbTableSchema::field(fieldName); if (!f) - return 0; + return nullptr; return lookupFieldSchema(*f); } QVector KDbTableSchema::lookupFields() const { if (d->lookupFields.isEmpty()) return QVector(); if (!d->lookupFields.isEmpty() && !d->lookupFieldsList.isEmpty()) return d->lookupFieldsList; //already updated //update d->lookupFieldsList.clear(); d->lookupFieldsList.resize(d->lookupFields.count()); int i = 0; foreach(KDbField* f, m_fields) { QHash::ConstIterator itMap = d->lookupFields.constFind(f); if (itMap != d->lookupFields.constEnd()) { d->lookupFieldsList[i] = itMap.value(); i++; } } return d->lookupFieldsList; } //-------------------------------------- class Q_DECL_HIDDEN KDbInternalTableSchema::Private { public: Private() {} bool dummy = false; }; KDbInternalTableSchema::KDbInternalTableSchema(const QString& name) : KDbTableSchema(name) , d(new Private) { } KDbInternalTableSchema::KDbInternalTableSchema(const KDbTableSchema& ts) : KDbTableSchema(ts, false) , d(new Private) { } KDbInternalTableSchema::KDbInternalTableSchema(const KDbInternalTableSchema& ts) : KDbTableSchema(ts, false) , d(new Private) { } KDbInternalTableSchema::~KDbInternalTableSchema() { delete d; } diff --git a/src/KDbTableSchema.h b/src/KDbTableSchema.h index 432d4efd..f95b3941 100644 --- a/src/KDbTableSchema.h +++ b/src/KDbTableSchema.h @@ -1,222 +1,222 @@ /* This file is part of the KDE project Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TABLESCHEMA_H #define KDB_TABLESCHEMA_H #include #include #include "KDbFieldList.h" #include "KDbIndexSchema.h" class KDbConnection; class KDbLookupFieldSchema; /*! Provides information about native database table that can be stored using KDb database engine. */ class KDB_EXPORT KDbTableSchema : public KDbFieldList, public KDbObject { public: explicit KDbTableSchema(const QString& name); explicit KDbTableSchema(const KDbObject& object); KDbTableSchema(); /*! Copy constructor. If @a copyId is true, it's copied as well, otherwise the table id becomes -1, what is usable when we want to store the copy as an independent table. */ explicit KDbTableSchema(const KDbTableSchema& ts, bool copyId); /*! Copy constructor like @ref KDbTableSchema(const KDbTableSchema&, bool). @a id is set as the table identifier. This is rarely usable, e.g. in project and data migration routines when we need to need deal with unique identifiers; @see KexiMigrate::performImport(). */ KDbTableSchema(const KDbTableSchema& ts, int id); - virtual ~KDbTableSchema(); + ~KDbTableSchema() override; /*! Inserts @a field into a specified position (@a index). 'order' property of @a field is set automatically. */ - virtual bool insertField(int index, KDbField *field); + bool insertField(int index, KDbField *field) override; /*! Reimplemented for internal reasons. */ - virtual bool removeField(KDbField *field); + bool removeField(KDbField *field) override; /*! @return list of fields that are primary key of this table. - This method never returns 0 value, + This method never returns @c nullptr value, if there is no primary key, empty KDbIndexSchema object is returned. KDbIndexSchema object is owned by the table schema. */ KDbIndexSchema* primaryKey(); //! @overload KDbIndexSchema* primaryKey() const KDbIndexSchema* primaryKey() const; /*! Sets table's primary key index to @a pkey. Pass pkey as @c nullptr to unassign existing primary key. In this case "primary" property of previous primary key KDbIndexSchema object that will be cleared, making it an ordinary index. If this table already has primary key assigned, it is unassigned using setPrimaryKey(nullptr). Before assigning as primary key, you should add the index to indices list with addIndex() (this is not done automatically!). */ void setPrimaryKey(KDbIndexSchema *pkey); const QList::ConstIterator indicesIterator() const; const QList* indices() const; //! Adds index @a index to this table schema //! Ownership of the index is transferred to the table schema. //! @return true on success //! @since 3.1 bool addIndex(KDbIndexSchema *index); //! Removes index @a index from this table schema //! Ownership of the index is transferred to the table schema. //! @return true on success //! @since 3.1 bool removeIndex(KDbIndexSchema *index); /*! Creates a copy of index @a index with references moved to fields of this table. The new index is added to this table schema. Table fields are taken by name from this table. This way it's possible to copy index owned by other table and add it to another table, e.g. a copied one. To copy an index from another table, call: @code KDbIndexSchema *originalIndex = anotherTable->indices()->at(...); KDbIndexSchema *newIndex = table->copyIndex(*originalIndex); // newIndex is now created and is added to the table 'table' @endcode To copy an index within the same table, call: @code KDbIndexSchema *originalIndex = table->indices()->at(...); KDbIndexSchema *newIndex = table->copyIndex(*originalIndex); // newIndex is now created and is added to the table @endcode @since 3.1 @todo All relationships should be also copied */ KDbIndexSchema* copyIndexFrom(const KDbIndexSchema& index); /*! Removes all fields from the list, clears name and all other properties. @see KDbFieldList::clear() */ - virtual void clear(); + void clear() override; /*! Sends information about fields of this table schema to debug output @a dbg. */ QDebug debugFields(QDebug dbg) const; /*! @return connection object if table was created/retrieved using a connection, otherwise 0. */ KDbConnection* connection() const; /*! @return true if this is internal KDb's table. Internal tables are hidden in applications (if desired) but are available for schema export/import functionality. Any internal KDb system table's schema (kexi__*) has cleared its KDbObject part, e.g. id=-1 for such table, and no description, caption and so on. This is because it represents a native database table rather that extended Kexi table. KDbTableSchema object has this property set to false, KDbInternalTableSchema has it set to true. */ bool isInternal() const; /*! @return query schema object that is defined by "select * from " This query schema object is owned by the table schema object. It is convenient way to get such a query when it is not available otherwise. Always returns non-0. */ KDbQuerySchema* query(); /*! @return any field not being a part of primary key of this table. - If there is no such field, returns 0. */ + If there is no such field, returns @c nullptr. */ KDbField* anyNonPKField(); /*! Sets lookup field schema @a lookupFieldSchema for @a fieldName. Passing null @a lookupFieldSchema will remove the previously set lookup field. @return true if @a lookupFieldSchema has been added, or false if there is no such field @a fieldName. */ bool setLookupFieldSchema(const QString& fieldName, KDbLookupFieldSchema *lookupFieldSchema); /*! @return lookup field schema for @a field. 0 is returned if there is no such field in the table or this field has no lookup schema. Note that even id non-zero is returned here, you may want to check whether lookup field's recordSource().name() is empty (if so, the field should behave as there was no lookup field defined at all). */ KDbLookupFieldSchema *lookupFieldSchema(const KDbField& field) const; /*! @overload KDbLookupFieldSchema *KDbTableSchema::lookupFieldSchema( KDbField& field ) const */ KDbLookupFieldSchema *lookupFieldSchema(const QString& fieldName); /*! @return list of lookup field schemas for this table. The order is the same as the order of fields within the table. */ QVector lookupFields() const; protected: /*! Automatically retrieves table schema via connection. */ explicit KDbTableSchema(KDbConnection *conn, const QString & name = QString()); /*! For KDbConnection. */ void setConnection(KDbConnection* conn); private: //! Used by some ctors. void init(KDbConnection* conn); //! Used by some ctors. void init(const KDbTableSchema& ts, bool copyId); class Private; Private * const d; friend class KDbConnection; friend class KDbNativeStatementBuilder; Q_DISABLE_COPY(KDbTableSchema) }; /*! Internal table with a name @a name. Rarely used. Use KDbConnection::createTable() to create a table using this schema. The table will not be visible as user table. For example, 'kexi__blobs' table is created this way by Kexi application. */ class KDB_EXPORT KDbInternalTableSchema : public KDbTableSchema { public: explicit KDbInternalTableSchema(const QString& name); explicit KDbInternalTableSchema(const KDbTableSchema& ts); KDbInternalTableSchema(const KDbInternalTableSchema& ts); - virtual ~KDbInternalTableSchema(); + ~KDbInternalTableSchema() override; private: class Private; Private * const d; }; //! Sends information about table schema @a table to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTableSchema& table); //! Sends information about internal table schema @a table to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbInternalTableSchema& table); #endif diff --git a/src/KDbTransaction.cpp b/src/KDbTransaction.cpp index 338d33df..441baebf 100644 --- a/src/KDbTransaction.cpp +++ b/src/KDbTransaction.cpp @@ -1,161 +1,161 @@ /* This file is part of the KDE project Copyright (C) 2003 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbTransaction.h" #include "KDbConnection.h" #include "kdb_debug.h" #include //helper for debugging KDB_EXPORT int KDbTransaction::globalcount = 0; KDB_EXPORT int KDbTransaction::globalCount() { return KDbTransaction::globalcount; } KDB_EXPORT int KDbTransactionData::globalcount = 0; KDB_EXPORT int KDbTransactionData::globalCount() { return KDbTransactionData::globalcount; } KDbTransactionData::KDbTransactionData(KDbConnection *conn) : m_conn(conn) , m_active(true) , refcount(1) { Q_ASSERT(conn); KDbTransaction::globalcount++; //because refcount(1) init. KDbTransactionData::globalcount++; transactionsDebug() << "-- globalcount ==" << KDbTransactionData::globalcount; } KDbTransactionData::~KDbTransactionData() { KDbTransactionData::globalcount--; transactionsDebug() << "-- globalcount ==" << KDbTransactionData::globalcount; } //--------------------------------------------------- KDbTransaction::KDbTransaction() - : m_data(0) + : m_data(nullptr) { } KDbTransaction::KDbTransaction(const KDbTransaction& trans) : m_data(trans.m_data) { if (m_data) { m_data->refcount++; KDbTransaction::globalcount++; } } KDbTransaction::~KDbTransaction() { if (m_data) { m_data->refcount--; KDbTransaction::globalcount--; transactionsDebug() << "m_data->refcount==" << m_data->refcount; if (m_data->refcount == 0) delete m_data; } else { transactionsDebug() << "null"; } transactionsDebug() << "-- globalcount == " << KDbTransaction::globalcount; } KDbTransaction& KDbTransaction::operator=(const KDbTransaction & trans) { if (this != &trans) { if (m_data) { m_data->refcount--; KDbTransaction::globalcount--; transactionsDebug() << "m_data->refcount==" << m_data->refcount; if (m_data->refcount == 0) delete m_data; } m_data = trans.m_data; if (m_data) { m_data->refcount++; KDbTransaction::globalcount++; } } return *this; } bool KDbTransaction::operator==(const KDbTransaction& trans) const { return m_data == trans.m_data; } KDbConnection* KDbTransaction::connection() const { - return m_data ? m_data->m_conn : 0; + return m_data ? m_data->m_conn : nullptr; } bool KDbTransaction::active() const { return m_data && m_data->m_active; } bool KDbTransaction::isNull() const { - return m_data == 0; + return m_data == nullptr; } //--------------------------------------------------- KDbTransactionGuard::KDbTransactionGuard(KDbConnection *conn) : m_doNothing(false) { Q_ASSERT(conn); m_trans = conn->beginTransaction(); } KDbTransactionGuard::KDbTransactionGuard(const KDbTransaction& trans) : m_trans(trans) , m_doNothing(false) { } KDbTransactionGuard::KDbTransactionGuard() : m_doNothing(false) { } KDbTransactionGuard::~KDbTransactionGuard() { if (!m_doNothing && m_trans.active() && m_trans.connection()) m_trans.connection()->rollbackTransaction(m_trans); } bool KDbTransactionGuard::commit() { if (m_trans.active() && m_trans.connection()) { return m_trans.connection()->commitTransaction(m_trans); } return false; } void KDbTransactionGuard::doNothing() { m_doNothing = true; } diff --git a/src/drivers/mysql/MysqlConnection.cpp b/src/drivers/mysql/MysqlConnection.cpp index 2348ec22..e74f1905 100644 --- a/src/drivers/mysql/MysqlConnection.cpp +++ b/src/drivers/mysql/MysqlConnection.cpp @@ -1,211 +1,211 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "MysqlConnection.h" #include "MysqlDriver.h" #include "MysqlCursor.h" #include "MysqlPreparedStatement.h" #include "mysql_debug.h" #include "KDbConnectionData.h" #include "KDbVersionInfo.h" #include MysqlConnection::MysqlConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options) : KDbConnection(driver, connData, options) , d(new MysqlConnectionInternal(this)) { } MysqlConnection::~MysqlConnection() { destroy(); delete d; } bool MysqlConnection::drv_connect() { const bool ok = d->db_connect(data()); if (!ok) { storeResult(); //store error msg, if any - can be destroyed after disconnect() d->db_disconnect(); return false; } // Get lower_case_table_name value so we know if there's case sensitivity supported // See http://dev.mysql.com/doc/refman/5.0/en/identifier-case-sensitivity.html int intLowerCaseTableNames = 0; tristate res = querySingleNumber(KDbEscapedString("SHOW VARIABLES LIKE 'lower_case_table_name'"), &intLowerCaseTableNames, 0/*col*/, false/* !addLimitTo1 */); if (res == false) // sanity return false; d->lowerCaseTableNames = intLowerCaseTableNames > 0; return true; } bool MysqlConnection::drv_getServerVersion(KDbServerVersionInfo* version) { // http://dev.mysql.com/doc/refman/5.1/en/mysql-get-server-info.html version->setString(QLatin1String(mysql_get_server_info(d->mysql))); // get the version info using 'version' built-in variable: //! @todo this is hardcoded for now; define api for retrieving variables and use this API... // http://dev.mysql.com/doc/refman/5.1/en/mysql-get-server-version.html QString versionString; tristate res = querySingleString(KDbEscapedString("SELECT @@version"), &versionString, /*column*/0, false /*!addLimitTo1*/); QRegularExpression versionRe(QLatin1String("^(\\d+)\\.(\\d+)\\.(\\d+)$")); QRegularExpressionMatch match = versionRe.match(versionString); if (res == false) // sanity return false; if (match.hasMatch()) { // (if querySingleString failed, the version will be 0.0.0... version->setMajor(match.captured(1).toInt()); version->setMinor(match.captured(2).toInt()); version->setRelease(match.captured(3).toInt()); } return true; } bool MysqlConnection::drv_disconnect() { return d->db_disconnect(); } KDbCursor* MysqlConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options) { return new MysqlCursor(this, sql, options); } KDbCursor* MysqlConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options) { return new MysqlCursor(this, query, options); } bool MysqlConnection::drv_getDatabasesList(QStringList* list) { mysqlDebug(); list->clear(); - MYSQL_RES *res = mysql_list_dbs(d->mysql, 0); - if (res != 0) { + MYSQL_RES *res = mysql_list_dbs(d->mysql, nullptr); + if (res != nullptr) { MYSQL_ROW row; - while ((row = mysql_fetch_row(res)) != 0) { + while ((row = mysql_fetch_row(res)) != nullptr) { *list << QString::fromUtf8(row[0]); } mysql_free_result(res); return true; } storeResult(); return false; } bool MysqlConnection::drv_databaseExists(const QString &dbName, bool ignoreErrors) { /* db names can be lower case in mysql */ const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName); const tristate result = resultExists( KDbEscapedString("SHOW DATABASES LIKE %1").arg(escapeString(storedDbName))); if (result == true) { return true; } if (!ignoreErrors) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("The database \"%1\" does not exist.").arg(storedDbName)); } return false; } bool MysqlConnection::drv_createDatabase(const QString &dbName) { const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName); mysqlDebug() << storedDbName; // mysql_create_db deprecated, use SQL here. // db names are lower case in mysql return drv_executeVoidSQL(KDbEscapedString("CREATE DATABASE %1").arg(escapeIdentifier(storedDbName))); } bool MysqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled, KDbMessageHandler* msgHandler) { Q_UNUSED(cancelled); Q_UNUSED(msgHandler); //! @todo is here escaping needed? const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName); if (!d->useDatabase(storedDbName)) { storeResult(); return false; } return true; } bool MysqlConnection::drv_closeDatabase() { //! @todo free resources, as far as I know, mysql doesn't support that return true; } bool MysqlConnection::drv_dropDatabase(const QString &dbName) { //! @todo is here escaping needed? const QString storedDbName(d->lowerCaseTableNames ? dbName.toLower() : dbName); return drv_executeVoidSQL(KDbEscapedString("DROP DATABASE %1").arg(escapeIdentifier(storedDbName))); } KDbSqlResult* MysqlConnection::drv_executeSQL(const KDbEscapedString& sql) { if (!drv_executeVoidSQL(sql)) { return nullptr; } MYSQL_RES *data = mysql_use_result(d->mysql); // more optimal than mysql_store_result //! @todo use mysql_error() return new MysqlSqlResult(this, data); } bool MysqlConnection::drv_executeVoidSQL(const KDbEscapedString& sql) { if (!d->executeVoidSQL(sql)) { storeResult(); return false; } return true; } QString MysqlConnection::serverResultName() const { return MysqlConnectionInternal::serverResultName(d->mysql); } tristate MysqlConnection::drv_containsTable(const QString& tableName) { return resultExists(KDbEscapedString("SHOW TABLES LIKE %1") .arg(escapeString(tableName))); } KDbPreparedStatementInterface* MysqlConnection::prepareStatementInternal() { return new MysqlPreparedStatement(d); } void MysqlConnection::storeResult() { d->storeResult(&m_result); } diff --git a/src/drivers/mysql/MysqlConnection.h b/src/drivers/mysql/MysqlConnection.h index 86cc04c5..81c1aa7e 100644 --- a/src/drivers/mysql/MysqlConnection.h +++ b/src/drivers/mysql/MysqlConnection.h @@ -1,82 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MYSQLCONNECTION_H #define KDB_MYSQLCONNECTION_H #include #include "KDbConnection.h" class MysqlConnectionInternal; /*! @short Provides database connection, allowing queries and data modification. */ class MysqlConnection : public KDbConnection { Q_DECLARE_TR_FUNCTIONS(MysqlConnection) public: - virtual ~MysqlConnection(); + ~MysqlConnection() override; - KDbCursor* prepareQuery(const KDbEscapedString& sql, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; - KDbCursor* prepareQuery(KDbQuerySchema* query, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(const KDbEscapedString &sql, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(KDbQuerySchema *query, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; - KDbPreparedStatementInterface* prepareStatementInternal() Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbPreparedStatementInterface *prepareStatementInternal() override Q_REQUIRED_RESULT; protected: /*! Used by driver */ MysqlConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options); - virtual bool drv_connect(); - virtual bool drv_getServerVersion(KDbServerVersionInfo* version); - virtual bool drv_disconnect(); - virtual bool drv_getDatabasesList(QStringList* list); + bool drv_connect() override; + bool drv_getServerVersion(KDbServerVersionInfo* version) override; + bool drv_disconnect() override; + bool drv_getDatabasesList(QStringList* list) override; //! reimplemented using "SHOW DATABASES LIKE..." because MySQL stores db names in lower case. - virtual bool drv_databaseExists(const QString &dbName, bool ignoreErrors = true); - virtual bool drv_createDatabase(const QString &dbName = QString()); - virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = 0, - KDbMessageHandler* msgHandler = 0); - virtual bool drv_closeDatabase(); - virtual bool drv_dropDatabase(const QString &dbName = QString()); - virtual KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) Q_REQUIRED_RESULT; - virtual bool drv_executeVoidSQL(const KDbEscapedString& sql); + bool drv_databaseExists(const QString &dbName, bool ignoreErrors = true) override; + bool drv_createDatabase(const QString &dbName = QString()) override; + bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = nullptr, + KDbMessageHandler* msgHandler = nullptr) override; + bool drv_closeDatabase() override; + bool drv_dropDatabase(const QString &dbName = QString()) override; + KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) override Q_REQUIRED_RESULT; + bool drv_executeVoidSQL(const KDbEscapedString& sql) override; //! Implemented for KDbResultable - virtual QString serverResultName() const; + QString serverResultName() const override; //! @todo move this somewhere to low level class (MIGRATION?) - virtual tristate drv_containsTable(const QString &tableName); + tristate drv_containsTable(const QString &tableName) override; void storeResult(); MysqlConnectionInternal* const d; friend class MysqlDriver; friend class MysqlCursorData; friend class MysqlSqlResult; private: Q_DISABLE_COPY(MysqlConnection) }; #endif diff --git a/src/drivers/mysql/MysqlConnection_p.cpp b/src/drivers/mysql/MysqlConnection_p.cpp index 40d26648..2a14a10a 100644 --- a/src/drivers/mysql/MysqlConnection_p.cpp +++ b/src/drivers/mysql/MysqlConnection_p.cpp @@ -1,372 +1,372 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "MysqlConnection_p.h" #include "mysql_debug.h" #include "KDbConnectionData.h" #include #include #include static inline QString escapeIdentifier(const QString& str) { return QString(str).replace(QLatin1Char('`'), QLatin1Char('\'')); } MysqlConnectionInternal::MysqlConnectionInternal(KDbConnection* connection) : KDbConnectionInternal(connection) - , mysql(0) + , mysql(nullptr) , mysql_owned(true) , res(0) , lowerCaseTableNames(false) , serverVersion(0) { } MysqlConnectionInternal::~MysqlConnectionInternal() { if (mysql_owned && mysql) { db_disconnect(); } } bool MysqlConnectionInternal::db_connect(const KDbConnectionData& data) { if (!(mysql = mysql_init(mysql))) return false; mysqlDebug(); QByteArray localSocket; QByteArray hostName = QFile::encodeName(data.hostName()); if (hostName.isEmpty() || 0 == qstricmp(hostName.constData(), "localhost")) { if (data.useLocalSocketFile()) { if (data.localSocketFileName().isEmpty()) { //! @todo move the list of default sockets to a generic method QStringList sockets; #ifndef Q_OS_WIN sockets << QLatin1String("/var/lib/mysql/mysql.sock") << QLatin1String("/var/run/mysqld/mysqld.sock") << QLatin1String("/var/run/mysql/mysql.sock") << QLatin1String("/tmp/mysql.sock"); foreach(const QString& socket, sockets) { if (QFile(socket).exists()) { localSocket = socket.toLatin1(); break; } } #endif } else localSocket = QFile::encodeName(data.localSocketFileName()); } else { //we're not using local socket hostName = "127.0.0.1"; //this will force mysql to connect to localhost } } /*! @todo is latin1() encoding here valid? what about using UTF for passwords? */ const QByteArray userName(data.userName().toLatin1()); const QByteArray password(data.password().toLatin1()); int client_flag = 0; //!< @todo support client_flag? - if (mysql_real_connect(mysql, hostName.isEmpty() ? 0 : hostName.constData(), - data.userName().isEmpty() ? 0 : userName.constData(), - data.password().isNull() ? 0 : password.constData(), - 0, - data.port(), localSocket.isEmpty() ? 0 : localSocket.constData(), + if (mysql_real_connect(mysql, hostName.isEmpty() ? nullptr : hostName.constData(), + data.userName().isEmpty() ? nullptr : userName.constData(), + data.password().isNull() ? nullptr : password.constData(), + nullptr, + data.port(), localSocket.isEmpty() ? nullptr : localSocket.constData(), client_flag)) { serverVersion = mysql_get_server_version(mysql); return true; } return false; } bool MysqlConnectionInternal::db_disconnect() { mysql_close(mysql); - mysql = 0; + mysql = nullptr; serverVersion = 0; mysqlDebug(); return true; } bool MysqlConnectionInternal::useDatabase(const QString &dbName) { //! @todo is here escaping needed? if (!executeVoidSQL(KDbEscapedString("USE ") + escapeIdentifier(dbName))) { return false; } if (!executeVoidSQL(KDbEscapedString("SET SESSION sql_mode='TRADITIONAL'"))) { // needed to turn warnings about trimming string values into SQL errors return false; } return true; } bool MysqlConnectionInternal::executeVoidSQL(const KDbEscapedString& sql) { return 0 == mysql_real_query(mysql, sql.constData(), sql.length()); } //static QString MysqlConnectionInternal::serverResultName(MYSQL *mysql) { //! @todo use mysql_stmt_sqlstate() for prepared statements return QString::fromLatin1(mysql_sqlstate(mysql)); } void MysqlConnectionInternal::storeResult(KDbResult *result) { result->setServerMessage(QString::fromLatin1(mysql_error(mysql))); result->setServerErrorCode(mysql_errno(mysql)); } //-------------------------------------- MysqlCursorData::MysqlCursorData(KDbConnection* connection) : MysqlConnectionInternal(connection) - , mysqlres(0) - , mysqlrow(0) - , lengths(0) + , mysqlres(nullptr) + , mysqlrow(nullptr) + , lengths(nullptr) , numRows(0) { mysql_owned = false; mysql = static_cast(connection)->d->mysql; } MysqlCursorData::~MysqlCursorData() { } //-------------------------------------- static inline KDbSqlString mysqlTypeName(MysqlSqlResult *result, QScopedPointer *recordPtr) { KDbSqlString name; if (result && result->fieldsCount() >= 2) { recordPtr->reset(static_cast(result->fetchRecord())); if (*recordPtr) { name = (*recordPtr)->cstringValue(1); } } return name; } //! @todo From Kexi MysqlMigrate, unused for now because enum type isn't supported by KDb #if 0 //! Get the strings that identify values in an enum field /*! Parse the type of a MySQL enum field as returned by the server in a 'DESCRIBE table' or 'SHOW COLUMNS FROM table' statement. The string returned by the server is in the form 'enum('option1','option2'). In this example, the result should be a string list containing two strings, "option1", "option2". \return list of possible values the field can take */ QStringList examineEnumField(const QString& table, const KDbSqlField* field) { QString vals; const KDbEscapedString query = KDbEscapedString("SHOW COLUMNS FROM `") + conn->escapeIdentifier(table) + "` LIKE '" + conn->escapeIdentifier(fld->name) + '\''; if (!conn->executeVoidSQL(query)) // Huh? MySQL wont tell us what values it can take. return QStringList(); MYSQL_RES *res = mysql_store_result(d->mysql); if (!res) { //qWarning() << "null result"; } else { MYSQL_ROW row; if ((row = mysql_fetch_row(res))) vals = QString(row[1]); mysql_free_result(res); } qDebug() << "considering" << vals; // Crash and burn if we get confused... if (!vals.startsWith("enum(")) { // Huh? We're supposed to be parsing an enum! qWarning() << "1 not an enum!"; return QStringList(); } if (!vals.endsWith(')')) { qWarning() << "2 not an enum!"; return QStringList(); } // It'd be nice to use QString.section or QStringList.split, but we need // to be careful as enum values can have commas and quote marks in them // e.g. CREATE TABLE t(f enum('option,''') gives one option: "option,'" vals.remove(0, 5); QRegularExpression rx = QRegularExpression("^'((?:[^,']|,|'')*)'"); QStringList values = QStringList(); int index = 0; while ((index = rx.indexIn(vals, index, QRegularExpression::CaretAtOffset)) != -1) { int len = rx.matchedLength(); if (len != -1) { //qDebug() << "3 " << rx.cap(1); values << rx.cap(1); } else { qDebug() << "4 lost"; } QChar next = vals[index + len]; if (next != QChar(',') && next != QChar(')')) { qDebug() << "5 " << next; } index += len + 1; } return values; } #endif // examineEnumField KDbField::Type MysqlSqlResult::blobType(const QString& tableName, MysqlSqlField *field) { KDbField::Type kdbType = KDbField::LongText; const KDbEscapedString sql = KDbEscapedString("SHOW COLUMNS FROM %1 LIKE '%2'") .arg(escapeIdentifier(tableName)).arg(field->name()); //! @todo this conflicts with active query QScopedPointer result(static_cast(conn->executeSQL(sql))); if (result) { QScopedPointer record; const KDbSqlString typeName(mysqlTypeName(result.data(), &record)); if (typeName.rawDataToByteArray().toLower().contains("blob")) { // Doesn't matter how big it is, it's binary kdbType = KDbField::BLOB; } else if (field->length() < 200) { kdbType = KDbField::Text; } } return kdbType; } KDbField::Type MysqlSqlResult::type(const QString& tableName, MysqlSqlField *field) { // Field type KDbField::Type kdbType = KDbField::InvalidType; switch (field->type()) { // These are in the same order as mysql_com.h. // MySQL names given on the right case FIELD_TYPE_DECIMAL: // DECIMAL or NUMERIC break; case FIELD_TYPE_TINY: // TINYINT (-2^7..2^7-1 or 2^8) kdbType = KDbField::Byte; break; case FIELD_TYPE_SHORT: // SMALLINT (-2^15..2^15-1 or 2^16) kdbType = KDbField::ShortInteger; break; case FIELD_TYPE_LONG: // INTEGER (-2^31..2^31-1 or 2^32) kdbType = KDbField::Integer; break; case FIELD_TYPE_FLOAT: // FLOAT kdbType = KDbField::Float; break; case FIELD_TYPE_DOUBLE: // DOUBLE or REAL (8 byte) kdbType = KDbField::Double; break; case FIELD_TYPE_NULL: // WTF? break; case FIELD_TYPE_TIMESTAMP: // TIMESTAMP (promote?) kdbType = KDbField::DateTime; break; case FIELD_TYPE_LONGLONG: // BIGINT (-2^63..2^63-1 or 2^64) case FIELD_TYPE_INT24: // MEDIUMINT (-2^23..2^23-1 or 2^24) (promote) kdbType = KDbField::BigInteger; break; case FIELD_TYPE_DATE: // DATE kdbType = KDbField::Date; break; case FIELD_TYPE_TIME: // TIME kdbType = KDbField::Time; break; case FIELD_TYPE_DATETIME: // DATETIME kdbType = KDbField::DateTime; break; case FIELD_TYPE_YEAR: // YEAR (promote) kdbType = KDbField::ShortInteger; break; case FIELD_TYPE_NEWDATE: // WTF? case FIELD_TYPE_ENUM: // ENUM // If MySQL did what it's documentation said it did, we would come here // for enum fields ... kdbType = KDbField::Enum; break; case FIELD_TYPE_SET: // SET //! @todo: Support set column type break; case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_MEDIUM_BLOB: case FIELD_TYPE_LONG_BLOB: case FIELD_TYPE_BLOB: // BLOB or TEXT case FIELD_TYPE_VAR_STRING: // VARCHAR case FIELD_TYPE_STRING: // CHAR if (field->data->flags & ENUM_FLAG) { // ... instead we come here, using the ENUM_FLAG which is supposed to // be deprecated! Duh. kdbType = KDbField::Enum; } else { kdbType = blobType(tableName, field); } break; default: break; } return kdbType; } inline void copyConstraints(int mysqlFieldFlags, KDbField* field) { field->setPrimaryKey(mysqlFieldFlags & PRI_KEY_FLAG); field->setAutoIncrement(mysqlFieldFlags & AUTO_INCREMENT_FLAG); field->setNotNull(mysqlFieldFlags & NOT_NULL_FLAG); field->setUniqueKey(mysqlFieldFlags & UNIQUE_KEY_FLAG); //! @todo: support keys } static inline void copyOptions(int mysqlFieldFlags, KDbField* field) { field->setUnsigned(mysqlFieldFlags & UNSIGNED_FLAG); } KDbField* MysqlSqlResult::createField(const QString &tableName, int index) { QScopedPointer f(static_cast(field(index))); if (!f) { return nullptr; } const QString caption(f->name()); QString realFieldName(KDb::stringToIdentifier(caption.toLower())); KDbField *kdbField = new KDbField(realFieldName, type(tableName, f.data())); kdbField->setCaption(caption); const int flags = f->data->flags; copyConstraints(flags, kdbField); copyOptions(flags, kdbField); return kdbField; } diff --git a/src/drivers/mysql/MysqlConnection_p.h b/src/drivers/mysql/MysqlConnection_p.h index f418135b..b7aa1ba2 100644 --- a/src/drivers/mysql/MysqlConnection_p.h +++ b/src/drivers/mysql/MysqlConnection_p.h @@ -1,227 +1,227 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MYSQLCONNECTION_P_H #define KDB_MYSQLCONNECTION_P_H #include "KDbConnection_p.h" #include "MysqlConnection.h" #include "KDbResult.h" #include "KDbSqlField.h" #include "KDbSqlRecord.h" #include "KDbSqlResult.h" #include "KDbSqlString.h" #include #include class KDbConnectionData; class KDbEscapedString; //! Internal MySQL connection data. /*! Provides a low-level API for accessing MySQL databases, that can be shared by any module that needs direct access to the underlying database. Used by the KDb and migration drivers. @todo fix the above note about migration... */ class MysqlConnectionInternal : public KDbConnectionInternal { public: explicit MysqlConnectionInternal(KDbConnection* connection); virtual ~MysqlConnectionInternal(); //! Connects to a MySQL database /*! Connects to the MySQL server on host as the given user using the specified password. If host is "localhost", then a socket on the local file system can be specified to connect to the server (several defaults will be tried if none is specified). If the server is on a remote machine, then a port is the port that the remote server is listening on. */ bool db_connect(const KDbConnectionData& data); //! Disconnects from the database bool db_disconnect(); //! Selects a database that is about to be used bool useDatabase(const QString &dbName = QString()); //! Executes query for a raw SQL statement @a sql using mysql_real_query() bool executeVoidSQL(const KDbEscapedString& sql); static QString serverResultName(MYSQL *mysql); void storeResult(KDbResult *result); MYSQL *mysql; bool mysql_owned; //!< true if mysql pointer should be freed on destruction int res; //!< result code of last operation on server //! Get lower_case_table_name variable value so we know if there's case sensitivity supported for table and database names bool lowerCaseTableNames; //! Server version known after successfull connection. //! Equal to major_version*10000 + release_level*100 + sub_version //! 0 if not known. //! See https://dev.mysql.com/doc/refman/5.7/en/mysql-get-server-version.html //! @todo store in Connection base class as a property or as public server info unsigned long serverVersion; private: Q_DISABLE_COPY(MysqlConnectionInternal) }; //! Internal MySQL cursor data. /*! Provides a low-level abstraction for iterating over MySql result sets. */ class MysqlCursorData : public MysqlConnectionInternal { public: explicit MysqlCursorData(KDbConnection* connection); - virtual ~MysqlCursorData(); + ~MysqlCursorData() override; MYSQL_RES *mysqlres; MYSQL_ROW mysqlrow; unsigned long *lengths; qint64 numRows; private: Q_DISABLE_COPY(MysqlCursorData) }; class MysqlSqlField : public KDbSqlField { public: inline MysqlSqlField(MYSQL_FIELD *f) : data(f) { } //! @return column name - inline QString name() Q_DECL_OVERRIDE { + inline QString name() override { //! @todo UTF8? return QString::fromLatin1(data->name); } - inline int type() Q_DECL_OVERRIDE { + inline int type() override { return data->type; } - inline int length() Q_DECL_OVERRIDE { + inline int length() override { return data->length; } MYSQL_FIELD *data; private: Q_DISABLE_COPY(MysqlSqlField) }; class MysqlSqlRecord : public KDbSqlRecord { public: inline MysqlSqlRecord(MYSQL_ROW r, unsigned long* len) : record(r), lengths(len) { } - inline ~MysqlSqlRecord() { + inline ~MysqlSqlRecord() override { } - inline QString stringValue(int index) Q_DECL_OVERRIDE { + inline QString stringValue(int index) override { return QString::fromUtf8(record[index], lengths[index]); } - inline KDbSqlString cstringValue(int index) Q_DECL_OVERRIDE { + inline KDbSqlString cstringValue(int index) override { return KDbSqlString(record[index], lengths[index]); } - inline QByteArray toByteArray(int index) Q_DECL_OVERRIDE { + inline QByteArray toByteArray(int index) override { return QByteArray(record[index], lengths[index]); } private: MYSQL_ROW record; unsigned long* lengths; Q_DISABLE_COPY(MysqlSqlRecord) }; class MysqlSqlResult : public KDbSqlResult { public: inline MysqlSqlResult(MysqlConnection *c, MYSQL_RES *d) : conn(c), data(d), fields(nullptr) { Q_ASSERT(c); } - inline ~MysqlSqlResult() { + inline ~MysqlSqlResult() override { if (data) { mysql_free_result(data); } } - inline KDbConnection *connection() const Q_DECL_OVERRIDE { + inline KDbConnection *connection() const override { return conn; } - inline int fieldsCount() Q_DECL_OVERRIDE { + inline int fieldsCount() override { return data ? mysql_num_fields(data) : 0; } - inline KDbSqlField *field(int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlField *field(int index) override Q_REQUIRED_RESULT { if (!fields) { if (!data) { return nullptr; } fields = mysql_fetch_fields(data); } return new MysqlSqlField(fields + index); } - KDbField *createField(const QString &tableName, int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbField *createField(const QString &tableName, int index) override Q_REQUIRED_RESULT; - inline KDbSqlRecord* fetchRecord() Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlRecord* fetchRecord() override Q_REQUIRED_RESULT { MYSQL_ROW row = data ? mysql_fetch_row(data) : nullptr; if (!row) { return nullptr; } unsigned long* lengths = mysql_fetch_lengths(data); return new MysqlSqlRecord(row, lengths); } - inline KDbResult lastResult() Q_DECL_OVERRIDE { + inline KDbResult lastResult() override { KDbResult res; const int err = mysql_errno(conn->d->mysql); if (err != 0) { res.setCode(ERR_OTHER); res.setServerErrorCode(err); } return res; } - inline quint64 lastInsertRecordId() Q_DECL_OVERRIDE { + inline quint64 lastInsertRecordId() override { //! @todo return static_cast(mysql_insert_id(conn->d->mysql)); } private: //! @return a KDb type for MySQL type //! @todo prompt user if necessary? KDbField::Type type(const QString& tableName, MysqlSqlField *field); //! @return a KDb BLOB-related type for MySQL type /*! Distinguishes between a BLOB and a TEXT types. MySQL uses the same field type to identify BLOB and TEXT fields. This method queries the server to find out if a field is a binary field or a text field. It also considers the length of CHAR and VARCHAR fields to see whether Text or LongText is the appropriate Kexi field type. Assumes fld is a CHAR, VARCHAR, one of the BLOBs or TEXTs. Returns KDbField::Text, KDbField::LongText or KDbField::BLOB. */ KDbField::Type blobType(const QString& tableName, MysqlSqlField *field); MysqlConnection * const conn; MYSQL_RES * const data; MYSQL_FIELD *fields; Q_DISABLE_COPY(MysqlSqlResult) }; #endif diff --git a/src/drivers/mysql/MysqlCursor.cpp b/src/drivers/mysql/MysqlCursor.cpp index af6d4091..e9c5664c 100644 --- a/src/drivers/mysql/MysqlCursor.cpp +++ b/src/drivers/mysql/MysqlCursor.cpp @@ -1,181 +1,181 @@ /* This file is part of the KDE project Copyright (C) 2003 Joseph Wenninger Copyright (C) 2005-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "MysqlCursor.h" #include "MysqlConnection.h" #include "MysqlConnection_p.h" #include "KDbError.h" #include "KDb.h" #include "KDbRecordData.h" #include #define BOOL bool MysqlCursor::MysqlCursor(KDbConnection* conn, const KDbEscapedString& sql, KDbCursor::Options options) : KDbCursor(conn, sql, options | KDbCursor::Option::Buffered) , d(new MysqlCursorData(conn)) { } MysqlCursor::MysqlCursor(KDbConnection* conn, KDbQuerySchema* query, KDbCursor::Options options) : KDbCursor(conn, query, options | KDbCursor::Option::Buffered) , d(new MysqlCursorData(conn)) { } MysqlCursor::~MysqlCursor() { close(); delete d; } bool MysqlCursor::drv_open(const KDbEscapedString& sql) { if (mysql_real_query(d->mysql, sql.constData(), sql.length()) == 0) { if (mysql_errno(d->mysql) == 0) { //! @todo Add option somewhere so we can use more optimal mysql_num_rows(). //! In this case mysql_num_rows() does not work however. d->mysqlres = mysql_store_result(d->mysql); m_fieldCount = mysql_num_fields(d->mysqlres); m_fieldsToStoreInRecord = m_fieldCount; d->numRows = mysql_num_rows(d->mysqlres); m_records_in_buf = d->numRows; m_buffering_completed = true; return true; } } storeResult(); return false; } bool MysqlCursor::drv_close() { mysql_free_result(d->mysqlres); - d->mysqlres = 0; - d->mysqlrow = 0; - d->lengths = 0; + d->mysqlres = nullptr; + d->mysqlrow = nullptr; + d->lengths = nullptr; d->numRows = 0; return true; } void MysqlCursor::drv_getNextRecord() { if (at() >= d->numRows) { m_fetchResult = FetchEnd; } else if (at() < 0) { // control will reach here only when at() < 0 ( which is usually -1 ) // -1 is same as "1 beyond the End" m_fetchResult = FetchEnd; } else { // 0 <= at() < d->numRows d->lengths = mysql_fetch_lengths(d->mysqlres); m_fetchResult = FetchOK; } } // This isn't going to work right now as it uses d->mysqlrow QVariant MysqlCursor::value(int pos) { - if (!d->mysqlrow || pos >= m_fieldCount || d->mysqlrow[pos] == 0) + if (!d->mysqlrow || pos >= m_fieldCount || d->mysqlrow[pos] == nullptr) return QVariant(); KDbField *f = (m_visibleFieldsExpanded && pos < m_visibleFieldsExpanded->count()) ? m_visibleFieldsExpanded->at(pos)->field() : nullptr; //! @todo js: use MYSQL_FIELD::type here! bool ok; return KDb::cstringToVariant(d->mysqlrow[pos], f ? f->type() : KDbField::Text, &ok, d->lengths[pos]); } /* As with sqlite, the DB library returns all values (including numbers) as strings. So just put that string in a QVariant and let KDb deal with it. */ bool MysqlCursor::drv_storeCurrentRecord(KDbRecordData* data) const { // mysqlDebug() << "position is " << (long)m_at; if (d->numRows == 0) return false; if (!m_visibleFieldsExpanded) {//simple version: without types for (int i = 0; i < m_fieldCount; ++i) { (*data)[i] = QString::fromUtf8(d->mysqlrow[i], d->lengths[i]); } return true; } for (int i = 0; i < m_fieldCount; ++i) { KDbField *f = m_visibleFieldsExpanded->at(i)->field(); bool ok; (*data)[i] = KDb::cstringToVariant(d->mysqlrow[i], f ? f->type() : KDbField::Text, &ok, d->lengths[i]); if (!ok) { return false; } } return true; } void MysqlCursor::drv_appendCurrentRecordToBuffer() { } void MysqlCursor::drv_bufferMovePointerNext() { d->mysqlrow = mysql_fetch_row(d->mysqlres); d->lengths = mysql_fetch_lengths(d->mysqlres); } void MysqlCursor::drv_bufferMovePointerPrev() { mysql_data_seek(d->mysqlres, m_at - 1); d->mysqlrow = mysql_fetch_row(d->mysqlres); d->lengths = mysql_fetch_lengths(d->mysqlres); } void MysqlCursor::drv_bufferMovePointerTo(qint64 to) { mysql_data_seek(d->mysqlres, to); d->mysqlrow = mysql_fetch_row(d->mysqlres); d->lengths = mysql_fetch_lengths(d->mysqlres); } const char** MysqlCursor::recordData() const { //! @todo - return 0; + return nullptr; } QString MysqlCursor::serverResultName() const { return MysqlConnectionInternal::serverResultName(d->mysql); } void MysqlCursor::storeResult() { d->storeResult(&m_result); } diff --git a/src/drivers/mysql/MysqlCursor.h b/src/drivers/mysql/MysqlCursor.h index ab8c5417..a057437c 100644 --- a/src/drivers/mysql/MysqlCursor.h +++ b/src/drivers/mysql/MysqlCursor.h @@ -1,58 +1,58 @@ /* This file is part of the KDE project Copyright (C) 2003 Joseph Wenninger Copyright (C) 2005-2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MYSQLCURSOR_H #define KDB_MYSQLCURSOR_H #include "KDbCursor.h" class KDbConnection; class MysqlCursorData; class MysqlCursor: public KDbCursor { public: MysqlCursor(KDbConnection* conn, const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None); MysqlCursor(KDbConnection* conn, KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None); - virtual ~MysqlCursor(); - - virtual QVariant value(int pos); - virtual const char** recordData() const; - virtual bool drv_storeCurrentRecord(KDbRecordData* data) const; - virtual bool drv_open(const KDbEscapedString& sql); - virtual bool drv_close(); - virtual void drv_getNextRecord(); - virtual void drv_appendCurrentRecordToBuffer(); - virtual void drv_bufferMovePointerNext(); - virtual void drv_bufferMovePointerPrev(); - virtual void drv_bufferMovePointerTo(qint64 to); + ~MysqlCursor() override; + + QVariant value(int pos) override; + const char** recordData() const override; + bool drv_storeCurrentRecord(KDbRecordData* data) const override; + bool drv_open(const KDbEscapedString& sql) override; + bool drv_close() override; + void drv_getNextRecord() override; + void drv_appendCurrentRecordToBuffer() override; + void drv_bufferMovePointerNext() override; + void drv_bufferMovePointerPrev() override; + void drv_bufferMovePointerTo(qint64 to) override; //! Implemented for KDbResultable - virtual QString serverResultName() const; + QString serverResultName() const override; private: void storeResult(); MysqlCursorData * const d; Q_DISABLE_COPY(MysqlCursor) }; #endif diff --git a/src/drivers/mysql/MysqlDriver.h b/src/drivers/mysql/MysqlDriver.h index 8c9b66de..fd45d233 100644 --- a/src/drivers/mysql/MysqlDriver.h +++ b/src/drivers/mysql/MysqlDriver.h @@ -1,103 +1,103 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MYSQLDRIVER_H #define KDB_MYSQLDRIVER_H #include "KDbDriver.h" //! MySQL database driver. class MysqlDriver : public KDbDriver { Q_OBJECT public: /*! * Constructor sets database features and * maps the types in KDbField::Type to the MySQL types. * * See: http://dev.mysql.com/doc/mysql/en/Column_types.html */ MysqlDriver(QObject *parent, const QVariantList &args); - virtual ~MysqlDriver(); + ~MysqlDriver() override; /*! @return false for this driver. */ - virtual bool isSystemObjectName(const QString& name) const; + bool isSystemObjectName(const QString& name) const override; /*! @return true if @a is "mysql", "information_schema" or "performance_schema". */ - virtual bool isSystemDatabaseName(const QString &name) const; + bool isSystemDatabaseName(const QString &name) const override; //! Escape a string for use as a value - virtual KDbEscapedString escapeString(const QString& str) const; - virtual KDbEscapedString escapeString(const QByteArray& str) const; + KDbEscapedString escapeString(const QString& str) const override; + KDbEscapedString escapeString(const QByteArray& str) const override; //! Escape BLOB value @a array - virtual KDbEscapedString escapeBLOB(const QByteArray& array) const; + KDbEscapedString escapeBLOB(const QByteArray& array) const override; //! Overrides the default implementation - virtual QString sqlTypeName(KDbField::Type type, const KDbField &field) const; + QString sqlTypeName(KDbField::Type type, const KDbField &field) const override; //! Generates native (driver-specific) LENGTH() function call. //! char_length(val) is used because length(val) in mysql returns number of bytes, //! what is not right for multibyte (unicode) encodings. */ - virtual KDbEscapedString lengthFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString lengthFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; //! Generates native (driver-specific) GREATEST() and LEAST() function call. //! Since MySQL's LEAST()/GREATEST() function ignores NULL values, it only returns NULL //! if all the expressions evaluate to NULL. So this is used for F(v0,..,vN): //! (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE F(v0,..,vN) END) //! where F == GREATEST or LEAST. - virtual KDbEscapedString greatestOrLeastFunctionToString(const QString &name, - const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString greatestOrLeastFunctionToString(const QString &name, + const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) UNICODE() function call. //! Uses ORD(CONVERT(X USING UTF16)). - virtual KDbEscapedString unicodeFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString unicodeFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) function call for concatenation of two strings. //! Uses CONCAT(). KDbEscapedString concatenateFunctionToString(const KDbBinaryExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const; protected: - virtual QString drv_escapeIdentifier(const QString& str) const; - virtual QByteArray drv_escapeIdentifier(const QByteArray& str) const; - virtual KDbConnection *drv_createConnection(const KDbConnectionData& connData, - const KDbConnectionOptions &options); - virtual bool drv_isSystemFieldName(const QString& name) const; - bool supportsDefaultValue(const KDbField &field) const Q_DECL_OVERRIDE; + QString drv_escapeIdentifier(const QString& str) const override; + QByteArray drv_escapeIdentifier(const QByteArray &str) const override; + KDbConnection *drv_createConnection(const KDbConnectionData &connData, + const KDbConnectionOptions &options) override; + bool drv_isSystemFieldName(const QString& name) const override; + bool supportsDefaultValue(const KDbField &field) const override; private: static const char *keywords[]; QString m_longTextPrimaryKeyType; Q_DISABLE_COPY(MysqlDriver) }; #endif diff --git a/src/drivers/mysql/MysqlKeywords.cpp b/src/drivers/mysql/MysqlKeywords.cpp index 04e0de4b..ed96f534 100644 --- a/src/drivers/mysql/MysqlKeywords.cpp +++ b/src/drivers/mysql/MysqlKeywords.cpp @@ -1,355 +1,355 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004 Jarosław Staniek This file has been automatically generated from tools/sql_keywords/sql_keywords.sh and mysql-4.1.7/sql/lex.h. Please edit the sql_keywords.sh, not this file! This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "MysqlDriver.h" const char* MysqlDriver::keywords[] = { "ACTION", "ADD", "AGAINST", "AGGREGATE", "ALTER", "ANALYZE", "ANY", "ASCII", "AUTO_INCREMENT", "AVG", "AVG_ROW_LENGTH", "BACKUP", "BDB", "BERKELEYDB", "BIGINT", "BINARY", "BINLOG", "BIT", "BLOB", "BOOL", "BOOLEAN", "BOTH", "BTREE", "BYTE", "CACHE", "CHANGE", "CHANGED", "CHAR", "CHARACTER", "CHARSET", "CHECKSUM", "CIPHER", "CLIENT", "CLOSE", "COLLATION", "COLUMN", "COLUMNS", "COMMENT", "COMMITTED", "COMPRESSED", "CONCURRENT", "CONVERT", "CUBE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DATA", "DATABASES", "DATE", "DATETIME", "DAY", "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEALLOCATE", "DEC", "DECIMAL", "DELAYED", "DELAY_KEY_WRITE", "DESCRIBE", "DES_KEY_FILE", "DIRECTORY", "DISABLE", "DISCARD", "DISTINCTROW", "DIV", "DO", "DOUBLE", "DUAL", "DUMPFILE", "DUPLICATE", "DYNAMIC", "ENABLE", "ENCLOSED", "ENGINE", "ENGINES", "ENUM", "ERRORS", "ESCAPE", "ESCAPED", "EVENTS", "EXECUTE", "EXISTS", "EXPANSION", "EXTENDED", "FALSE", "FAST", "FIELDS", "FILE", "FIRST", "FIXED", "FLOAT", "FLOAT4", "FLOAT8", "FLUSH", "FORCE", "FULLTEXT", "FUNCTION", "GEOMETRY", "GEOMETRYCOLLECTION", "GET_FORMAT", "GLOBAL", "GRANT", "GRANTS", "HANDLER", "HASH", "HELP", "HIGH_PRIORITY", "HOSTS", "HOUR", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IDENTIFIED", "IF", "IMPORT", "INDEXES", "INFILE", "INNOBASE", "INNODB", "INSERT_METHOD", "INT", "INT1", "INT2", "INT3", "INT4", "INT8", "INTERVAL", "IO_THREAD", "ISOLATION", "ISSUER", "KEYS", "KILL", "LAST", "LEADING", "LEAVES", "LEVEL", "LINES", "LINESTRING", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LOCKS", "LOGS", "LONG", "LONGBLOB", "LONGTEXT", "LOW_PRIORITY", "MASTER", "MASTER_CONNECT_RETRY", "MASTER_HOST", "MASTER_LOG_FILE", "MASTER_LOG_POS", "MASTER_PASSWORD", "MASTER_PORT", "MASTER_SERVER_ID", "MASTER_SSL", "MASTER_SSL_CA", "MASTER_SSL_CAPATH", "MASTER_SSL_CERT", "MASTER_SSL_CIPHER", "MASTER_SSL_KEY", "MASTER_USER", "MAX_CONNECTIONS_PER_HOUR", "MAX_QUERIES_PER_HOUR", "MAX_ROWS", "MAX_UPDATES_PER_HOUR", "MEDIUM", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MICROSECOND", "MIDDLEINT", "MINUTE", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MIN_ROWS", "MOD", "MODE", "MODIFY", "MONTH", "MULTILINESTRING", "MULTIPOINT", "MULTIPOLYGON", "NAMES", "NATIONAL", "NDB", "NDBCLUSTER", "NCHAR", "NEW", "NEXT", "NO", "NONE", "NO_WRITE_TO_BINLOG", "NUMERIC", "NVARCHAR", "OLD_PASSWORD", "ONE_SHOT", "OPEN", "OPTIMIZE", "OPTION", "OPTIONALLY", "OUTFILE", "PACK_KEYS", "PARTIAL", "PASSWORD", "POINT", "POLYGON", "PRECISION", "PREPARE", "PREV", "PRIVILEGES", "PROCEDURE", "PROCESS", "PROCESSLIST", "PURGE", "QUERY", "QUICK", "RAID0", "RAID_CHUNKS", "RAID_CHUNKSIZE", "RAID_TYPE", "READ", "REAL", "REGEXP", "RELAY_LOG_FILE", "RELAY_LOG_POS", "RELAY_THREAD", "RELOAD", "RENAME", "REPAIR", "REPEATABLE", "REPLICATION", "REQUIRE", "RESET", "RESTORE", "RETURNS", "REVOKE", "RLIKE", "ROLLUP", "ROWS", "ROW_FORMAT", "RTREE", "SAVEPOINT", "SECOND", "SECOND_MICROSECOND", "SEPARATOR", "SERIAL", "SERIALIZABLE", "SESSION", "SHARE", "SHOW", "SHUTDOWN", "SIGNED", "SIMPLE", "SLAVE", "SMALLINT", "SOME", "SONAME", "SOUNDS", "SPATIAL", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT", "SQL_CACHE", "SQL_CALC_FOUND_ROWS", "SQL_NO_CACHE", "SQL_SMALL_RESULT", "SQL_THREAD", "SSL", "START", "STARTING", "STATUS", "STOP", "STORAGE", "STRAIGHT_JOIN", "STRING", "STRIPED", "SUBJECT", "SUPER", "TABLES", "TABLESPACE", "TERMINATED", "TEXT", "TIME", "TIMESTAMP", "TINYBLOB", "TINYINT", "TINYTEXT", "TRAILING", "TRUE", "TRUNCATE", "TYPE", "TYPES", "UNCOMMITTED", "UNICODE", "UNLOCK", "UNSIGNED", "UNTIL", "USAGE", "USE", "USER", "USER_RESOURCES", "USE_FRM", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", "VALUE", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARIABLES", "VARYING", "WARNINGS", "WITH", "WORK", "WRITE", "X509", "YEAR", "YEAR_MONTH", "ZEROFILL", - 0 + nullptr }; diff --git a/src/drivers/mysql/MysqlPreparedStatement.h b/src/drivers/mysql/MysqlPreparedStatement.h index 5a8f488f..e3857d40 100644 --- a/src/drivers/mysql/MysqlPreparedStatement.h +++ b/src/drivers/mysql/MysqlPreparedStatement.h @@ -1,60 +1,58 @@ /* This file is part of the KDE project Copyright (C) 2006-2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_MYSQLPREPAREDSTATEMENT_H #define KDB_MYSQLPREPAREDSTATEMENT_H #include "KDbPreparedStatementInterface.h" #include "MysqlConnection_p.h" //! @todo 3.1 - unfinished: #define KDB_USE_MYSQL_STMT; for 3.0 we're using unoptimized version /*! Implementation of prepared statements for MySQL driver. */ class MysqlPreparedStatement : public KDbPreparedStatementInterface, public MysqlConnectionInternal { public: explicit MysqlPreparedStatement(MysqlConnectionInternal* conn); - virtual ~MysqlPreparedStatement(); + ~MysqlPreparedStatement() override; private: - virtual bool prepare(const KDbEscapedString& sql); - - virtual KDbSqlResult* execute( - KDbPreparedStatement::Type type, - const KDbField::List& selectFieldList, - KDbFieldList* insertFieldList, - const KDbPreparedStatementParameters& parameters, - bool *resultOwned) Q_REQUIRED_RESULT; + bool prepare(const KDbEscapedString& sql) override; + + KDbSqlResult *execute(KDbPreparedStatement::Type type, const KDbField::List &selectFieldList, + KDbFieldList *insertFieldList, + const KDbPreparedStatementParameters ¶meters, + bool *resultOwned) override Q_REQUIRED_RESULT; bool init(); void done(); #ifdef KDB_USE_MYSQL_STMT bool bindValue(KDbField *field, const QVariant& value, int arg); int m_realParamCount; MYSQL_STMT *m_statement; MYSQL_BIND *m_mysqlBind; #endif KDbEscapedString m_tempStatementString; bool m_resetRequired; Q_DISABLE_COPY(MysqlPreparedStatement) }; #endif diff --git a/src/drivers/postgresql/PostgresqlConnection.cpp b/src/drivers/postgresql/PostgresqlConnection.cpp index 873591c3..d3ea92e0 100644 --- a/src/drivers/postgresql/PostgresqlConnection.cpp +++ b/src/drivers/postgresql/PostgresqlConnection.cpp @@ -1,296 +1,296 @@ /* This file is part of the KDE project Copyright (C) 2003 Adam Pigg Copyright (C) 2010-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PostgresqlConnection.h" #include "PostgresqlConnection_p.h" #include "PostgresqlPreparedStatement.h" #include "PostgresqlCursor.h" #include "postgresql_debug.h" #include "KDbConnectionData.h" #include "KDbError.h" #include "KDbGlobal.h" #include "KDbVersionInfo.h" #include #include #define MIN_SERVER_VERSION_MAJOR 7 #define MIN_SERVER_VERSION_MINOR 1 inline static QByteArray parameter(PGconn *conn, const char *paramName) { return PQparameterStatus(conn, paramName); } PostgresqlTransactionData::PostgresqlTransactionData(KDbConnection *conn) : KDbTransactionData(conn) { } PostgresqlTransactionData::~PostgresqlTransactionData() { } //================================================================================== PostgresqlConnection::PostgresqlConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options) : KDbConnection(driver, connData, options) , d(new PostgresqlConnectionInternal(this)) { } PostgresqlConnection::~PostgresqlConnection() { //delete m_trans; destroy(); delete d; } KDbCursor* PostgresqlConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options) { return new PostgresqlCursor(this, sql, options); } KDbCursor* PostgresqlConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options) { return new PostgresqlCursor(this, query, options); } bool PostgresqlConnection::drv_connect() { return true; } bool PostgresqlConnection::drv_getServerVersion(KDbServerVersionInfo* version) { // http://www.postgresql.org/docs/8.4/static/libpq-status.html //postgresqlDebug() << "server_version:" << d->parameter("server_version"); version->setString(QString::fromLatin1(parameter(d->conn, "server_version"))); const int versionNumber = PQserverVersion(d->conn); if (versionNumber > 0) { version->setMajor(versionNumber / 10000); version->setMinor((versionNumber % 1000) / 100); version->setRelease(versionNumber % 100); } if ( version->major() < MIN_SERVER_VERSION_MAJOR || (version->major() == MIN_SERVER_VERSION_MAJOR && version->minor() < MIN_SERVER_VERSION_MINOR)) { postgresqlWarning() << QString::fromLatin1("PostgreSQL %d.%d is not supported and may not work. The minimum is %d.%d") .arg(version->major()).arg(version->minor()).arg(MIN_SERVER_VERSION_MAJOR).arg(MIN_SERVER_VERSION_MINOR); } return true; } bool PostgresqlConnection::drv_disconnect() { return true; } bool PostgresqlConnection::drv_getDatabasesList(QStringList* list) { return queryStringList(KDbEscapedString("SELECT datname FROM pg_database WHERE datallowconn = TRUE"), list); } bool PostgresqlConnection::drv_createDatabase(const QString &dbName) { return executeVoidSQL(KDbEscapedString("CREATE DATABASE ") + escapeIdentifier(dbName)); } QByteArray buildConnParameter(const QByteArray& key, const QVariant& value) { QByteArray result = key; //! @todo optimize result.replace('\\', "\\\\").replace('\'', "\\'"); return key + "='" + value.toString().toUtf8() + "' "; } bool PostgresqlConnection::drv_useDatabase(const QString &dbName, bool *cancelled, KDbMessageHandler* msgHandler) { Q_UNUSED(cancelled); Q_UNUSED(msgHandler); QByteArray conninfo; if (data().hostName().isEmpty() || 0 == QString::compare(data().hostName(), QLatin1String("localhost"), Qt::CaseInsensitive)) { if (!data().localSocketFileName().isEmpty()) { QFileInfo fileInfo(data().localSocketFileName()); if (fileInfo.exists()) { conninfo += buildConnParameter("host", fileInfo.absolutePath()); } } } else { const QHostAddress ip(data().hostName()); if (ip.isNull()) { conninfo += buildConnParameter("host", data().hostName()); } else { conninfo += buildConnParameter("hostaddr", ip.toString()); } } //Build up the connection string if (data().port() > 0) conninfo += buildConnParameter("port", data().port()); QString myDbName = dbName; if (myDbName.isEmpty()) myDbName = data().databaseName(); if (!myDbName.isEmpty()) conninfo += buildConnParameter("dbname", myDbName); if (!data().userName().isEmpty()) conninfo += buildConnParameter("user", data().userName()); if (!data().password().isEmpty()) conninfo += buildConnParameter("password", data().password()); //postgresqlDebug() << conninfo; //! @todo other parameters: connect_timeout, options, options, sslmode, sslcert, sslkey, sslrootcert, sslcrl, krbsrvname, gsslib, service // http://www.postgresql.org/docs/8.4/interactive/libpq-connect.html d->conn = PQconnectdb(conninfo.constData()); if (!d->connectionOK()) { PQfinish(d->conn); - d->conn = 0; + d->conn = nullptr; return false; } // pgsql 8.1 changed the default to no oids but we need them PGresult* result = PQexec(d->conn, "SET DEFAULT_WITH_OIDS TO ON"); int status = PQresultStatus(result); PQclear(result); // initialize encoding result = PQexec(d->conn, "SET CLIENT_ENCODING TO 'UNICODE'"); status = PQresultStatus(result); PQclear(result); d->unicode = status == PGRES_COMMAND_OK; result = PQexec(d->conn, "SET DATESTYLE TO 'ISO'"); status = PQresultStatus(result); if (status != PGRES_COMMAND_OK) { postgresqlWarning() << "Failed to set DATESTYLE to 'ISO':" << PQerrorMessage(d->conn); } //! @todo call on first use of SOUNDEX(), etc.; //! it's not possible now because we don't have connection context in KDbFunctionExpressionData if (!d->fuzzystrmatchExtensionCreated) { d->fuzzystrmatchExtensionCreated = drv_executeVoidSQL(KDbEscapedString("CREATE EXTENSION IF NOT EXISTS fuzzystrmatch")); } PQclear(result); return true; } bool PostgresqlConnection::drv_closeDatabase() { PQfinish(d->conn); - d->conn = 0; + d->conn = nullptr; return true; } bool PostgresqlConnection::drv_dropDatabase(const QString &dbName) { //postgresqlDebug() << dbName; //! @todo Maybe should check that dbname is no the currentdb if (executeVoidSQL(KDbEscapedString("DROP DATABASE ") + escapeIdentifier(dbName))) return true; return false; } KDbSqlResult* PostgresqlConnection::drv_executeSQL(const KDbEscapedString& sql) { PGresult* result = d->executeSQL(sql); const ExecStatusType status = PQresultStatus(result); if (status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK) { return new PostgresqlSqlResult(this, result, status); } storeResult(result, status); return nullptr; } bool PostgresqlConnection::drv_executeVoidSQL(const KDbEscapedString& sql) { PGresult* result = d->executeSQL(sql); const ExecStatusType status = PQresultStatus(result); d->storeResultAndClear(&m_result, &result, status); return status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK; } bool PostgresqlConnection::drv_isDatabaseUsed() const { return d->conn; } tristate PostgresqlConnection::drv_containsTable(const QString &tableName) { return resultExists(KDbEscapedString("SELECT 1 FROM pg_class WHERE relkind='r' AND relname LIKE %1") .arg(escapeString(tableName))); } QString PostgresqlConnection::serverResultName() const { if (m_result.code() >= 0 && m_result.code() <= PGRES_SINGLE_TUPLE) { return QString::fromLatin1(PQresStatus(ExecStatusType(m_result.code()))); } return QString(); } KDbPreparedStatementInterface* PostgresqlConnection::prepareStatementInternal() { return new PostgresqlPreparedStatement(d); } KDbEscapedString PostgresqlConnection::escapeString(const QByteArray& str) const { int error; d->escapingBuffer.resize(str.length() * 2 + 1); size_t count = PQescapeStringConn(d->conn, d->escapingBuffer.data(), str.constData(), str.length(), &error); d->escapingBuffer.resize(count); if (error != 0) { d->storeResult(const_cast(&m_result)); const_cast(m_result) = KDbResult(ERR_INVALID_ENCODING, PostgresqlConnection::tr("Escaping string failed. Invalid multibyte encoding.")); return KDbEscapedString(); } return KDbEscapedString("\'") + d->escapingBuffer + '\''; } KDbEscapedString PostgresqlConnection::escapeString(const QString& str) const { return escapeString(d->unicode ? str.toUtf8() : str.toLocal8Bit()); } void PostgresqlConnection::storeResult(PGresult *pgResult, ExecStatusType execStatus) { d->storeResultAndClear(&m_result, &pgResult, execStatus); } diff --git a/src/drivers/postgresql/PostgresqlConnection.h b/src/drivers/postgresql/PostgresqlConnection.h index e2fb468b..42ddf2d7 100644 --- a/src/drivers/postgresql/PostgresqlConnection.h +++ b/src/drivers/postgresql/PostgresqlConnection.h @@ -1,105 +1,105 @@ /* This file is part of the KDE project Copyright (C) 2003 Adam Pigg Copyright (C) 2010-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_POSTGRESQLCONNECTION_H #define KDB_POSTGRESQLCONNECTION_H #include "KDbConnection.h" #include class PostgresqlConnectionInternal; //! @internal class PostgresqlTransactionData : public KDbTransactionData { public: explicit PostgresqlTransactionData(KDbConnection *conn); ~PostgresqlTransactionData(); private: Q_DISABLE_COPY(PostgresqlTransactionData) }; class PostgresqlConnection : public KDbConnection { Q_DECLARE_TR_FUNCTIONS(PostgresqlConnection) public: - virtual ~PostgresqlConnection(); + ~PostgresqlConnection() override; //! @return a new query based on a query statement - KDbCursor* prepareQuery(const KDbEscapedString& sql, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(const KDbEscapedString &sql, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; //! @return a new query based on a query object - KDbCursor* prepareQuery(KDbQuerySchema* query, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(KDbQuerySchema *query, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; - KDbPreparedStatementInterface* prepareStatementInternal() Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbPreparedStatementInterface *prepareStatementInternal() override Q_REQUIRED_RESULT; /*! Connection-specific string escaping. */ - virtual KDbEscapedString escapeString(const QString& str) const; + KDbEscapedString escapeString(const QString& str) const override; virtual KDbEscapedString escapeString(const QByteArray& str) const; private: /*! Used by driver */ PostgresqlConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options); //! @return true if currently connected to a database, ignoring the m_is_connected flag. - virtual bool drv_isDatabaseUsed() const; + bool drv_isDatabaseUsed() const override; //! Noop: we tell we are connected, but we wont actually connect until we use a database. - virtual bool drv_connect(); - virtual bool drv_getServerVersion(KDbServerVersionInfo* version); + bool drv_connect() override; + bool drv_getServerVersion(KDbServerVersionInfo* version) override; //! Noop: we tell we have disconnected, but it is actually handled by closeDatabase. - virtual bool drv_disconnect(); + bool drv_disconnect() override; //! @return a list of database names - virtual bool drv_getDatabasesList(QStringList* list); + bool drv_getDatabasesList(QStringList* list) override; //! Create a new database - virtual bool drv_createDatabase(const QString &dbName = QString()); + bool drv_createDatabase(const QString &dbName = QString()) override; //! Uses database. Note that if data().localSocketFileName() is not empty, //! only directory path is used for connecting; the local socket's filename stays ".s.PGSQL.5432". - virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = 0, - KDbMessageHandler* msgHandler = 0); + bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = nullptr, + KDbMessageHandler* msgHandler = nullptr) override; //! Close the database connection - virtual bool drv_closeDatabase(); + bool drv_closeDatabase() override; //! Drops the given database - virtual bool drv_dropDatabase(const QString &dbName = QString()); + bool drv_dropDatabase(const QString &dbName = QString()) override; //! Executes an SQL statement - KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; - bool drv_executeVoidSQL(const KDbEscapedString& sql) Q_DECL_OVERRIDE; + KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) override Q_REQUIRED_RESULT; + bool drv_executeVoidSQL(const KDbEscapedString& sql) override; //! Implemented for KDbResultable - virtual QString serverResultName() const; + QString serverResultName() const override; //! @todo move this somewhere to low level class (MIGRATION?) - virtual tristate drv_containsTable(const QString &tableName); + tristate drv_containsTable(const QString &tableName) override; void storeResult(PGresult *pgResult, ExecStatusType execStatus); PostgresqlConnectionInternal * const d; friend class PostgresqlDriver; friend class PostgresqlCursorData; friend class PostgresqlTransactionData; friend class PostgresqlSqlResult; Q_DISABLE_COPY(PostgresqlConnection) }; #endif diff --git a/src/drivers/postgresql/PostgresqlConnection_p.cpp b/src/drivers/postgresql/PostgresqlConnection_p.cpp index e24a682e..9353646d 100644 --- a/src/drivers/postgresql/PostgresqlConnection_p.cpp +++ b/src/drivers/postgresql/PostgresqlConnection_p.cpp @@ -1,108 +1,108 @@ /* This file is part of the KDE project Copyright (C) 2005 Adam Pigg Copyright (C) 2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PostgresqlConnection_p.h" PostgresqlConnectionInternal::PostgresqlConnectionInternal(KDbConnection *_conn) : KDbConnectionInternal(_conn) - , conn(0) + , conn(nullptr) , unicode(true) // will be set in PostgresqlConnection::drv_useDatabase() { escapingBuffer.reserve(0x8000); } PostgresqlConnectionInternal::~PostgresqlConnectionInternal() { } //static QString PostgresqlConnectionInternal::serverResultName(int resultCode) { return QString::fromLatin1(PQresStatus(static_cast(resultCode))); } void PostgresqlConnectionInternal::storeResultAndClear(KDbResult *result, PGresult **pgResult, ExecStatusType execStatus) { QByteArray msg(PQresultErrorMessage(*pgResult)); if (msg.endsWith('\n')) { msg.chop(1); } result->setServerMessage(QString::fromLatin1(msg)); if (*pgResult) { result->setServerErrorCode(execStatus); PQclear(*pgResult); *pgResult = nullptr; } } void PostgresqlConnectionInternal::storeResult(KDbResult *result) { QByteArray msg(PQerrorMessage(conn)); if (msg.endsWith('\n')) { msg.chop(1); } result->setServerMessage(QString::fromLatin1(msg)); } PGresult* PostgresqlConnectionInternal::executeSQL(const KDbEscapedString& sql) { //! @todo consider using binary mode with PQexecParams() return PQexec(conn, sql.toByteArray().constData()); } //-------------------------------------- PostgresqlCursorData::PostgresqlCursorData(KDbConnection* connection) : PostgresqlConnectionInternal(connection), res(nullptr), resultStatus(PGRES_FATAL_ERROR) { conn = static_cast(connection)->d->conn; } PostgresqlCursorData::~PostgresqlCursorData() { } KDbField* PostgresqlSqlResult::createField(const QString &tableName, int index) { Q_UNUSED(tableName) QScopedPointer f(static_cast(field(index))); if (!f) { return nullptr; } const QString caption(f->name()); QString realFieldName(KDb::stringToIdentifier(caption.toLower())); const PostgresqlDriver *pgdriver = static_cast(conn->driver()); const KDbField::Type kdbType = pgdriver->pgsqlToKDbType( PQftype(result, index), PQfmod(result, index), nullptr); KDbField *kdbField = new KDbField(realFieldName, kdbType); kdbField->setCaption(caption); if (KDbField::isTextType(kdbType)) { const int len = f->length(); if (len != -1) { kdbField->setMaxLength(len); } } //! @todo use information_schema.table_constraints to get constraints //copyConstraints(...); //! @todo use information_schema.columns to get options //copyOptions(...); return kdbField; } diff --git a/src/drivers/postgresql/PostgresqlConnection_p.h b/src/drivers/postgresql/PostgresqlConnection_p.h index 28e37b44..5624d37c 100644 --- a/src/drivers/postgresql/PostgresqlConnection_p.h +++ b/src/drivers/postgresql/PostgresqlConnection_p.h @@ -1,204 +1,204 @@ /* This file is part of the KDE project Copyright (C) 2005 Adam Pigg Copyright (C) 2010-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_POSTGRESQLCONNECTION_P_H #define KDB_POSTGRESQLCONNECTION_P_H #include "KDbConnection_p.h" #include "PostgresqlConnection.h" #include "PostgresqlDriver.h" #include "KDbResult.h" #include "KDbSqlField.h" #include "KDbSqlRecord.h" #include "KDbSqlResult.h" #include "KDbSqlString.h" #include #include class KDbEscapedString; class PostgresqlConnectionInternal : public KDbConnectionInternal { public: explicit PostgresqlConnectionInternal(KDbConnection *connection); virtual ~PostgresqlConnectionInternal(); //! Executes query for a raw SQL statement @a sql on the database PGresult* executeSQL(const KDbEscapedString& sql); static QString serverResultName(int resultCode); void storeResultAndClear(KDbResult *result, PGresult **pgResult, ExecStatusType execStatus); void storeResult(KDbResult *result); //! @return true if status of connection is "OK". /*! From http://www.postgresql.org/docs/8.4/static/libpq-status.html: "Only two of these are seen outside of an asynchronous connection procedure: CONNECTION_OK and CONNECTION_BAD." */ inline bool connectionOK() { return CONNECTION_OK == PQstatus(conn); } PGconn *conn; bool unicode; QByteArray escapingBuffer; bool fuzzystrmatchExtensionCreated = false; private: Q_DISABLE_COPY(PostgresqlConnectionInternal) }; //! Internal PostgreSQL cursor data. /*! Provides a low-level abstraction for iterating over result sets. */ class PostgresqlCursorData : public PostgresqlConnectionInternal { public: explicit PostgresqlCursorData(KDbConnection* connection); - virtual ~PostgresqlCursorData(); + ~PostgresqlCursorData() override; PGresult* res; ExecStatusType resultStatus; private: Q_DISABLE_COPY(PostgresqlCursorData) }; class PostgresqlSqlField : public KDbSqlField { public: inline PostgresqlSqlField(const PGresult *r, int n) : result(r), number(n) { } //! @return column name - inline QString name() Q_DECL_OVERRIDE { + inline QString name() override { //! @todo UTF8? return QString::fromLatin1(PQfname(result, number)); } - inline int type() Q_DECL_OVERRIDE { + inline int type() override { return static_cast(PQftype(result, number)); } - inline int length() Q_DECL_OVERRIDE { + inline int length() override { return PostgresqlDriver::pqfmodToLength(PQfmod(result, number)); } const PGresult * const result; const int number; private: Q_DISABLE_COPY(PostgresqlSqlField) }; class PostgresqlSqlRecord : public KDbSqlRecord { public: inline PostgresqlSqlRecord(const PGresult *res, int r) : result(res), record(r) { } - inline ~PostgresqlSqlRecord() { + inline ~PostgresqlSqlRecord() override { } - inline QString stringValue(int index) Q_DECL_OVERRIDE { + inline QString stringValue(int index) override { return PQgetisnull(result, record, index) ? QString() : QString::fromUtf8(PQgetvalue(result, record, index), PQgetlength(result, record, index)); } - inline KDbSqlString cstringValue(int index) Q_DECL_OVERRIDE { + inline KDbSqlString cstringValue(int index) override { return PQgetisnull(result, record, index) ? KDbSqlString() : KDbSqlString(PQgetvalue(result, record, index), PQgetlength(result, record, index)); } - inline QByteArray toByteArray(int index) Q_DECL_OVERRIDE { + inline QByteArray toByteArray(int index) override { return PQgetisnull(result, record, index) ? QByteArray() : QByteArray(PQgetvalue(result, record, index), PQgetlength(result, record, index)); } private: const PGresult * const result; const int record; Q_DISABLE_COPY(PostgresqlSqlRecord) }; class PostgresqlSqlResult : public KDbSqlResult { public: inline PostgresqlSqlResult(PostgresqlConnection *c, PGresult* r, ExecStatusType status) : conn(c), result(r), resultStatus(status), recordToFetch(0), recordsCount(PQntuples(r)) { Q_ASSERT(c); } - inline ~PostgresqlSqlResult() { + inline ~PostgresqlSqlResult() override { PQclear(result); } - inline KDbConnection *connection() const Q_DECL_OVERRIDE { + inline KDbConnection *connection() const override { return conn; } - inline int fieldsCount() Q_DECL_OVERRIDE { + inline int fieldsCount() override { return PQnfields(result); } - inline KDbSqlField *field(int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlField *field(int index) override Q_REQUIRED_RESULT { return new PostgresqlSqlField(result, index); } - KDbField *createField(const QString &tableName, int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbField *createField(const QString &tableName, int index) override Q_REQUIRED_RESULT; - inline KDbSqlRecord* fetchRecord() Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlRecord* fetchRecord() override Q_REQUIRED_RESULT { return recordToFetch < recordsCount ? new PostgresqlSqlRecord(result, recordToFetch++) : nullptr; } - inline KDbResult lastResult() Q_DECL_OVERRIDE { + inline KDbResult lastResult() override { KDbResult r; if (resultStatus == PGRES_TUPLES_OK || resultStatus == PGRES_COMMAND_OK) { return r; } QByteArray msg(PQresultErrorMessage(result)); if (msg.endsWith('\n')) { msg.chop(1); } r.setServerMessage(QString::fromLatin1(msg)); r.setServerErrorCode(resultStatus); return r; } //! @return the oid of the last insert - only works if there was insert of 1 row - inline quint64 lastInsertRecordId() Q_DECL_OVERRIDE { + inline quint64 lastInsertRecordId() override { // InvalidOid == 0 means error const Oid oid = PQoidValue(result); return oid == 0 ? std::numeric_limits::max() : static_cast(oid); } private: //! @return a KDb type for PostgreSQL type //! @todo prompt user if necessary? KDbField::Type type(const QString& tableName, PostgresqlSqlField *field); PostgresqlConnection* const conn; PGresult* result; ExecStatusType resultStatus; int recordToFetch; int recordsCount; Q_DISABLE_COPY(PostgresqlSqlResult) }; #endif diff --git a/src/drivers/postgresql/PostgresqlCursor.cpp b/src/drivers/postgresql/PostgresqlCursor.cpp index 10f161c5..9a43e6d4 100644 --- a/src/drivers/postgresql/PostgresqlCursor.cpp +++ b/src/drivers/postgresql/PostgresqlCursor.cpp @@ -1,353 +1,353 @@ /* This file is part of the KDE project Copyright (C) 2003 Adam Pigg Copyright (C) 2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PostgresqlCursor.h" #include "PostgresqlConnection.h" #include "PostgresqlConnection_p.h" #include "PostgresqlDriver.h" #include "postgresql_debug.h" #include "KDbError.h" #include "KDbGlobal.h" #include "KDbRecordData.h" // Constructor based on query statement PostgresqlCursor::PostgresqlCursor(KDbConnection* conn, const KDbEscapedString& sql, KDbCursor::Options options) : KDbCursor(conn, sql, options | KDbCursor::Option::Buffered) , m_numRows(0) , d(new PostgresqlCursorData(conn)) { } //================================================================================== //Constructor base on query object PostgresqlCursor::PostgresqlCursor(KDbConnection* conn, KDbQuerySchema* query, KDbCursor::Options options) : KDbCursor(conn, query, options | KDbCursor::Option::Buffered) , m_numRows(0) , d(new PostgresqlCursorData(conn)) { } //================================================================================== //Destructor PostgresqlCursor::~PostgresqlCursor() { close(); delete d; } //================================================================================== //Create a cursor result set bool PostgresqlCursor::drv_open(const KDbEscapedString& sql) { d->res = d->executeSQL(sql); d->resultStatus = PQresultStatus(d->res); if (d->resultStatus != PGRES_TUPLES_OK && d->resultStatus != PGRES_COMMAND_OK) { storeResultAndClear(&d->res, d->resultStatus); return false; } m_fieldsToStoreInRecord = PQnfields(d->res); m_fieldCount = m_fieldsToStoreInRecord - (containsRecordIdInfo() ? 1 : 0); m_numRows = PQntuples(d->res); m_records_in_buf = m_numRows; m_buffering_completed = true; // get real types for all fields PostgresqlDriver* drv = static_cast(connection()->driver()); m_realTypes.resize(m_fieldsToStoreInRecord); m_realLengths.resize(m_fieldsToStoreInRecord); for (int i = 0; i < int(m_fieldsToStoreInRecord); i++) { const int pqtype = PQftype(d->res, i); const int pqfmod = PQfmod(d->res, i); m_realTypes[i] = drv->pgsqlToKDbType(pqtype, pqfmod, &m_realLengths[i]); } return true; } //================================================================================== //Delete objects bool PostgresqlCursor::drv_close() { PQclear(d->res); return true; } //================================================================================== //Gets the next record...does not need to do much, just return fetchend if at end of result set void PostgresqlCursor::drv_getNextRecord() { if (at() >= qint64(m_numRows)) { m_fetchResult = FetchEnd; } else if (at() < 0) { // control will reach here only when at() < 0 ( which is usually -1 ) // -1 is same as "1 beyond the End" m_fetchResult = FetchEnd; } else { // 0 <= at() < m_numRows m_fetchResult = FetchOK; } } //================================================================================== //Check the current position is within boundaries #if 0 void PostgresqlCursor::drv_getPrevRecord() { if (at() < m_res->size() && at() >= 0) { m_fetchResult = FetchOK; } else if (at() >= m_res->size()) { m_fetchResult = FetchEnd; } else { m_fetchResult = FetchError; } } #endif //================================================================================== //Return the value for a given column for the current record QVariant PostgresqlCursor::value(int pos) { if (pos < m_fieldCount) return pValue(pos); else return QVariant(); } #if 0 inline QVariant pgsqlCStrToVariant(const pqxx::result::field& r) { switch (r.type()) { case BOOLOID: return QString::fromLatin1(r.c_str(), r.size()) == "true"; //!< @todo check formatting case INT2OID: case INT4OID: case INT8OID: return r.as(int()); case FLOAT4OID: case FLOAT8OID: case NUMERICOID: return r.as(double()); case DATEOID: return QString::fromUtf8(r.c_str(), r.size()); //!< @todo check formatting case TIMEOID: return QString::fromUtf8(r.c_str(), r.size()); //!< @todo check formatting case TIMESTAMPOID: return QString::fromUtf8(r.c_str(), r.size()); //!< @todo check formatting case BYTEAOID: return KDb::pgsqlByteaToByteArray(r.c_str(), r.size()); case BPCHAROID: case VARCHAROID: case TEXTOID: return QString::fromUtf8(r.c_str(), r.size()); //utf8? default: return QString::fromUtf8(r.c_str(), r.size()); //utf8? } } #endif static inline bool hasTimeZone(const QString& s) { return s.at(s.length() - 3) == QLatin1Char('+') || s.at(s.length() - 3) == QLatin1Char('-'); } static inline QVariant convertToKDbType(bool convert, const QVariant &value, KDbField::Type kdbType) { return (convert && kdbType != KDbField::InvalidType) ? KDbField::convertToType(value, kdbType) : value; } static inline QTime timeFromData(const char *data, int len) { if (len == 0) { return QTime(); } QString s(QString::fromLatin1(data, len)); if (hasTimeZone(s)) { s.chop(3); // skip timezone return QTime::fromString(s, Qt::ISODate); } return QTime::fromString(s, Qt::ISODate); } static inline QDateTime dateTimeFromData(const char *data, int len) { if (len < 10 /*ISO Date*/) { return QDateTime(); } QString s(QString::fromLatin1(data, len)); if (hasTimeZone(s)) { s.chop(3); // skip timezone if (s.isEmpty()) { return QDateTime(); } } if (s.at(s.length() - 3).isPunct()) { // fix ms, should be three digits s += QLatin1Char('0'); } return QDateTime::fromString(s, Qt::ISODate); } static inline QByteArray byteArrayFromData(const char *data) { size_t unescapedLen; unsigned char *unescapedData = PQunescapeBytea((const unsigned char*)data, &unescapedLen); const QByteArray result((const char*)unescapedData, unescapedLen); //! @todo avoid deep copy; QByteArray does not allow passing ownership of data; maybe copy PQunescapeBytea code? PQfreemem(unescapedData); return result; } //================================================================================== //Return the value for a given column for the current record - Private const version QVariant PostgresqlCursor::pValue(int pos) const { // postgresqlWarning() << "PostgresqlCursor::value - ERROR: requested position is greater than the number of fields"; const qint64 row = at(); KDbField *f = (m_visibleFieldsExpanded && pos < qMin(m_visibleFieldsExpanded->count(), m_fieldCount)) ? m_visibleFieldsExpanded->at(pos)->field() : nullptr; // postgresqlDebug() << "pos:" << pos; const KDbField::Type type = m_realTypes[pos]; const KDbField::Type kdbType = f ? f->type() : KDbField::InvalidType; // cache: evaluating type of expressions can be expensive if (PQgetisnull(d->res, row, pos) || kdbType == KDbField::Null) { return QVariant(); } const char *data = PQgetvalue(d->res, row, pos); int len = PQgetlength(d->res, row, pos); switch (type) { // from most to least frequently used types: case KDbField::Text: case KDbField::LongText: { const int maxLength = m_realLengths[pos]; if (maxLength > 0) { len = qMin(len, maxLength); } return convertToKDbType(!KDbField::isTextType(kdbType), d->unicode ? QString::fromUtf8(data, len) : QString::fromLatin1(data, len), kdbType); } case KDbField::Integer: return convertToKDbType(!KDbField::isIntegerType(kdbType), atoi(data), // the fastest way kdbType); case KDbField::Boolean: return convertToKDbType(kdbType != KDbField::Boolean, bool(data[0] == 't'), kdbType); case KDbField::BigInteger: return convertToKDbType(kdbType != KDbField::BigInteger, (data[0] == '-') ? QByteArray::fromRawData(data, len).toLongLong() : QByteArray::fromRawData(data, len).toULongLong(), kdbType); case KDbField::Double: //! @todo support equivalent of QSql::NumericalPrecisionPolicy, especially for NUMERICOID return convertToKDbType(!KDbField::isFPNumericType(kdbType), QByteArray::fromRawData(data, len).toDouble(), kdbType); case KDbField::Date: return convertToKDbType(kdbType != KDbField::Date, (len == 0) ? QVariant(QDate()) : QVariant(QDate::fromString(QLatin1String(QByteArray::fromRawData(data, len)), Qt::ISODate)), kdbType); case KDbField::Time: return convertToKDbType(kdbType != KDbField::Time, timeFromData(data, len), kdbType); case KDbField::DateTime: return convertToKDbType(kdbType != KDbField::DateTime, dateTimeFromData(data, len), kdbType); case KDbField::BLOB: return convertToKDbType(kdbType != KDbField::BLOB, byteArrayFromData(data), kdbType); default: postgresqlWarning() << "PostgresqlCursor::pValue() data type?"; } return QVariant(); } //================================================================================== //Return the current record as a char** const char** PostgresqlCursor::recordData() const { //! @todo - return 0; + return nullptr; } //================================================================================== //Store the current record in [data] bool PostgresqlCursor::drv_storeCurrentRecord(KDbRecordData* data) const { // postgresqlDebug() << "POSITION IS" << (long)m_at; for (int i = 0; i < m_fieldsToStoreInRecord; i++) (*data)[i] = pValue(i); return true; } //================================================================================== // /*void PostgresqlCursor::drv_clearServerResult() { //! @todo PostgresqlCursor: stuff with server results }*/ //================================================================================== //Add the current record to the internal buffer //Implementation required but no need in this driver //Result set is a buffer so do not need another void PostgresqlCursor::drv_appendCurrentRecordToBuffer() { } //================================================================================== //Move internal pointer to internal buffer +1 //Implementation required but no need in this driver void PostgresqlCursor::drv_bufferMovePointerNext() { } //================================================================================== //Move internal pointer to internal buffer -1 //Implementation required but no need in this driver void PostgresqlCursor::drv_bufferMovePointerPrev() { } //================================================================================== //Move internal pointer to internal buffer to N //Implementation required but no need in this driver void PostgresqlCursor::drv_bufferMovePointerTo(qint64 to) { Q_UNUSED(to); } void PostgresqlCursor::storeResultAndClear(PGresult **pgResult, ExecStatusType execStatus) { d->storeResultAndClear(&m_result, pgResult, execStatus); } diff --git a/src/drivers/postgresql/PostgresqlCursor.h b/src/drivers/postgresql/PostgresqlCursor.h index 74607e7b..40c2d44c 100644 --- a/src/drivers/postgresql/PostgresqlCursor.h +++ b/src/drivers/postgresql/PostgresqlCursor.h @@ -1,65 +1,65 @@ /* This file is part of the KDE project Copyright (C) 2003 Adam Pigg Copyright (C) 2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_POSTGRESQLCURSOR_H #define KDB_POSTGRESQLCURSOR_H #include "KDbCursor.h" #include "KDbField.h" #include class KDbConnection; class PostgresqlCursorData; class PostgresqlCursor: public KDbCursor { public: explicit PostgresqlCursor(KDbConnection* conn, const KDbEscapedString& sql, KDbCursor::Options options = KDbCursor::Option::None); PostgresqlCursor(KDbConnection* conn, KDbQuerySchema* query, KDbCursor::Options options = KDbCursor::Option::None); - virtual ~PostgresqlCursor(); + ~PostgresqlCursor() override; - virtual QVariant value(int pos); - virtual const char** recordData() const; - virtual bool drv_storeCurrentRecord(KDbRecordData* data) const; - virtual bool drv_open(const KDbEscapedString& sql); - virtual bool drv_close(); - virtual void drv_getNextRecord(); - virtual void drv_appendCurrentRecordToBuffer(); - virtual void drv_bufferMovePointerNext(); - virtual void drv_bufferMovePointerPrev(); - virtual void drv_bufferMovePointerTo(qint64 to); + QVariant value(int pos) override; + const char** recordData() const override; + bool drv_storeCurrentRecord(KDbRecordData* data) const override; + bool drv_open(const KDbEscapedString& sql) override; + bool drv_close() override; + void drv_getNextRecord() override; + void drv_appendCurrentRecordToBuffer() override; + void drv_bufferMovePointerNext() override; + void drv_bufferMovePointerPrev() override; + void drv_bufferMovePointerTo(qint64 to) override; void storeResultAndClear(PGresult **pgResult, ExecStatusType execStatus); private: QVariant pValue(int pos)const; unsigned long m_numRows; QVector m_realTypes; QVector m_realLengths; PostgresqlCursorData * const d; Q_DISABLE_COPY(PostgresqlCursor) }; #endif diff --git a/src/drivers/postgresql/PostgresqlDriver.h b/src/drivers/postgresql/PostgresqlDriver.h index 290a2bbc..c8f47000 100644 --- a/src/drivers/postgresql/PostgresqlDriver.h +++ b/src/drivers/postgresql/PostgresqlDriver.h @@ -1,152 +1,152 @@ /* This file is part of the KDE project Copyright (C) 2003 Adam Pigg Copyright (C) 2010-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_POSTGRESQLDRIVER_H #define KDB_POSTGRESQLDRIVER_H #include "KDbDriver.h" class KDbConnection; //! PostgreSQL database driver. class PostgresqlDriver : public KDbDriver { Q_OBJECT public: PostgresqlDriver(QObject *parent, const QVariantList &args); - virtual ~PostgresqlDriver(); + ~PostgresqlDriver() override; //! @todo implement - virtual bool isSystemObjectName(const QString& name) const; + bool isSystemObjectName(const QString& name) const override; - virtual bool isSystemDatabaseName(const QString& name) const; + bool isSystemDatabaseName(const QString& name) const override; //! Escape a string for use as a value - virtual KDbEscapedString escapeString(const QString& str) const; - virtual KDbEscapedString escapeString(const QByteArray& str) const; + KDbEscapedString escapeString(const QString& str) const override; + KDbEscapedString escapeString(const QByteArray& str) const override; //! Overrides the default implementation to allow for NUMERIC type natively - virtual QString sqlTypeName(KDbField::Type type, const KDbField &field) const; + QString sqlTypeName(KDbField::Type type, const KDbField &field) const override; //! Escape BLOB value @a array - virtual KDbEscapedString escapeBLOB(const QByteArray& array) const; + KDbEscapedString escapeBLOB(const QByteArray& array) const override; //! Converts information converted from PQfmod() to length. -1 if missing. inline static int pqfmodToLength(int pqfmod) { int len; if (pqfmod > 0) { const int PGSQL_VAR_HDRSZ = 4; len = pqfmod - PGSQL_VAR_HDRSZ; //!< See e.g. postgis_get_char_length() } else { len = -1; } return len; } //! Uses information obtained from PQfmod() and adjust type @a t if possible. //! Also sets @a maxTextLength. //! @todo using pqfmod not tested //! @todo more types such as decimal inline static KDbField::Type typeForSize(KDbField::Type t, int pqfmod, int *maxTextLength) { KDbField::Type newType = t; if (maxTextLength) { *maxTextLength = -1; } if (t == KDbField::Integer) { if (pqfmod == 1) { newType = KDbField::Byte; } else if (pqfmod == 2) { newType = KDbField::ShortInteger; } else if (pqfmod == 8) { newType = KDbField::BigInteger; } } else if (t == KDbField::LongText) { const int len = pqfmodToLength(pqfmod); if (len > 0 && len <= 255) { newType = KDbField::Text; if (maxTextLength) { *maxTextLength = len; } } } return newType; } //! @return KDb field type for PostgreSQL type @a pqtype and modifier @a pqfmod. //! If type cannot be found KDbField::InvalidType is returned. //! Used in cursors to speed up data conversion. inline KDbField::Type pgsqlToKDbType(int pqtype, int pqfmod, int *maxTextLength) const { KDbField::Type t = m_pgsqlToKDbTypes.value(pqtype, KDbField::InvalidType); return typeForSize(t, pqfmod, maxTextLength); } //! Generates native (driver-specific) HEX() function call. //! Uses UPPER(ENCODE(val, 'hex')). //! See http://www.postgresql.org/docs/9.3/static/functions-string.html#FUNCTIONS-STRING-OTHER */ - virtual KDbEscapedString hexFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString hexFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) IFNULL() function call. //! Uses COALESCE(). - virtual KDbEscapedString ifnullFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString ifnullFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) LENGTH() function call. //! For text types default LENGTH(val) is used, for BLOBs OCTET_LENGTH(val) is used because //! LENGTH(val) for BLOB returns number of bits. - virtual KDbEscapedString lengthFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString lengthFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) GREATEST() and LEAST() function calls. //! Since PostgreSQL's LEAST()/GREATEST() function ignores NULL values, it only returns NULL //! if all the expressions evaluate to NULL. So this is used for F(v0,..,vN): //! (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE F(v0,..,vN) END) //! where F == GREATEST or LEAST. - virtual KDbEscapedString greatestOrLeastFunctionToString(const QString &name, - const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString greatestOrLeastFunctionToString(const QString &name, + const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) UNICODE() function call. //! Uses ASCII(X). - virtual KDbEscapedString unicodeFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString unicodeFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; protected: - virtual QString drv_escapeIdentifier(const QString& str) const; - virtual QByteArray drv_escapeIdentifier(const QByteArray& str) const; - virtual KDbConnection *drv_createConnection(const KDbConnectionData& connData, - const KDbConnectionOptions &options); - virtual bool drv_isSystemFieldName(const QString& name)const; + QString drv_escapeIdentifier(const QString& str) const override; + QByteArray drv_escapeIdentifier(const QByteArray& str) const override; + KDbConnection *drv_createConnection(const KDbConnectionData& connData, + const KDbConnectionOptions &options) override; + bool drv_isSystemFieldName(const QString& name)const override; private: void initPgsqlToKDbMap(); static const char *m_keywords[]; QMap m_pgsqlToKDbTypes; Q_DISABLE_COPY(PostgresqlDriver) }; #endif // KDB_DRIVER_POSTGRESQLDRIVER_H diff --git a/src/drivers/postgresql/PostgresqlKeywords.cpp b/src/drivers/postgresql/PostgresqlKeywords.cpp index d451012e..40f8f001 100644 --- a/src/drivers/postgresql/PostgresqlKeywords.cpp +++ b/src/drivers/postgresql/PostgresqlKeywords.cpp @@ -1,261 +1,261 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004 Jarosław Staniek This file has been automatically generated from tools/sql_keywords/sql_keywords.sh and postgresql-7.4.6/src/backend/parser/keywords.c. Please edit the sql_keywords.sh, not this file! This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "PostgresqlDriver.h" const char* PostgresqlDriver::m_keywords[] = { "ABORT", "ABSOLUTE", "ACCESS", "ACTION", "ADD", "AGGREGATE", "ALTER", "ANALYSE", "ANALYZE", "ANY", "ARRAY", "ASSERTION", "ASSIGNMENT", "AT", "AUTHORIZATION", "BACKWARD", "BIGINT", "BINARY", "BIT", "BOOLEAN", "BOTH", "CACHE", "CALLED", "CAST", "CHAIN", "CHAR", "CHARACTER", "CHARACTERISTICS", "CHECKPOINT", "CLASS", "CLOSE", "CLUSTER", "COALESCE", "COLUMN", "COMMENT", "COMMITTED", "CONSTRAINTS", "CONVERSION", "CONVERT", "COPY", "CREATEDB", "CREATEUSER", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "CYCLE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULTS", "DEFERRABLE", "DEFERRED", "DEFINER", "DELIMITER", "DELIMITERS", "DO", "DOMAIN", "DOUBLE", "EACH", "ENCODING", "ENCRYPTED", "ESCAPE", "EXCEPT", "EXCLUDING", "EXCLUSIVE", "EXECUTE", "EXISTS", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FIRST", "FLOAT", "FORCE", "FORWARD", "FREEZE", "FUNCTION", "GLOBAL", "GRANT", "HANDLER", "HOLD", "HOUR", "ILIKE", "IMMEDIATE", "IMMUTABLE", "IMPLICIT", "INCLUDING", "INCREMENT", "INHERITS", "INITIALLY", "INOUT", "INPUT", "INSENSITIVE", "INSTEAD", "INT", "INTERSECT", "INTERVAL", "INVOKER", "ISNULL", "ISOLATION", "LANCOMPILER", "LANGUAGE", "LAST", "LEADING", "LEVEL", "LISTEN", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCK", "MAXVALUE", "MINUTE", "MINVALUE", "MODE", "MONTH", "MOVE", "NAMES", "NATIONAL", "NCHAR", "NEW", "NEXT", "NO", "NOCREATEDB", "NOCREATEUSER", "NONE", "NOTHING", "NOTIFY", "NOTNULL", "NULLIF", "NUMERIC", "OF", "OFF", "OIDS", "OLD", "ONLY", "OPERATOR", "OPTION", "OUT", "OVERLAPS", "OVERLAY", "OWNER", "PARTIAL", "PASSWORD", "PATH", "PENDANT", "PLACING", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIOR", "PRIVILEGES", "PROCEDURAL", "PROCEDURE", "READ", "REAL", "RECHECK", "REINDEX", "RELATIVE", "RENAME", "RESET", "RESTART", "RETURNS", "REVOKE", "ROWS", "RULE", "SCHEMA", "SCROLL", "SECOND", "SECURITY", "SEQUENCE", "SERIALIZABLE", "SESSION", "SESSION_USER", "SETOF", "SHARE", "SHOW", "SIMPLE", "SMALLINT", "SOME", "STABLE", "START", "STATEMENT", "STATISTICS", "STDIN", "STDOUT", "STORAGE", "STRICT", "SUBSTRING", "SYSID", "TEMP", "TEMPLATE", "TIME", "TIMESTAMP", "TOAST", "TRAILING", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TRUSTED", "TYPE", "UNENCRYPTED", "UNKNOWN", "UNLISTEN", "UNTIL", "USAGE", "USER", "VACUUM", "VALID", "VALIDATOR", "VARCHAR", "VARYING", "VERBOSE", "VERSION", "VIEW", "VOLATILE", "WITH", "WITHOUT", "WORK", "WRITE", "YEAR", "ZONE", - 0 + nullptr }; diff --git a/src/drivers/postgresql/PostgresqlPreparedStatement.h b/src/drivers/postgresql/PostgresqlPreparedStatement.h index 29e2e8ce..7bf7e866 100644 --- a/src/drivers/postgresql/PostgresqlPreparedStatement.h +++ b/src/drivers/postgresql/PostgresqlPreparedStatement.h @@ -1,47 +1,47 @@ /* This file is part of the KDE project Copyright (C) 2005 Adam Pigg Copyright (C) 2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_POSTGRESQLPREPAREDSTATEMENT_H #define KDB_POSTGRESQLPREPAREDSTATEMENT_H #include "KDbPreparedStatementInterface.h" #include "PostgresqlConnection_p.h" class PostgresqlPreparedStatement : public KDbPreparedStatementInterface, public PostgresqlConnectionInternal { public: explicit PostgresqlPreparedStatement(PostgresqlConnectionInternal* conn); - virtual ~PostgresqlPreparedStatement(); + ~PostgresqlPreparedStatement() override; - virtual bool prepare(const KDbEscapedString& sql); + bool prepare(const KDbEscapedString& sql) override; - virtual KDbSqlResult* execute( + KDbSqlResult* execute( KDbPreparedStatement::Type type, const KDbField::List& selectFieldList, KDbFieldList* insertFieldList, const KDbPreparedStatementParameters& parameters, - bool *resultOwned) Q_REQUIRED_RESULT; + bool *resultOwned) override Q_REQUIRED_RESULT; private: Q_DISABLE_COPY(PostgresqlPreparedStatement) }; #endif diff --git a/src/drivers/sqlite/SqliteAdmin.h b/src/drivers/sqlite/SqliteAdmin.h index f794f19d..6ad97abd 100644 --- a/src/drivers/sqlite/SqliteAdmin.h +++ b/src/drivers/sqlite/SqliteAdmin.h @@ -1,41 +1,41 @@ /* This file is part of the KDE project Copyright (C) 2006 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_SQLITEADMIN_H #define KDB_SQLITEADMIN_H #include "SqliteGlobal.h" #include "KDbAdmin.h" //! @short An interface containing a set of tools for SQLite database administration. class SqliteAdminTools : public KDbAdminTools { public: SqliteAdminTools(); - virtual ~SqliteAdminTools(); + ~SqliteAdminTools() override; #ifdef KDB_SQLITE_VACUUM /*! Performs vacuum (compacting) for connection @a conn. */ virtual bool vacuum(const KDbConnectionData& data, const QString& databaseName); #endif private: Q_DISABLE_COPY(SqliteAdminTools) }; #endif diff --git a/src/drivers/sqlite/SqliteConnection.cpp b/src/drivers/sqlite/SqliteConnection.cpp index dcb515ee..a434526b 100644 --- a/src/drivers/sqlite/SqliteConnection.cpp +++ b/src/drivers/sqlite/SqliteConnection.cpp @@ -1,412 +1,412 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteConnection.h" #include "SqliteConnection_p.h" #include "SqliteCursor.h" #include "SqlitePreparedStatement.h" #include "SqliteFunctions.h" #include "sqlite_debug.h" #include #include "KDbConnectionData.h" #include "KDbConnectionOptions.h" #include "KDbUtils.h" #include "KDbUtils_p.h" #include "KDbVersionInfo.h" #include #include #include SqliteConnection::SqliteConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options) : KDbConnection(driver, connData, options) , d(new SqliteConnectionInternal(this)) { QByteArray propertyName = "extraSqliteExtensionPaths"; KDbUtils::Property extraSqliteExtensionPathsProperty = this->options()->property(propertyName); if (extraSqliteExtensionPathsProperty.isNull()) { this->options()->insert(propertyName, QStringList()); } this->options()->setCaption(propertyName, SqliteConnection::tr("Extra paths for SQLite plugins")); } SqliteConnection::~SqliteConnection() { destroy(); delete d; } void SqliteConnection::storeResult() { d->storeResult(&m_result); } bool SqliteConnection::drv_connect() { return true; } bool SqliteConnection::drv_getServerVersion(KDbServerVersionInfo* version) { version->setString(QLatin1String(SQLITE_VERSION)); //defined in sqlite3.h QRegularExpression re(QLatin1String("^(\\d+)\\.(\\d+)\\.(\\d+)$")); QRegularExpressionMatch match = re.match(version->string()); if (match.hasMatch()) { version->setMajor(match.captured(1).toInt()); version->setMinor(match.captured(2).toInt()); version->setRelease(match.captured(3).toInt()); } return true; } bool SqliteConnection::drv_disconnect() { return true; } bool SqliteConnection::drv_getDatabasesList(QStringList* list) { //this is one-db-per-file database list->append(data().databaseName()); return true; } tristate SqliteConnection::drv_containsTable(const QString &tableName) { return resultExists(KDbEscapedString("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE %1") .arg(escapeString(tableName))); } #if 0 // TODO bool SqliteConnection::drv_getTablesList(QStringList* list) { KDbCursor *cursor; if (!(cursor = executeQuery(KDbEscapedString("SELECT name FROM sqlite_master WHERE type='table'")))) { sqliteWarning() << "!executeQuery()"; return false; } list->clear(); cursor->moveFirst(); while (!cursor->eof() && !cursor->result().isError()) { *list += cursor->value(0).toString(); cursor->moveNext(); } if (cursor->result().isError()) { deleteCursor(cursor); return false; } return deleteCursor(cursor); } #endif bool SqliteConnection::drv_createDatabase(const QString &dbName) { Q_UNUSED(dbName); - return drv_useDatabaseInternal(0, 0, true/*create if missing*/); + return drv_useDatabaseInternal(nullptr, nullptr, true/*create if missing*/); } bool SqliteConnection::drv_useDatabase(const QString &dbName, bool *cancelled, KDbMessageHandler* msgHandler) { Q_UNUSED(dbName); return drv_useDatabaseInternal(cancelled, msgHandler, false/*do not create if missing*/); } bool SqliteConnection::drv_useDatabaseInternal(bool *cancelled, KDbMessageHandler* msgHandler, bool createIfMissing) { //! @todo add option (command line or in kdbrc?) //! @todo int exclusiveFlag = KDbConnection::isReadOnly() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_WRITE_LOCKED; // <-- shared read + (if !r/o): exclusive write int openFlags = 0; if (options()->isReadOnly()) { openFlags |= SQLITE_OPEN_READONLY; } else { openFlags |= SQLITE_OPEN_READWRITE; if (createIfMissing) { openFlags |= SQLITE_OPEN_CREATE; } } //! @todo add option // int allowReadonly = 1; // const bool wasReadOnly = KDbConnection::isReadOnly(); //sqliteDebug() << data().databaseName(); int res = sqlite3_open_v2( /* unicode expected since SQLite 3.1 */ QDir::toNativeSeparators(data().databaseName()).toUtf8().constData(), &d->data, openFlags, /*exclusiveFlag, allowReadonly *//* If 1 and locking fails, try opening in read-only mode */ - 0 + nullptr ); if (res != SQLITE_OK) { m_result.setServerErrorCode(res); } storeResult(); if (!m_result.isError()) { // Set the secure-delete on, so SQLite overwrites deleted content with zeros. // The default setting is determined by the SQLITE_SECURE_DELETE compile-time option but we overwrite it here. // Works with 3.6.23. Earlier version just ignore this pragma. // See http://www.sqlite.org/pragma.html#pragma_secure_delete //! @todo add connection flags to the driver and global setting to control the "secure delete" pragma if (!drv_executeVoidSQL(KDbEscapedString("PRAGMA secure_delete = on"))) { drv_closeDatabaseSilently(); return false; } // Load ICU extension for unicode collations if (!findAndLoadExtension(QLatin1String("kdb_sqlite_icu"))) { drv_closeDatabaseSilently(); return false; } // load ROOT collation for use as default collation if (!drv_executeVoidSQL(KDbEscapedString("SELECT icu_load_collation('', '')"))) { drv_closeDatabaseSilently(); return false; } if (!createCustomSQLiteFunctions(d->data)) { drv_closeDatabaseSilently(); return false; } } //! @todo check exclusive status Q_UNUSED(cancelled); Q_UNUSED(msgHandler); //! @todo removed in kdb - reenable? /* if (d->res == SQLITE_OK && cancelled && !wasReadOnly && allowReadonly && isReadOnly()) { //opened as read only, ask if (KDbMessageHandler::Continue != askQuestion( KDbMessageHandler::WarningContinueCancel, futureTr("Do you want to open file \"%1\" as read-only?\n\n" "The file is probably already open on this or another computer. " "Could not gain exclusive access for writing the file.") .arg(QDir::fromNativeSeparators(data()->databaseName())), futureTr("Opening As Read-Only"), KDbMessageHandler::Continue, KDbMessageHandler::KDbGuiItem() .setProperty("text", futureTr("Open As Read-Only")) .setProperty("icon", "document-open"), KDbMessageHandler::KDbGuiItem(), "askBeforeOpeningFileReadOnly", KDbMessageHandler::Notify, msgHandler) { clearError(); if (!drv_closeDatabase()) return false; *cancelled = true; return false; } } if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_READWRITE) { setError(ERR_ACCESS_RIGHTS, futureTr("The file is probably already open on this or another computer.\n\n" "Could not gain exclusive access for reading and writing the file. " "Check the file's permissions and whether it is already opened and locked by another application.")); } else if (d->res == SQLITE_CANTOPEN_WITH_LOCKED_WRITE) { setError(ERR_ACCESS_RIGHTS, futureTr("The file is probably already open on this or another computer.\n\n" "Could not gain exclusive access for writing the file. " "Check the file's permissions and whether it is already opened and locked by another application.")); }*/ return res == SQLITE_OK; } void SqliteConnection::drv_closeDatabaseSilently() { KDbResult result = this->result(); // save drv_closeDatabase(); m_result = result; } bool SqliteConnection::drv_closeDatabase() { if (!d->data) return false; const int res = sqlite3_close(d->data); if (SQLITE_OK == res) { - d->data = 0; + d->data = nullptr; return true; } if (SQLITE_BUSY == res) { #if 0 //this is ANNOYING, needs fixing (by closing cursors or waiting) setError(ERR_CLOSE_FAILED, futureTr("Could not close busy database.")); #else return true; #endif } return false; } bool SqliteConnection::drv_dropDatabase(const QString &dbName) { Q_UNUSED(dbName); // Each database is one single SQLite file. const QString filename = data().databaseName(); if (QFile::exists(filename) && !QFile::remove(filename)) { m_result = KDbResult(ERR_ACCESS_RIGHTS, SqliteConnection::tr("Could not delete file \"%1\". " "Check the file's permissions and whether it is already " "opened and locked by another application.") .arg(QDir::fromNativeSeparators(filename))); return false; } return true; } KDbCursor* SqliteConnection::prepareQuery(const KDbEscapedString& sql, KDbCursor::Options options) { return new SqliteCursor(this, sql, options); } KDbCursor* SqliteConnection::prepareQuery(KDbQuerySchema* query, KDbCursor::Options options) { return new SqliteCursor(this, query, options); } KDbSqlResult* SqliteConnection::drv_executeSQL(const KDbEscapedString& sql) { #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String("ExecuteSQL (SQLite): ") + sql.toString()); #endif sqlite3_stmt *prepared_st = nullptr; const int res = sqlite3_prepare( d->data, /* Database handle */ sql.constData(), /* SQL statement, UTF-8 encoded */ sql.length(), /* Length of zSql in bytes. */ &prepared_st, /* OUT: Statement handle */ nullptr/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */ ); if (res != SQLITE_OK) { m_result.setServerErrorCode(res); storeResult(); #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String(" Failure")); #endif return nullptr; } #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String(" Success")); #endif return new SqliteSqlResult(this, prepared_st); } bool SqliteConnection::drv_executeVoidSQL(const KDbEscapedString& sql) { #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String("ExecuteVoidSQL (SQLite): ") + sql.toString()); #endif char *errmsg_p = nullptr; const int res = sqlite3_exec( d->data, sql.constData(), nullptr/*callback*/, nullptr, &errmsg_p); if (res != SQLITE_OK) { m_result.setServerErrorCode(res); } if (errmsg_p) { clearResult(); m_result.setServerMessage(QLatin1String(errmsg_p)); sqlite3_free(errmsg_p); } else { storeResult(); } #ifdef KDB_DEBUG_GUI KDb::debugGUI(QLatin1String( res == SQLITE_OK ? " Success" : " Failure")); #endif return res == SQLITE_OK; } QString SqliteConnection::serverResultName() const { return SqliteConnectionInternal::serverResultName(m_result.serverErrorCode()); } KDbPreparedStatementInterface* SqliteConnection::prepareStatementInternal() { return new SqlitePreparedStatement(d); } bool SqliteConnection::findAndLoadExtension(const QString & name) { QStringList pluginPaths; foreach (const QString& path, KDb::libraryPaths()) { pluginPaths += path + QLatin1String("/sqlite3"); } pluginPaths += options()->property("extraSqliteExtensionPaths").value().toStringList(); foreach (const QString& path, pluginPaths) { if (loadExtension(path + QLatin1Char('/') + name + QLatin1String(KDB_SHARED_LIB_EXTENSION))) { return true; } } clearResult(); m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT, SqliteConnection::tr("Could not load SQLite plugin \"%1\".").arg(name)); return false; } bool SqliteConnection::loadExtension(const QString& path) { bool tempEnable = false; clearResult(); QFileInfo fileInfo(path); if (!fileInfo.exists()) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, SqliteConnection::tr("Could not find SQLite plugin file \"%1\".").arg(path)); //sqliteWarning() << "SqliteConnection::loadExtension(): Could not find SQLite extension"; return false; } if (!d->extensionsLoadingEnabled()) { tempEnable = true; d->setExtensionsLoadingEnabled(true); } - char *errmsg_p = 0; - int res = sqlite3_load_extension(d->data, QDir::toNativeSeparators(path).toUtf8().constData(), 0, &errmsg_p); + char *errmsg_p = nullptr; + int res = sqlite3_load_extension(d->data, QDir::toNativeSeparators(path).toUtf8().constData(), nullptr, &errmsg_p); bool ok = res == SQLITE_OK; if (!ok) { m_result.setServerErrorCode(res); m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT, SqliteConnection::tr("Could not load SQLite extension \"%1\".").arg(path)); sqliteWarning() << "SqliteConnection::loadExtension(): Could not load SQLite extension" << path << ":" << errmsg_p; if (errmsg_p) { m_result.setServerMessage(QLatin1String(errmsg_p)); sqlite3_free(errmsg_p); } } if (tempEnable) { d->setExtensionsLoadingEnabled(false); } return ok; } diff --git a/src/drivers/sqlite/SqliteConnection.h b/src/drivers/sqlite/SqliteConnection.h index 27444e97..aa12d995 100644 --- a/src/drivers/sqlite/SqliteConnection.h +++ b/src/drivers/sqlite/SqliteConnection.h @@ -1,123 +1,123 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_CONN_SQLITE_H #define KDB_CONN_SQLITE_H #include #include "KDbConnection.h" class SqliteConnectionInternal; class KDbDriver; /*! @brief SQLite-specific connection Following connection options are supported (see KDbConnectionOptions): - extraSqliteExtensionPaths (read/write, QStringList): adds extra seach paths for SQLite extensions. Set them before KDbConnection::useDatabase() is called. Absolute paths are recommended. */ class SqliteConnection : public KDbConnection { Q_DECLARE_TR_FUNCTIONS(SqliteConnection) public: - virtual ~SqliteConnection(); + ~SqliteConnection() override; - KDbCursor* prepareQuery(const KDbEscapedString& sql, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; - KDbCursor* prepareQuery(KDbQuerySchema* query, - KDbCursor::Options options = KDbCursor::Option::None) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(const KDbEscapedString &sql, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(KDbQuerySchema *query, KDbCursor::Options options + = KDbCursor::Option::None) override Q_REQUIRED_RESULT; - KDbPreparedStatementInterface* prepareStatementInternal() Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbPreparedStatementInterface* prepareStatementInternal() override Q_REQUIRED_RESULT; protected: /*! Used by driver */ SqliteConnection(KDbDriver *driver, const KDbConnectionData& connData, const KDbConnectionOptions &options); - virtual bool drv_connect(); - virtual bool drv_getServerVersion(KDbServerVersionInfo* version); - virtual bool drv_disconnect(); - virtual bool drv_getDatabasesList(QStringList* list); + bool drv_connect() override; + bool drv_getServerVersion(KDbServerVersionInfo* version) override; + bool drv_disconnect() override; + bool drv_getDatabasesList(QStringList* list) override; #if 0 // TODO //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_getTablesList(QStringList* list); #endif //! @todo move this somewhere to low level class (MIGRATION?) - virtual tristate drv_containsTable(const QString &tableName); + tristate drv_containsTable(const QString &tableName) override; /*! Creates new database using connection. Note: Do not pass @a dbName arg because for file-based engine (that has one database per connection) it is defined during connection. */ - virtual bool drv_createDatabase(const QString &dbName = QString()); + bool drv_createDatabase(const QString &dbName = QString()) override; /*! Opens existing database using connection. Do not pass @a dbName arg because for file-based engine (that has one database per connection) it is defined during connection. If you pass it, database file name will be changed. */ - virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = 0, - KDbMessageHandler* msgHandler = 0); + bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = nullptr, + KDbMessageHandler* msgHandler = nullptr) override; - virtual bool drv_closeDatabase(); + bool drv_closeDatabase() override; /*! Drops database from the server using connection. After drop, database shouldn't be accessible anymore, so database file is just removed. See note from drv_useDatabase(). */ - virtual bool drv_dropDatabase(const QString &dbName = QString()); + bool drv_dropDatabase(const QString &dbName = QString()) override; - virtual KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql); - virtual bool drv_executeVoidSQL(const KDbEscapedString& sql); + KDbSqlResult* drv_executeSQL(const KDbEscapedString& sql) override; + bool drv_executeVoidSQL(const KDbEscapedString& sql) override; //! Implemented for KDbResultable - virtual QString serverResultName() const; + QString serverResultName() const override; void storeResult(); - virtual tristate drv_changeFieldProperty(KDbTableSchema* table, KDbField* field, - const QString& propertyName, const QVariant& value); + tristate drv_changeFieldProperty(KDbTableSchema* table, KDbField* field, + const QString& propertyName, const QVariant& value) override; //! for drv_changeFieldProperty() tristate changeFieldType(KDbTableSchema *table, KDbField *field, KDbField::Type type); SqliteConnectionInternal* d; private: bool drv_useDatabaseInternal(bool *cancelled, KDbMessageHandler* msgHandler, bool createIfMissing); //! Closes database without altering stored result number and message void drv_closeDatabaseSilently(); //! Finds a native SQLite extension @a name in the search path and loads it. //! Path and filename extension should not be provided. //! @return true on success bool findAndLoadExtension(const QString & name); //! Loads extension from plugin at @a path (absolute path is recommended) //! @return true on success bool loadExtension(const QString& path); friend class SqliteDriver; friend class SqliteCursor; friend class SqliteSqlResult; Q_DISABLE_COPY(SqliteConnection) }; #endif diff --git a/src/drivers/sqlite/SqliteConnection_p.cpp b/src/drivers/sqlite/SqliteConnection_p.cpp index b5bd7459..2cc95b4d 100644 --- a/src/drivers/sqlite/SqliteConnection_p.cpp +++ b/src/drivers/sqlite/SqliteConnection_p.cpp @@ -1,223 +1,223 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteConnection_p.h" SqliteConnectionInternal::SqliteConnectionInternal(KDbConnection *connection) : KDbConnectionInternal(connection) - , data(0) + , data(nullptr) , data_owned(true) , m_extensionsLoadingEnabled(false) { } SqliteConnectionInternal::~SqliteConnectionInternal() { if (data_owned && data) { sqlite3_close(data); - data = 0; + data = nullptr; } } static const char* const serverResultNames[] = { "SQLITE_OK", // 0 "SQLITE_ERROR", "SQLITE_INTERNAL", "SQLITE_PERM", "SQLITE_ABORT", "SQLITE_BUSY", "SQLITE_LOCKED", "SQLITE_NOMEM", "SQLITE_READONLY", "SQLITE_INTERRUPT", "SQLITE_IOERR", "SQLITE_CORRUPT", "SQLITE_NOTFOUND", "SQLITE_FULL", "SQLITE_CANTOPEN", "SQLITE_PROTOCOL", "SQLITE_EMPTY", "SQLITE_SCHEMA", "SQLITE_TOOBIG", "SQLITE_CONSTRAINT", "SQLITE_MISMATCH", "SQLITE_MISUSE", "SQLITE_NOLFS", "SQLITE_AUTH", "SQLITE_FORMAT", "SQLITE_RANGE", "SQLITE_NOTADB", // 26 }; // static QString SqliteConnectionInternal::serverResultName(int serverResultCode) { if (serverResultCode >= 0 && serverResultCode <= SQLITE_NOTADB) return QString::fromLatin1(serverResultNames[serverResultCode]); else if (serverResultCode == SQLITE_ROW) return QLatin1String("SQLITE_ROW"); else if (serverResultCode == SQLITE_DONE) return QLatin1String("SQLITE_DONE"); return QString(); } void SqliteConnectionInternal::storeResult(KDbResult *result) { result->setServerMessage( (data && result->isError()) ? QString::fromUtf8(sqlite3_errmsg(data)) : QString()); } bool SqliteConnectionInternal::extensionsLoadingEnabled() const { return m_extensionsLoadingEnabled; } void SqliteConnectionInternal::setExtensionsLoadingEnabled(bool set) { if (set == m_extensionsLoadingEnabled) return; sqlite3_enable_load_extension(data, set); m_extensionsLoadingEnabled = set; } //static KDbField::Type SqliteSqlResult::type(int sqliteType) { KDbField::Type t; switch(sqliteType) { case SQLITE_INTEGER: t = KDbField::Integer; break; case SQLITE_FLOAT: t = KDbField::Double; break; case SQLITE_BLOB: t = KDbField::BLOB; break; case SQLITE_NULL: t = KDbField::Null; break; case SQLITE_TEXT: t = KDbField::LongText; break; default: t = KDbField::InvalidType; break; } return t; } bool SqliteSqlResult::setConstraints(const QString &tableName, KDbField* field) { Q_ASSERT(field); if (!cacheFieldInfo(tableName)) { return false; } SqliteSqlFieldInfo* info = cachedFieldInfos.value(field->name()); if (info) { info->setConstraints(field); return true; } else { return false; } } void SqliteSqlFieldInfo::setConstraints(KDbField* field) { field->setDefaultValue(KDbField::convertToType(defaultValue, field->type())); field->setNotNull(isNotNull); field->setPrimaryKey(isPrimaryKey); } bool SqliteSqlResult::cacheFieldInfo(const QString &tableName) { if (!cachedFieldInfos.isEmpty()) { return true; } QScopedPointer tableInfoResult(conn->executeSQL( KDbEscapedString("PRAGMA table_info(%1)").arg(conn->escapeIdentifier(tableName)))); if (!tableInfoResult) { return false; } // Forward-compatible approach: find columns of table_info that we need const int columns = tableInfoResult->fieldsCount(); enum TableInfoColumns { TableInfoFieldName, TableInfoNotNull, TableInfoDefault, TableInfoPK }; QVector columnIndex(TableInfoPK + 1); int found = 0; for(int col = 0; col < columns; ++col) { QScopedPointer f(tableInfoResult->field(col)); if (!f) { return false; } if (f->name() == QLatin1String("name")) { columnIndex[TableInfoFieldName] = col; ++found; } else if (f->name() == QLatin1String("notnull")) { columnIndex[TableInfoNotNull] = col; ++found; } else if (f->name() == QLatin1String("dflt_value")) { columnIndex[TableInfoDefault] = col; ++found; } else if (f->name() == QLatin1String("pk")) { columnIndex[TableInfoPK] = col; ++found; } } if (found != TableInfoPK + 1) { // not all columns found return false; } bool ok = true; Q_FOREVER { QScopedPointer record(tableInfoResult->fetchRecord()); if (!record) { ok = !tableInfoResult->lastResult().isError(); break; } QScopedPointer info(new SqliteSqlFieldInfo); const QString name = record->stringValue(columnIndex[TableInfoFieldName]); if (name.isEmpty()) { ok = false; break; } info->defaultValue = record->stringValue(columnIndex[TableInfoDefault]); info->isNotNull = record->cstringValue(columnIndex[TableInfoNotNull]).rawDataToByteArray() == "1"; //! @todo Support composite primary keys: //! The "pk" column in the result set is zero for columns that are not part of //! the primary key, and is the index of the column in the primary key for columns //! that are part of the primary key. //! https://www.sqlite.org/pragma.html#pragma_table_info info->isPrimaryKey = record->cstringValue(columnIndex[TableInfoPK]).rawDataToByteArray() != "0"; cachedFieldInfos.insert(name, info.take()); } if (!ok) { cachedFieldInfos.clear(); } return ok; } KDbField *SqliteSqlResult::createField(const QString &tableName, int index) { QScopedPointer f(static_cast(field(index))); if (!f) { return nullptr; } const QString caption(f->name()); QString realFieldName(KDb::stringToIdentifier(caption.toLower())); KDbField *kdbField = new KDbField(realFieldName, type(f->type())); kdbField->setCaption(caption); setConstraints(tableName, kdbField); return kdbField; } diff --git a/src/drivers/sqlite/SqliteConnection_p.h b/src/drivers/sqlite/SqliteConnection_p.h index 82a2a35e..bb1719c6 100644 --- a/src/drivers/sqlite/SqliteConnection_p.h +++ b/src/drivers/sqlite/SqliteConnection_p.h @@ -1,204 +1,204 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_SQLITECONN_P_H #define KDB_SQLITECONN_P_H #include "KDbConnection_p.h" #include "SqliteConnection.h" #include "KDbSqlField.h" #include "KDbSqlRecord.h" #include "KDbSqlResult.h" #include "KDbSqlString.h" #include /*! Internal SQLite connection data. Also used by SqliteCursor. */ class SqliteConnectionInternal : public KDbConnectionInternal { public: explicit SqliteConnectionInternal(KDbConnection *connection); virtual ~SqliteConnectionInternal(); //! @return true if loading extensions is enabled bool extensionsLoadingEnabled() const; //! Sets loading extensions flag to @a set void setExtensionsLoadingEnabled(bool set); static QString serverResultName(int serverResultCode); void storeResult(KDbResult *result); sqlite3 *data; bool data_owned; //!< true if data pointer should be freed on destruction private: bool m_extensionsLoadingEnabled; Q_DISABLE_COPY(SqliteConnectionInternal) }; class SqliteSqlField : public KDbSqlField { public: inline SqliteSqlField(sqlite3_stmt *st, int i) : prepared_st(st), index(i) { } //! @return column name - inline QString name() Q_DECL_OVERRIDE { + inline QString name() override { return QString::fromUtf8(sqlite3_column_name(prepared_st, index)); } //! @return column type - inline int type() Q_DECL_OVERRIDE { + inline int type() override { return sqlite3_column_type(prepared_st, index); } //! @return length limit - no limits for SQLite - inline int length() Q_DECL_OVERRIDE { + inline int length() override { return std::numeric_limits::max(); } private: sqlite3_stmt * const prepared_st; const int index; Q_DISABLE_COPY(SqliteSqlField) }; class SqliteSqlRecord : public KDbSqlRecord { public: inline SqliteSqlRecord(sqlite3_stmt *st) : prepared_st(st) { Q_ASSERT(st); } - inline ~SqliteSqlRecord() { + inline ~SqliteSqlRecord() override { } - inline QString stringValue(int index) Q_DECL_OVERRIDE { + inline QString stringValue(int index) override { return QString::fromUtf8( (const char*)sqlite3_column_text(prepared_st, index), sqlite3_column_bytes(prepared_st, index)); } - inline KDbSqlString cstringValue(int index) Q_DECL_OVERRIDE { + inline KDbSqlString cstringValue(int index) override { // sqlite3_column_text() returns UTF-8 but it's OK if the data is a C string return KDbSqlString((const char*)sqlite3_column_text(prepared_st, index), sqlite3_column_bytes(prepared_st, index)); } - inline QByteArray toByteArray(int index) Q_DECL_OVERRIDE { + inline QByteArray toByteArray(int index) override { return QByteArray((const char*)sqlite3_column_blob(prepared_st, index), sqlite3_column_bytes(prepared_st, index)); } private: sqlite3_stmt * const prepared_st; Q_DISABLE_COPY(SqliteSqlRecord) }; //! Used by SqliteSqlResult::cacheFieldInfo(const QString&) struct SqliteSqlFieldInfo { void setConstraints(KDbField* field); QString defaultValue; bool isNotNull; bool isPrimaryKey; }; class SqliteSqlResult : public KDbSqlResult { public: inline SqliteSqlResult(SqliteConnection *c, sqlite3_stmt *st) : conn(c), prepared_st(st) { Q_ASSERT(c); } - inline ~SqliteSqlResult() { + inline ~SqliteSqlResult() override { // don't check result here, done elsewhere already (void)sqlite3_finalize(prepared_st); } - inline KDbConnection *connection() const Q_DECL_OVERRIDE { + inline KDbConnection *connection() const override { return conn; } - inline int fieldsCount() Q_DECL_OVERRIDE { + inline int fieldsCount() override { // We're using sqlite3_column_count instead of sqlite3_data_count to know // the column count before fetching. User will know if fetching succeeded anyway. return sqlite3_column_count(prepared_st); } - inline KDbSqlField *field(int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlField *field(int index) override Q_REQUIRED_RESULT { return prepared_st ? new SqliteSqlField(prepared_st, index) : nullptr; } - KDbField *createField(const QString &tableName, int index) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbField *createField(const QString &tableName, int index) override Q_REQUIRED_RESULT; - inline KDbSqlRecord* fetchRecord() Q_DECL_OVERRIDE Q_REQUIRED_RESULT { + inline KDbSqlRecord* fetchRecord() override Q_REQUIRED_RESULT { SqliteSqlRecord *record; const int res = sqlite3_step(prepared_st); if (res == SQLITE_ROW) { record = new SqliteSqlRecord(prepared_st); } else { record = nullptr; } return record; } - inline KDbResult lastResult() Q_DECL_OVERRIDE { + inline KDbResult lastResult() override { KDbResult res; const int err = sqlite3_errcode(conn->d->data); if (err != SQLITE_ROW && err != SQLITE_OK && err != SQLITE_DONE) { res.setCode(ERR_OTHER); res.setServerErrorCode(err); conn->d->storeResult(&res); } return res; } - inline quint64 lastInsertRecordId() Q_DECL_OVERRIDE { + inline quint64 lastInsertRecordId() override { return static_cast(sqlite3_last_insert_rowid(conn->d->data)); } protected: //! @return a KDb type for a SQLite type //! The returned type is a guess, for example KDbField::Integer is returned for SQLITE_INTEGER. //! For unsupported types returns KDbField::InvalidType. //! See https://www.sqlite.org/c3ref/c_blob.html static KDbField::Type type(int sqliteType); //! Sets constraints to a @a field based on SQLite's internal schema: //! - whether the column can be NULL //! - the default value for the column //! - whether the column is primary key //! @note @a field should have its name set //! See https://www.sqlite.org/pragma.html#pragma_table_info //! @todo support keys bool setConstraints(const QString &tableName, KDbField* field); //! Caches information about the fields, for setConstraints() //! @todo Support composite primary keys //! @todo Default values are only encoded as string bool cacheFieldInfo(const QString &tableName); private: SqliteConnection * const conn; sqlite3_stmt * const prepared_st; KDbUtils::AutodeletedHash cachedFieldInfos; friend class SqlitePreparedStatement; Q_DISABLE_COPY(SqliteSqlResult) }; #endif diff --git a/src/drivers/sqlite/SqliteCursor.cpp b/src/drivers/sqlite/SqliteCursor.cpp index c62f8688..3cc14da3 100644 --- a/src/drivers/sqlite/SqliteCursor.cpp +++ b/src/drivers/sqlite/SqliteCursor.cpp @@ -1,343 +1,343 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteCursor.h" #include "SqliteConnection.h" #include "SqliteConnection_p.h" #include "sqlite_debug.h" #include "KDbDriver.h" #include "KDbError.h" #include "KDbRecordData.h" #include "KDbUtils.h" #include #include #include //! safer interpretations of boolean values for SQLite static bool sqliteStringToBool(const QString& s) { return 0 == s.compare(QLatin1String("yes"), Qt::CaseInsensitive) || (0 != s.compare(QLatin1String("no"), Qt::CaseInsensitive) && s != QLatin1String("0")); } //---------------------------------------------------- class SqliteCursorData : public SqliteConnectionInternal { public: explicit SqliteCursorData(SqliteConnection* conn) : SqliteConnectionInternal(conn) - , prepared_st_handle(0) - , utail(0) - , curr_coldata(0) - , curr_colname(0) + , prepared_st_handle(nullptr) + , utail(nullptr) + , curr_coldata(nullptr) + , curr_colname(nullptr) , cols_pointers_mem_size(0) { data_owned = false; } /* void fetchRowDataIfNeeded() { if (!rowDataReadyToFetch) return true; rowDataReadyToFetch = false; m_fieldCount = sqlite3_data_count(data); for (int i=0; i records; //!< buffer data inline QVariant getValue(KDbField *f, int i) { int type = sqlite3_column_type(prepared_st_handle, i); if (type == SQLITE_NULL) { return QVariant(); } else if (!f || type == SQLITE_TEXT) { //! @todo support for UTF-16 QString text(QString::fromUtf8( (const char*)sqlite3_column_text(prepared_st_handle, i), sqlite3_column_bytes(prepared_st_handle, i))); if (!f) { return text; } const KDbField::Type t = f->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isTextType(t)) { return text; } else if (t == KDbField::Date) { return QDate::fromString(text, Qt::ISODate); } else if (t == KDbField::Time) { //QDateTime - a hack needed because QVariant(QTime) has broken isNull() return KDbUtils::stringToHackedQTime(text); } else if (t == KDbField::DateTime) { if (text.length() > 10) { text[10] = QLatin1Char('T'); //for ISODate compatibility } return QDateTime::fromString(text, Qt::ISODate); } else if (t == KDbField::Boolean) { return sqliteStringToBool(text); } else { return QVariant(); //!< @todo } } else if (type == SQLITE_INTEGER) { const KDbField::Type t = f->type(); // cache: evaluating type of expressions can be expensive if (t == KDbField::BigInteger) { return QVariant(qint64(sqlite3_column_int64(prepared_st_handle, i))); } else if (KDbField::isIntegerType(t)) { return QVariant(sqlite3_column_int(prepared_st_handle, i)); } else if (t == KDbField::Boolean) { return sqlite3_column_int(prepared_st_handle, i) != 0; } else if (KDbField::isFPNumericType(t)) { //WEIRD, YEAH? return QVariant(double(sqlite3_column_int(prepared_st_handle, i))); } else { return QVariant(); //!< @todo } } else if (type == SQLITE_FLOAT) { const KDbField::Type t = f->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isFPNumericType(t)) { return QVariant(sqlite3_column_double(prepared_st_handle, i)); } else if (t == KDbField::BigInteger) { return QVariant(qint64(sqlite3_column_int64(prepared_st_handle, i))); } else if (KDbField::isIntegerType(t)) { return QVariant(int(sqlite3_column_double(prepared_st_handle, i))); } else { return QVariant(); //!< @todo } } else if (type == SQLITE_BLOB) { if (f && f->type() == KDbField::BLOB) { //! @todo efficient enough? return QByteArray((const char*)sqlite3_column_blob(prepared_st_handle, i), sqlite3_column_bytes(prepared_st_handle, i)); } else return QVariant(); //!< @todo } return QVariant(); } Q_DISABLE_COPY(SqliteCursorData) }; SqliteCursor::SqliteCursor(SqliteConnection* conn, const KDbEscapedString& sql, Options options) : KDbCursor(conn, sql, options) , d(new SqliteCursorData(conn)) { d->data = static_cast(conn)->d->data; } SqliteCursor::SqliteCursor(SqliteConnection* conn, KDbQuerySchema* query, Options options) : KDbCursor(conn, query, options) , d(new SqliteCursorData(conn)) { d->data = static_cast(conn)->d->data; } SqliteCursor::~SqliteCursor() { close(); delete d; } bool SqliteCursor::drv_open(const KDbEscapedString& sql) { //! @todo decode if (! d->data) { // this may as example be the case if SqliteConnection::drv_useDatabase() // wasn't called before. Normaly sqlite_compile/sqlite3_prepare // should handle it, but it crashes in in sqlite3SafetyOn at util.c:786 sqliteWarning() << "SqliteCursor::drv_open(): Database handle undefined."; return false; } int res = sqlite3_prepare( d->data, /* Database handle */ sql.constData(), /* SQL statement, UTF-8 encoded */ sql.length(), /* Length of zSql in bytes. */ &d->prepared_st_handle, /* OUT: Statement handle */ - 0/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */ + nullptr/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */ ); if (res != SQLITE_OK) { m_result.setServerErrorCode(res); storeResult(); return false; } if (isBuffered()) { //! @todo manage size dynamically d->records.resize(128); } return true; } bool SqliteCursor::drv_close() { int res = sqlite3_finalize(d->prepared_st_handle); if (res != SQLITE_OK) { m_result.setServerErrorCode(res); storeResult(); return false; } return true; } void SqliteCursor::drv_getNextRecord() { int res = sqlite3_step(d->prepared_st_handle); if (res == SQLITE_ROW) { m_fetchResult = FetchOK; m_fieldCount = sqlite3_data_count(d->prepared_st_handle); //#else //for SQLITE3 data fetching is delayed. Now we even do not take field count information // // -- just set a flag that we've a data not fetched but available m_fieldsToStoreInRecord = m_fieldCount; } else { if (res == SQLITE_DONE) { m_fetchResult = FetchEnd; } else { m_result.setServerErrorCode(res); m_fetchResult = FetchError; } } //debug /* if ((int)m_result == (int)FetchOK && d->curr_coldata) { for (int i=0;icurr_colname[i]<<" "<< d->curr_colname[m_fieldCount+i] << " = " << (d->curr_coldata[i] ? QString::fromLocal8Bit(d->curr_coldata[i]) : "(NULL)"); } // sqliteDebug() << m_fieldCount << "col(s) fetched"; }*/ } void SqliteCursor::drv_appendCurrentRecordToBuffer() { // sqliteDebug(); if (!d->curr_coldata) return; if (!d->cols_pointers_mem_size) d->cols_pointers_mem_size = m_fieldCount * sizeof(char*); const char **record = (const char**)malloc(d->cols_pointers_mem_size); const char **src_col = d->curr_coldata; const char **dest_col = record; for (int i = 0; i < m_fieldCount; i++, src_col++, dest_col++) { // sqliteDebug() << i <<": '" << *src_col << "'"; // sqliteDebug() << "src_col: " << src_col; - *dest_col = *src_col ? strdup(*src_col) : 0; + *dest_col = *src_col ? strdup(*src_col) : nullptr; } d->records[m_records_in_buf] = record; // sqliteDebug() << "ok."; } void SqliteCursor::drv_bufferMovePointerNext() { d->curr_coldata++; //move to next record in the buffer } void SqliteCursor::drv_bufferMovePointerPrev() { d->curr_coldata--; //move to prev record in the buffer } //compute a place in the buffer that contain next record's data //and move internal buffer pointer to that place void SqliteCursor::drv_bufferMovePointerTo(qint64 at) { d->curr_coldata = d->records.at(at); } void SqliteCursor::drv_clearBuffer() { if (d->cols_pointers_mem_size > 0) { const int records_in_buf = m_records_in_buf; const char ***r_ptr = d->records.data(); for (int i = 0; i < records_in_buf; i++, r_ptr++) { const char **field_data = *r_ptr; for (int col = 0; col < m_fieldCount; col++, field_data++) { free((void*)*field_data); //free field memory } free(*r_ptr); //free pointers to fields array } } m_records_in_buf = 0; d->cols_pointers_mem_size = 0; d->records.clear(); } //! @todo /* const char *** SqliteCursor::bufferData() { if (!isBuffered()) return 0; return m_records.data(); }*/ const char ** SqliteCursor::recordData() const { return d->curr_coldata; } bool SqliteCursor::drv_storeCurrentRecord(KDbRecordData* data) const { if (!m_visibleFieldsExpanded) {//simple version: without types for (int i = 0; i < m_fieldCount; i++) { (*data)[i] = QString::fromUtf8( (const char*)sqlite3_column_text(d->prepared_st_handle, i), sqlite3_column_bytes(d->prepared_st_handle, i)); } return true; } for (int i = 0; i < m_fieldCount; ++i) { KDbField *f = m_visibleFieldsExpanded->at(i)->field(); // sqliteDebug() << "col=" << (col ? *col : 0); (*data)[i] = d->getValue(f, i); } return true; } QVariant SqliteCursor::value(int i) { if (i < 0 || i > (m_fieldCount - 1)) //range checking return QVariant(); //! @todo allow disable range checking! - performance reasons KDbField *f = (m_visibleFieldsExpanded && i < m_visibleFieldsExpanded->count()) ? m_visibleFieldsExpanded->at(i)->field() : nullptr; return d->getValue(f, i); //, i==m_logicalFieldCount/*ROWID*/); } QString SqliteCursor::serverResultName() const { return SqliteConnectionInternal::serverResultName(m_result.serverErrorCode()); } void SqliteCursor::storeResult() { d->storeResult(&m_result); } diff --git a/src/drivers/sqlite/SqliteCursor.h b/src/drivers/sqlite/SqliteCursor.h index 15244623..805252fa 100644 --- a/src/drivers/sqlite/SqliteCursor.h +++ b/src/drivers/sqlite/SqliteCursor.h @@ -1,83 +1,83 @@ /* This file is part of the KDE project Copyright (C) 2003-2010 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_SQLITECURSOR_H #define KDB_SQLITECURSOR_H #include #include "KDbCursor.h" class SqliteCursorData; class SqliteConnection; /*! */ class SqliteCursor : public KDbCursor { public: - virtual ~SqliteCursor(); - virtual QVariant value(int i); + ~SqliteCursor() override; + QVariant value(int i) override; /*! [PROTOTYPE] @return internal buffer data. */ //! @todo virtual const char *** bufferData() - /*! [PROTOTYPE] @return current record data or NULL if there is no current records. */ - virtual const char ** recordData() const; + /*! [PROTOTYPE] @return current record data or @c nullptr if there is no current records. */ + const char ** recordData() const override; - virtual bool drv_storeCurrentRecord(KDbRecordData* data) const; + bool drv_storeCurrentRecord(KDbRecordData* data) const override; //! Implemented for KDbResultable - virtual QString serverResultName() const; + QString serverResultName() const override; protected: /*! KDbCursor will operate on @a conn, raw @a sql statement will be used to execute query. */ SqliteCursor(SqliteConnection* conn, const KDbEscapedString& sql, Options options = KDbCursor::Option::None); /*! KDbCursor will operate on @a conn, @a query schema will be used to execute query. */ SqliteCursor(SqliteConnection* conn, KDbQuerySchema* query, Options options = KDbCursor::Option::None); - virtual bool drv_open(const KDbEscapedString& sql); + bool drv_open(const KDbEscapedString& sql) override; - virtual bool drv_close(); - virtual void drv_getNextRecord(); + bool drv_close() override; + void drv_getNextRecord() override; - virtual void drv_appendCurrentRecordToBuffer(); - virtual void drv_bufferMovePointerNext(); - virtual void drv_bufferMovePointerPrev(); - virtual void drv_bufferMovePointerTo(qint64 at); + void drv_appendCurrentRecordToBuffer() override; + void drv_bufferMovePointerNext() override; + void drv_bufferMovePointerPrev() override; + void drv_bufferMovePointerTo(qint64 at) override; //! @todo virtual void drv_storeCurrentRecord(); //PROTOTYPE: /*! Method called when cursor's buffer need to be cleared (only for buffered cursor type), eg. in close(). */ - virtual void drv_clearBuffer(); + void drv_clearBuffer() override; void storeResult(); SqliteCursorData * const d; friend class SqliteConnection; Q_DISABLE_COPY(SqliteCursor) }; #endif diff --git a/src/drivers/sqlite/SqliteDriver.h b/src/drivers/sqlite/SqliteDriver.h index 7c493279..5e24171b 100644 --- a/src/drivers/sqlite/SqliteDriver.h +++ b/src/drivers/sqlite/SqliteDriver.h @@ -1,118 +1,114 @@ /* This file is part of the KDE project Copyright (C) 2003-2015 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_DRIVER_SQLITE_H #define KDB_DRIVER_SQLITE_H #include "KDbDriver.h" class KDbConnection; class SqliteDriverPrivate; //! SQLite database driver. class SqliteDriver : public KDbDriver { Q_OBJECT public: SqliteDriver(QObject *parent, const QVariantList &args); - virtual ~SqliteDriver(); + ~SqliteDriver() override; /*! @return true if @a n is a system object name; for this driver any object with name prefixed with "sqlite_" is considered as system object. */ - virtual bool isSystemObjectName(const QString& n) const; + bool isSystemObjectName(const QString& n) const override; /*! @return false for this driver. */ - virtual bool isSystemDatabaseName(const QString&) const; + bool isSystemDatabaseName(const QString&) const override; //! Escape a string for use as a value - virtual KDbEscapedString escapeString(const QString& str) const; - virtual KDbEscapedString escapeString(const QByteArray& str) const; + KDbEscapedString escapeString(const QString& str) const override; + KDbEscapedString escapeString(const QByteArray& str) const override; //! Escape BLOB value @a array - virtual KDbEscapedString escapeBLOB(const QByteArray& array) const; + KDbEscapedString escapeBLOB(const QByteArray& array) const override; /*! Implemented for KDbDriver class. @return SQL clause to add for unicode text collation sequence used in ORDER BY clauses of SQL statements generated by KDb. Later other clauses may use this statement. One space character should be be prepended. Can be reimplemented for other drivers, e.g. the SQLite3 driver returns " COLLATE ''". Default implementation returns empty string. */ - virtual KDbEscapedString collationSQL() const; + KDbEscapedString collationSQL() const override; //! Generates native (driver-specific) GREATEST() and LEAST() function calls. //! Uses MAX() and MIN(), respectively. //! If arguments are of text type, to each argument default (unicode) collation //! is assigned that is configured for SQLite by KexiDB. //! Example: SELECT MAX('ą' COLLATE '', 'z' COLLATE ''). - virtual KDbEscapedString greatestOrLeastFunctionToString( - const QString &name, - const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString greatestOrLeastFunctionToString(const QString &name, const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; //! Generates native (driver-specific) RANDOM() and RANDOM(X,Y) function calls. //! Accepted @a args can contain zero or two positive integer arguments X, Y; X < Y. //! In case of numeric arguments, RANDOM(X, Y) returns a random integer that is equal //! or greater than X and less than Y. //! Because SQLite returns integer between -9223372036854775808 and +9223372036854775807, //! RANDOM() for SQLite is equal to (RANDOM()+9223372036854775807)/18446744073709551615. //! Similarly, RANDOM(X,Y) for SQLite is equal to //! (X + CAST((Y-X) * (RANDOM()+9223372036854775807)/18446744073709551615 AS INT)). - virtual KDbEscapedString randomFunctionToString(const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString randomFunctionToString(const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator* params, + KDb::ExpressionCallStack* callStack) const override; //! Generates native (driver-specific) CEILING() and FLOOR() function calls. //! Default implementation USES CEILING() and FLOOR(), respectively. //! For CEILING() uses: //! (CASE WHEN X = CAST(X AS INT) THEN CAST(X AS INT) WHEN X >= 0 THEN CAST(X AS INT) + 1 ELSE CAST(X AS INT) END). //! For FLOOR() uses: //! (CASE WHEN X >= 0 OR X = CAST(X AS INT) THEN CAST(X AS INT) ELSE CAST(X AS INT) - 1 END). - virtual KDbEscapedString ceilingOrFloorFunctionToString( - const QString &name, - const KDbNArgExpression &args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString ceilingOrFloorFunctionToString(const QString &name, const KDbNArgExpression &args, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; protected: - virtual QString drv_escapeIdentifier(const QString& str) const; - virtual QByteArray drv_escapeIdentifier(const QByteArray& str) const; - virtual KDbConnection *drv_createConnection(const KDbConnectionData& connData, - const KDbConnectionOptions &options); - virtual KDbAdminTools* drv_createAdminTools() const; + QString drv_escapeIdentifier(const QString& str) const override; + QByteArray drv_escapeIdentifier(const QByteArray& str) const override; + KDbConnection *drv_createConnection(const KDbConnectionData& connData, + const KDbConnectionOptions &options) override; + KDbAdminTools* drv_createAdminTools() const override; /*! @return true if @a n is a system field name; for this driver fields with name equal "_ROWID_" is considered as system field. */ - virtual bool drv_isSystemFieldName(const QString& n) const; + bool drv_isSystemFieldName(const QString& n) const override; SqliteDriverPrivate * const dp; private: static const char * const keywords[]; Q_DISABLE_COPY(SqliteDriver) }; #endif diff --git a/src/drivers/sqlite/SqliteFunctions.cpp b/src/drivers/sqlite/SqliteFunctions.cpp index d25cda97..f02e09b1 100644 --- a/src/drivers/sqlite/SqliteFunctions.cpp +++ b/src/drivers/sqlite/SqliteFunctions.cpp @@ -1,118 +1,118 @@ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek Contains portions of sqlite3.c licensed under public domain. The author disclaims copyright to this source code. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteFunctions.h" #include #include static bool tryExec(sqlite3 *db, const char *sql) { - return SQLITE_OK == sqlite3_exec(db, sql, 0 /*callback*/, - 0 /* 1st argument to callback */, 0 /*err*/); + return SQLITE_OK == sqlite3_exec(db, sql, nullptr /*callback*/, + nullptr /* 1st argument to callback */, nullptr /*err*/); } // BEGIN from sqlite3.c #define sqlite3Toupper(x) toupper((unsigned char)(x)) #define sqlite3Isalpha(x) isalpha((unsigned char)(x)) /* ** Compute the soundex encoding of a word. ** ** IMP: R-59782-00072 The soundex(X) function returns a string that is the ** soundex encoding of the string X. */ static void soundexFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ char zResult[8]; const uchar *zIn; int i, j; static const uchar iCode[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, }; Q_ASSERT(argc==1); zIn = (uchar*)sqlite3_value_text(argv[0]); - if( zIn==0 ) zIn = (uchar*)""; + if( zIn==nullptr ) zIn = (uchar*)""; for(i=0; zIn[i] && !sqlite3Isalpha(zIn[i]); i++){} if( zIn[i] ){ uchar prevcode = iCode[zIn[i]&0x7f]; zResult[0] = sqlite3Toupper(zIn[i]); for(j=1; j<4 && zIn[i]; i++){ int code = iCode[zIn[i]&0x7f]; if( code>0 ){ if( code!=prevcode ){ prevcode = code; zResult[j++] = code + '0'; } }else{ prevcode = 0; } } while( j<4 ){ zResult[j++] = '0'; } zResult[j] = 0; sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT); }else{ /* IMP: R-64894-50321 The string "?000" is returned if the argument ** is NULL or contains no ASCII alphabetic characters. */ sqlite3_result_text(context, "?000", 4, SQLITE_STATIC); } } bool createCustomSQLiteFunctions(sqlite3 *db) { int eTextRep = SQLITE_UTF8; #if SQLITE_VERSION_NUMBER >= 3008003 eTextRep |= SQLITE_DETERMINISTIC; #endif if (!tryExec(db, "SELECT SOUNDEX()")) { int res = sqlite3_create_function_v2( db, "SOUNDEX", 1, //nArg eTextRep, - 0, // pApp + nullptr, // pApp soundexFunc, - 0, // xStep - 0, // xFinal - 0 // xDestroy + nullptr, // xStep + nullptr, // xFinal + nullptr // xDestroy ); if (res != SQLITE_OK) { return false; } } return true; } // END from sqlite3.c diff --git a/src/drivers/sqlite/SqliteKeywords.cpp b/src/drivers/sqlite/SqliteKeywords.cpp index 06b62900..62b8acb3 100644 --- a/src/drivers/sqlite/SqliteKeywords.cpp +++ b/src/drivers/sqlite/SqliteKeywords.cpp @@ -1,159 +1,159 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteDriver.h" //! The list is created by hand based on parse.c from SQLite 3.13.0. //! @todo Proces patterns like this from http://www.sqlite.org/draft/tokenreq.html //! to extract tokens automatically: //! "SQLite shall recognize the *-character sequenence "*" in any combination of upper and lower case letters as the keyword token *." const char* const SqliteDriver::keywords[] = { "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "ANY", "AS", "ASC", "ATTACH", "AUTOINCR", "BEFORE", "BEGIN", "BETWEEN", "BLOB", "BY", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FLOAT", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "ID", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTEGER", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL", "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "STRING", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VARIABLE", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", "WITHOUT", - 0 + nullptr }; diff --git a/src/drivers/sqlite/SqlitePreparedStatement.h b/src/drivers/sqlite/SqlitePreparedStatement.h index 861db889..d468d1ee 100644 --- a/src/drivers/sqlite/SqlitePreparedStatement.h +++ b/src/drivers/sqlite/SqlitePreparedStatement.h @@ -1,53 +1,53 @@ /* This file is part of the KDE project Copyright (C) 2005-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_SQLITEPREPAREDSTATEMENT_H #define KDB_SQLITEPREPAREDSTATEMENT_H #include "KDbPreparedStatementInterface.h" #include "SqliteConnection_p.h" class KDbField; /*! Implementation of prepared statements for the SQLite driver. */ class SqlitePreparedStatement : public KDbPreparedStatementInterface, public SqliteConnectionInternal { public: explicit SqlitePreparedStatement(SqliteConnectionInternal* conn); - virtual ~SqlitePreparedStatement(); + ~SqlitePreparedStatement() override; protected: - virtual bool prepare(const KDbEscapedString& sql); + bool prepare(const KDbEscapedString& sql) override; - virtual KDbSqlResult* execute( + KDbSqlResult* execute( KDbPreparedStatement::Type type, const KDbField::List& selectFieldList, KDbFieldList* insertFieldList, const KDbPreparedStatementParameters& parameters, - bool *resultOwned) Q_REQUIRED_RESULT; + bool *resultOwned) override Q_REQUIRED_RESULT; bool bindValue(KDbField *field, const QVariant& value, int arg); QScopedPointer m_sqlResult; private: Q_DISABLE_COPY(SqlitePreparedStatement) }; #endif diff --git a/src/drivers/sqlite/SqliteVacuum.cpp b/src/drivers/sqlite/SqliteVacuum.cpp index f5444234..f3237b3a 100644 --- a/src/drivers/sqlite/SqliteVacuum.cpp +++ b/src/drivers/sqlite/SqliteVacuum.cpp @@ -1,250 +1,250 @@ /* This file is part of the KDE project Copyright (C) 2006-2013 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SqliteVacuum.h" #include "sqlite_debug.h" #include "KDb.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include void usleep(unsigned int usec) { Sleep(usec/1000); } #else #include #endif SqliteVacuum::SqliteVacuum(const QString& filePath) : m_filePath(filePath) { - m_dumpProcess = 0; - m_sqliteProcess = 0; + m_dumpProcess = nullptr; + m_sqliteProcess = nullptr; m_percent = 0; - m_dlg = 0; + m_dlg = nullptr; m_canceled = false; } SqliteVacuum::~SqliteVacuum() { if (m_dumpProcess) { m_dumpProcess->waitForFinished(); delete m_dumpProcess; } if (m_sqliteProcess) { m_sqliteProcess->waitForFinished(); delete m_sqliteProcess; } if (m_dlg) m_dlg->close(); delete m_dlg; QFile::remove(m_tmpFilePath); } tristate SqliteVacuum::run() { const QString dump_app = QString::fromLatin1(KDB_SQLITE_DUMP_TOOL); //sqliteDebug() << dump_app; if (dump_app.isEmpty()) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find tool \"%1\".") .arg(dump_app)); sqliteWarning() << m_result; return false; } const QString sqlite_app(KDb::sqlite3ProgramPath()); //sqliteDebug() << sqlite_app; if (sqlite_app.isEmpty()) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not find application \"%1\".") .arg(sqlite_app)); sqliteWarning() << m_result; return false; } QFileInfo fi(m_filePath); if (!fi.isReadable()) { m_result = KDbResult(ERR_OBJECT_NOT_FOUND, tr("Could not read file \"%1\".") .arg(m_filePath)); sqliteWarning() << m_result; return false; } //sqliteDebug() << fi.absoluteFilePath() << fi.absoluteDir().path(); delete m_dumpProcess; m_dumpProcess = new QProcess(this); m_dumpProcess->setWorkingDirectory(fi.absoluteDir().path()); m_dumpProcess->setReadChannel(QProcess::StandardError); connect(m_dumpProcess, SIGNAL(readyReadStandardError()), this, SLOT(readFromStdErr())); connect(m_dumpProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dumpProcessFinished(int,QProcess::ExitStatus))); delete m_sqliteProcess; m_sqliteProcess = new QProcess(this); m_sqliteProcess->setWorkingDirectory(fi.absoluteDir().path()); connect(m_sqliteProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(sqliteProcessFinished(int,QProcess::ExitStatus))); m_dumpProcess->setStandardOutputProcess(m_sqliteProcess); m_dumpProcess->start(dump_app, QStringList() << fi.absoluteFilePath()); if (!m_dumpProcess->waitForStarted()) { delete m_dumpProcess; - m_dumpProcess = 0; + m_dumpProcess = nullptr; m_result.setCode(ERR_OTHER); return false; } QTemporaryFile *tempFile = new QTemporaryFile(fi.absoluteFilePath()); tempFile->open(); m_tmpFilePath = tempFile->fileName(); delete tempFile; //sqliteDebug() << m_tmpFilePath; m_sqliteProcess->start(sqlite_app, QStringList() << m_tmpFilePath); if (!m_sqliteProcess->waitForStarted()) { delete m_dumpProcess; - m_dumpProcess = 0; + m_dumpProcess = nullptr; delete m_sqliteProcess; - m_sqliteProcess = 0; + m_sqliteProcess = nullptr; m_result.setCode(ERR_OTHER); return false; } delete m_dlg; - m_dlg = new QProgressDialog(0); // krazy:exclude=qclasses + m_dlg = new QProgressDialog(nullptr); // krazy:exclude=qclasses m_dlg->setWindowTitle(tr("Compacting database")); m_dlg->setLabelText( QLatin1String("") + tr("Compacting database \"%1\"...") .arg(QLatin1String("") + QDir::fromNativeSeparators(fi.fileName()) + QLatin1String("")) ); m_dlg->adjustSize(); m_dlg->resize(300, m_dlg->height()); m_dlg->setMinimumDuration(1000); m_dlg->setAutoClose(true); m_dlg->setRange(0, 100); m_dlg->exec(); if (m_dlg->wasCanceled()) { cancelClicked(); } delete m_dlg; - m_dlg = 0; + m_dlg = nullptr; while (m_dumpProcess->state() == QProcess::Running && m_sqliteProcess->state() == QProcess::Running) { readFromStdErr(); qApp->processEvents(QEventLoop::AllEvents, 50000); } readFromStdErr(); return true; } void SqliteVacuum::readFromStdErr() { while (true) { QByteArray s(m_dumpProcess->readLine(1000)); if (s.isEmpty()) break; //sqliteDebug() << s; if (s.startsWith("DUMP: ")) { //set previously known progress if (m_dlg) { m_dlg->setValue(m_percent); } //update progress info if (s.mid(6, 4) == "100%") { m_percent = 100; //! @todo IMPORTANT: m_dlg->setAllowCancel(false); if (m_dlg) { m_dlg->setCursor(QCursor(Qt::WaitCursor)); } } else if (s.mid(7, 1) == "%") { m_percent = s.mid(6, 1).toInt(); } else if (s.mid(8, 1) == "%") { m_percent = s.mid(6, 2).toInt(); } if (m_dlg) { m_dlg->setValue(m_percent); } } } } void SqliteVacuum::dumpProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { //sqliteDebug() << exitCode << exitStatus; if (exitCode != 0 || exitStatus != QProcess::NormalExit) { cancelClicked(); m_result.setCode(ERR_OTHER); } if (m_dlg) { m_dlg->close(); } if (m_result.isError() || m_canceled) { return; } QFileInfo fi(m_filePath); const qint64 origSize = fi.size(); const QByteArray oldName(QFile::encodeName(m_tmpFilePath)), newName(QFile::encodeName(fi.absoluteFilePath())); if (0 != ::rename(oldName.constData(), newName.constData())) { m_result.setMessage(tr("Could not rename file \"%1\" to \"%2\".") .arg(m_tmpFilePath, fi.absoluteFilePath())); sqliteWarning() << m_result; } if (!m_result.isError()) { const qint64 newSize = QFileInfo(m_filePath).size(); const qint64 decrease = 100 - 100 * newSize / origSize; - QMessageBox::information(0, QString(), // krazy:exclude=qclasses + QMessageBox::information(nullptr, QString(), // krazy:exclude=qclasses tr("The database has been compacted. Current size decreased by %1% to %2 MB.") .arg(decrease).arg(QLocale().toString(double(newSize)/1000000.0, 'f', 2))); } } void SqliteVacuum::sqliteProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { //sqliteDebug() << exitCode << exitStatus; if (exitCode != 0 || exitStatus != QProcess::NormalExit) { m_result.setCode(ERR_OTHER); return; } } void SqliteVacuum::cancelClicked() { m_sqliteProcess->terminate(); m_canceled = true; QFile::remove(m_tmpFilePath); } diff --git a/src/drivers/sqlite/SqliteVacuum.h b/src/drivers/sqlite/SqliteVacuum.h index 0b7befac..81b610e0 100644 --- a/src/drivers/sqlite/SqliteVacuum.h +++ b/src/drivers/sqlite/SqliteVacuum.h @@ -1,74 +1,74 @@ /* This file is part of the KDE project Copyright (C) 2006-2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_SQLITEVACUUM_H #define KDB_SQLITEVACUUM_H #include #include #include #include "KDbTristate.h" #include "KDbResult.h" class QProgressDialog; //! @short Helper class performing interactive compacting (VACUUM) of the SQLite database /*! Proved SQLite database filename in the constructor. Then execute run() should be executed. QProgressDialog will be displayed. Its progress bar will be updated whenever another table's data compacting is performed. User can click "Cancel" button in any time (except the final committing) to cancel the operation. In this case, it's guaranteed that the original file remains unchanged. This is possible because we rely on SQLite's VACUUM SQL command, which itself temporarily creates a copy of the original database file, and replaces the orginal with the new only on success. */ class SqliteVacuum : public QObject, public KDbResultable { Q_OBJECT public: explicit SqliteVacuum(const QString& filePath); - ~SqliteVacuum(); + ~SqliteVacuum() override; /*! Performs compacting procedure. @return true on success, false on failure and cancelled if user clicked "Cancel" button in the progress dialog. */ tristate run(); public Q_SLOTS: void readFromStdErr(); void dumpProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void sqliteProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void cancelClicked(); private: QString m_filePath; QString m_tmpFilePath; QProcess *m_dumpProcess; QProcess *m_sqliteProcess; QProgressDialog* m_dlg; // krazy:exclude=qclasses int m_percent; bool m_canceled; Q_DISABLE_COPY(SqliteVacuum) }; #endif diff --git a/src/drivers/sqlite/icu/icu.cpp b/src/drivers/sqlite/icu/icu.cpp index 7f78375d..e436b317 100644 --- a/src/drivers/sqlite/icu/icu.cpp +++ b/src/drivers/sqlite/icu/icu.cpp @@ -1,508 +1,508 @@ /* ** 2007 May 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** $Id: icu.c,v 1.7 2007/12/13 21:54:11 drh Exp $ ** ** This file implements an integration between the ICU library ** ("International Components for Unicode", an open-source library ** for handling unicode data) and SQLite. The integration uses ** ICU to provide the following to SQLite: ** ** * An implementation of the SQL regexp() function (and hence REGEXP ** operator) using the ICU uregex_XX() APIs. ** ** * Implementations of the SQL scalar upper() and lower() functions ** for case mapping. ** ** * Integration of ICU and SQLite collation seqences. ** ** * An implementation of the LIKE operator that uses ICU to ** provide case-independent matching. */ #include "sqliteicu.h" #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) /* Include ICU headers */ #include #include #include #include #include #if U_ICU_VERSION_MAJOR_NUM>=51 #include #endif #include #ifndef SQLITE_CORE #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #else #include "sqlite3.h" #endif /* ** Maximum length (in bytes) of the pattern in a LIKE or GLOB ** operator. */ #ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH # define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000 #endif /* ** Version of sqlite3_free() that is always a function, never a macro. */ static void xFree(void *p){ sqlite3_free(p); } /* ** Compare two UTF-8 strings for equality where the first string is ** a "LIKE" expression. Return true (1) if they are the same and ** false (0) if they are different. */ static int icuLikeCompare( const uint8_t *zPattern, /* LIKE pattern */ const uint8_t *zString, /* The UTF-8 string to compare against */ const UChar32 uEsc /* The escape character */ ){ static const int MATCH_ONE = (UChar32)'_'; static const int MATCH_ALL = (UChar32)'%'; int iPattern = 0; /* Current byte index in zPattern */ int iString = 0; /* Current byte index in zString */ int prevEscape = 0; /* True if the previous character was uEsc */ while( zPattern[iPattern]!=0 ){ /* Read (and consume) the next character from the input pattern. */ UChar32 uPattern; U8_NEXT_UNSAFE(zPattern, iPattern, uPattern); assert(uPattern!=0); /* There are now 4 possibilities: ** ** 1. uPattern is an unescaped match-all character "%", ** 2. uPattern is an unescaped match-one character "_", ** 3. uPattern is an unescaped escape character, or ** 4. uPattern is to be handled as an ordinary character */ if( !prevEscape && uPattern==MATCH_ALL ){ /* Case 1. */ uint8_t c; /* Skip any MATCH_ALL or MATCH_ONE characters that follow a ** MATCH_ALL. For each MATCH_ONE, skip one character in the ** test string. */ while( (c=zPattern[iPattern]) == MATCH_ALL || c == MATCH_ONE ){ if( c==MATCH_ONE ){ if( zString[iString]==0 ) return 0; U8_FWD_1_UNSAFE(zString, iString); } iPattern++; } if( zPattern[iPattern]==0 ) return 1; while( zString[iString] ){ if( icuLikeCompare(&zPattern[iPattern], &zString[iString], uEsc) ){ return 1; } U8_FWD_1_UNSAFE(zString, iString); } return 0; }else if( !prevEscape && uPattern==MATCH_ONE ){ /* Case 2. */ if( zString[iString]==0 ) return 0; U8_FWD_1_UNSAFE(zString, iString); }else if( !prevEscape && uPattern==uEsc){ /* Case 3. */ prevEscape = 1; }else{ /* Case 4. */ UChar32 uString; U8_NEXT_UNSAFE(zString, iString, uString); uString = u_foldCase(uString, U_FOLD_CASE_DEFAULT); uPattern = u_foldCase(uPattern, U_FOLD_CASE_DEFAULT); if( uString!=uPattern ){ return 0; } prevEscape = 0; } } return zString[iString]==0; } /* ** Implementation of the like() SQL function. This function implements ** the build-in LIKE operator. The first argument to the function is the ** pattern and the second argument is the string. So, the SQL statements: ** ** A LIKE B ** ** is implemented as like(B, A). If there is an escape character E, ** ** A LIKE B ESCAPE E ** ** is mapped to like(B, A, E). */ static void icuLikeFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *zA = sqlite3_value_text(argv[0]); const unsigned char *zB = sqlite3_value_text(argv[1]); UChar32 uEsc = 0; /* Limit the length of the LIKE or GLOB pattern to avoid problems ** of deep recursion and N*N behavior in patternCompare(). */ if( sqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){ sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1); return; } if( argc==3 ){ /* The escape character string must consist of a single UTF-8 character. ** Otherwise, return an error. */ int nE= sqlite3_value_bytes(argv[2]); const unsigned char *zE = sqlite3_value_text(argv[2]); int i = 0; - if( zE==0 ) return; + if( zE==nullptr ) return; U8_NEXT(zE, i, nE, uEsc); if( i!=nE){ sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); return; } } if( zA && zB ){ sqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc)); } } /* ** This function is called when an ICU function called from within ** the implementation of an SQL scalar function returns an error. ** ** The scalar function context passed as the first argument is ** loaded with an error message based on the following two args. */ static void icuFunctionError( sqlite3_context *pCtx, /* SQLite scalar function context */ const char *zName, /* Name of ICU function that failed */ UErrorCode e /* Error code returned by ICU function */ ){ char zBuf[128]; sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e)); zBuf[127] = '\0'; sqlite3_result_error(pCtx, zBuf, -1); } /* ** Function to delete compiled regexp objects. Registered as ** a destructor function with sqlite3_set_auxdata(). */ static void icuRegexpDelete(void *p){ URegularExpression *pExpr = (URegularExpression *)p; uregex_close(pExpr); } /* ** Implementation of SQLite REGEXP operator. This scalar function takes ** two arguments. The first is a regular expression pattern to compile ** the second is a string to match against that pattern. If either ** argument is an SQL NULL, then NULL is returned. Otherwise, the result ** is 1 if the string matches the pattern, or 0 otherwise. ** ** SQLite maps the regexp() function to the regexp() operator such ** that the following two are equivalent: ** ** zString REGEXP zPattern ** regexp(zPattern, zString) ** ** Uses the following ICU regexp APIs: ** ** uregex_open() ** uregex_matches() ** uregex_close() */ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ UErrorCode status = U_ZERO_ERROR; URegularExpression *pExpr; UBool res; const UChar *zString = static_cast(sqlite3_value_text16(apArg[1])); (void)nArg; /* Unused parameter */ /* If the left hand side of the regexp operator is NULL, ** then the result is also NULL. */ if( !zString ){ return; } pExpr = static_cast(sqlite3_get_auxdata(p, 0)); if( !pExpr ){ const UChar *zPattern = static_cast(sqlite3_value_text16(apArg[0])); if( !zPattern ){ return; } - pExpr = uregex_open(zPattern, -1, 0, 0, &status); + pExpr = uregex_open(zPattern, -1, 0, nullptr, &status); if( U_SUCCESS(status) ){ sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete); }else{ assert(!pExpr); icuFunctionError(p, "uregex_open", status); return; } } /* Configure the text that the regular expression operates on. */ uregex_setText(pExpr, zString, -1, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "uregex_setText", status); return; } /* Attempt the match */ res = uregex_matches(pExpr, 0, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "uregex_matches", status); return; } /* Set the text that the regular expression operates on to a NULL ** pointer. This is not really necessary, but it is tidier than ** leaving the regular expression object configured with an invalid ** pointer after this function returns. */ - uregex_setText(pExpr, 0, 0, &status); + uregex_setText(pExpr, nullptr, 0, &status); /* Return 1 or 0. */ sqlite3_result_int(p, res ? 1 : 0); } /* ** Implementations of scalar functions for case mapping - upper() and ** lower(). Function upper() converts its input to upper-case (ABC). ** Function lower() converts to lower-case (abc). ** ** ICU provides two types of case mapping, "general" case mapping and ** "language specific". Refer to ICU documentation for the differences ** between the two. ** ** To utilise "general" case mapping, the upper() or lower() scalar ** functions are invoked with one argument: ** ** upper('ABC') -> 'abc' ** lower('abc') -> 'ABC' ** ** To access ICU "language specific" case mapping, upper() or lower() ** should be invoked with two arguments. The second argument is the name ** of the locale to use. Passing an empty string ("") or SQL NULL value ** as the second argument is the same as invoking the 1 argument version ** of upper() or lower(). ** ** lower('I', 'en_us') -> 'i' ** lower('I', 'tr_tr') -> 'ı' (small dotless i) ** ** http://www.icu-project.org/userguide/posix.html#case_mappings */ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ const UChar *zInput; UChar *zOutput; int nInput; int nOutput; UErrorCode status = U_ZERO_ERROR; - const unsigned char *zLocale = 0; + const unsigned char *zLocale = nullptr; assert(nArg==1 || nArg==2); if( nArg==2 ){ zLocale = static_cast(sqlite3_value_text(apArg[1])); } zInput = static_cast(sqlite3_value_text16(apArg[0])); if( !zInput ){ return; } nInput = sqlite3_value_bytes16(apArg[0]); nOutput = nInput * 2 + 2; zOutput = static_cast(sqlite3_malloc(nOutput)); if( !zOutput ){ return; } if( sqlite3_user_data(p) ){ u_strToUpper(zOutput, nOutput/2, zInput, nInput/2, reinterpret_cast(zLocale), &status); }else{ u_strToLower(zOutput, nOutput/2, zInput, nInput/2, reinterpret_cast(zLocale), &status); } if( !U_SUCCESS(status) ){ icuFunctionError(p, "u_strToLower()/u_strToUpper", status); return; } sqlite3_result_text16(p, zOutput, -1, xFree); } /* ** Collation sequence destructor function. The pCtx argument points to ** a UCollator structure previously allocated using ucol_open(). */ static void icuCollationDel(void *pCtx){ UCollator *p = (UCollator *)pCtx; ucol_close(p); } /* ** Collation sequence comparison function. The pCtx argument points to ** a UCollator structure previously allocated using ucol_open(). */ static int icuCollationColl( void *pCtx, int nLeft, const void *zLeft, int nRight, const void *zRight ){ UCollationResult res; UCollator *p = (UCollator *)pCtx; res = ucol_strcoll(p, (UChar *)zLeft, nLeft/2, (UChar *)zRight, nRight/2); switch( res ){ case UCOL_LESS: return -1; case UCOL_GREATER: return +1; case UCOL_EQUAL: return 0; } assert(!"Unexpected return value from ucol_strcoll()"); return 0; } /* ** Implementation of the scalar function icu_load_collation(). ** ** This scalar function is used to add ICU collation based collation ** types to an SQLite database connection. It is intended to be called ** as follows: ** ** SELECT icu_load_collation(, ); ** ** Where is a string containing an ICU locale identifier (i.e. ** "en_AU", "tr_TR" etc.) and is the name of the ** collation sequence to create. */ static void icuLoadCollation( sqlite3_context *p, int nArg, sqlite3_value **apArg ){ sqlite3 *db = (sqlite3 *)sqlite3_user_data(p); UErrorCode status = U_ZERO_ERROR; const char *zLocale; /* Locale identifier - (eg. "jp_JP") */ const char *zName; /* SQL Collation sequence name (eg. "japanese") */ UCollator *pUCollator; /* ICU library collation object */ int rc; /* Return code from sqlite3_create_collation_x() */ assert(nArg==2); zLocale = (const char *)sqlite3_value_text(apArg[0]); zName = (const char *)sqlite3_value_text(apArg[1]); if( !zLocale || !zName ){ return; } pUCollator = ucol_open(zLocale, &status); if( !U_SUCCESS(status) ){ icuFunctionError(p, "ucol_open", status); return; } assert(p); rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator, icuCollationColl, icuCollationDel ); if( rc!=SQLITE_OK ){ ucol_close(pUCollator); sqlite3_result_error(p, "Error registering collation function", -1); } } /* ** Register the ICU extension functions with database db. */ KDB_SQLITE_ICU_EXPORT int sqlite3IcuInit(sqlite3 *db){ struct IcuScalar { const char *zName; /* Function name */ int nArg; /* Number of arguments */ int enc; /* Optimal text encoding */ void *pContext; /* sqlite3_user_data() context */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } scalars[] = { - {"regexp", 2, SQLITE_ANY, 0, icuRegexpFunc}, + {"regexp", 2, SQLITE_ANY, nullptr, icuRegexpFunc}, - {"lower", 1, SQLITE_UTF16, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF16, 0, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF16, nullptr, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF16, nullptr, icuCaseFunc16}, {"upper", 1, SQLITE_UTF16, (void*)1, icuCaseFunc16}, {"upper", 2, SQLITE_UTF16, (void*)1, icuCaseFunc16}, - {"lower", 1, SQLITE_UTF8, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF8, 0, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF8, nullptr, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF8, nullptr, icuCaseFunc16}, {"upper", 1, SQLITE_UTF8, (void*)1, icuCaseFunc16}, {"upper", 2, SQLITE_UTF8, (void*)1, icuCaseFunc16}, - {"like", 2, SQLITE_UTF8, 0, icuLikeFunc}, - {"like", 3, SQLITE_UTF8, 0, icuLikeFunc}, + {"like", 2, SQLITE_UTF8, nullptr, icuLikeFunc}, + {"like", 3, SQLITE_UTF8, nullptr, icuLikeFunc}, {"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation}, }; int rc = SQLITE_OK; int i; for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){ struct IcuScalar *p = &scalars[i]; rc = sqlite3_create_function( - db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, 0, 0 + db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, nullptr, nullptr ); } return rc; } #if !defined SQLITE_CORE || !SQLITE_CORE KDB_SQLITE_ICU_EXPORT int sqlite3_extension_init( sqlite3 *db, char **pzErrMsg, const struct sqlite3_api_routines *pApi ){ (void)pzErrMsg; /* Unused parameter */ SQLITE_EXTENSION_INIT2(pApi) return sqlite3IcuInit(db); } #endif #endif diff --git a/src/drivers/sybase/SybaseConnection.h b/src/drivers/sybase/SybaseConnection.h index e911fd7b..f56e1dcf 100644 --- a/src/drivers/sybase/SybaseConnection.h +++ b/src/drivers/sybase/SybaseConnection.h @@ -1,80 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2007 Sharan Rao This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SYBASECONNECTION_H #define SYBASECONNECTION_H #include #include "KDbConnection.h" #include "SybaseCursor.h" class SybaseConnectionInternal; /*! @short Provides database connection, allowing queries and data modification. */ class SybaseConnection : public KDbConnection { public: virtual ~SybaseConnection(); - KDbCursor* prepareQuery(const KDbEscapedString& sql, int cursor_options = 0) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; - KDbCursor* prepareQuery(KDbQuerySchema* query, int cursor_options = 0) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(const KDbEscapedString &sql, + int cursor_options = 0) override Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(KDbQuerySchema *query, + int cursor_options = 0) override Q_REQUIRED_RESULT; KDbPreparedStatement prepareStatement(KDbPreparedStatement::StatementType type, - KDbFieldList* fields) Q_DECL_OVERRIDE; + KDbFieldList *fields) override; protected: /*! Used by driver */ SybaseConnection(KDbDriver *driver, const KDbConnectionData& connData); virtual bool drv_connect(KDbServerVersionInfo* version); virtual bool drv_disconnect(); virtual bool drv_getDatabasesList(QStringList* list); virtual bool drv_createDatabase(const QString &dbName = QString()); virtual bool drv_useDatabase(const QString &dbName = QString(), bool *cancelled = 0, KDbMessageHandler* msgHandler = 0); virtual bool drv_closeDatabase(); virtual bool drv_dropDatabase(const QString &dbName = QString()); virtual bool drv_executeSQL(const KDbEscapedString& sql); virtual quint64 drv_lastInsertRecordId(); //! Implemented for KDbResultable virtual QString serverResultName() const; // virtual void drv_clearServerResult(); //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_getTablesList(QStringList* list); //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_containsTable(const QString &tableName); virtual bool drv_beforeInsert(const QString& table, KDbFieldList* fields); virtual bool drv_afterInsert(const QString& table, KDbFieldList* fields); virtual bool drv_beforeUpdate(const QString& table, KDbFieldList* fields); virtual bool drv_afterUpdate(const QString& table, KDbFieldList* fields); SybaseConnectionInternal* d; friend class SybaseDriver; friend class SybaseCursor; }; #endif diff --git a/src/drivers/sybase/SybaseConnection_p.cpp b/src/drivers/sybase/SybaseConnection_p.cpp index 8f97ddba..bdcb2494 100644 --- a/src/drivers/sybase/SybaseConnection_p.cpp +++ b/src/drivers/sybase/SybaseConnection_p.cpp @@ -1,248 +1,248 @@ /* This file is part of the KDE project Copyright (C) 2007 Sharan Rao This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "SybaseConnection_p.h" #include "KDbConnectionData.h" QMap SybaseConnectionInternal::dbProcessConnectionMap; int connectionMessageHandler(DBPROCESS* dbproc, DBINT msgno, int msgstate, int severity, char* msgtext, char* srvname, char* procname, int line) { if (!dbproc) { return 0; } SybaseConnectionInternal* conn = SybaseConnectionInternal::dbProcessConnectionMap[dbproc]; if (conn) conn->messageHandler(msgno, msgstate, severity, msgtext, srvname, procname, line); return (0); } SybaseConnectionInternal::SybaseConnectionInternal(KDbConnection* connection) : ConnectionInternal(connection) , dbProcess(0) , res(0) { } SybaseConnectionInternal::~SybaseConnectionInternal() { if (sybase_owned && dbProcess) { dbclose(dbProcess); dbProcess = 0; } } void SybaseConnectionInternal::storeResult() { //sybaseDebug() << "Store Result!!"; // all message numbers and message texts were handled in the messageHandler // so don't do anything here } void SybaseConnectionInternal::messageHandler(DBINT msgno, int msgstate, int severity, char* msgtext, char* srvname, char* procname, int line) { Q_UNUSED(msgstate); Q_UNUSED(severity); Q_UNUSED(srvname); Q_UNUSED(procname); Q_UNUSED(line); res = msgno; errmsg = QString::fromLatin1(msgtext); //sybaseDebug() << "Message Handler" << res << errmsg; } bool SybaseConnectionInternal::db_connect(const KDbConnectionData& data) { if (dbinit() == FAIL) return false; // set message handler dbmsghandle(connectionMessageHandler); QByteArray localSocket; QString hostName = data.hostName; if (data.serverName.isEmpty()) { sybaseWarning() << "Can't connect without server name"; return false; } // set Error.handlers // set message handlers LOGINREC* login; login = dblogin(); - if (login == NULL) { + if (!login) { //dbexit(); return false; } // umm, copied from pqxx driver. if (hostName.isEmpty() || 0 == hostName.compare(QLatin1String("localhost"), Qt::CaseInsensitive)) { if (data.useLocalSocketFile) { if (data.localSocketFileName.isEmpty()) { QStringList sockets; #ifndef Q_OS_WIN sockets.append("/tmp/s.sybase.2638"); foreach(const QString& socket, sockets) { if (QFile(socket).exists()) { localSocket = socket.toLatin1(); break; } } #endif } else localSocket = QFile::encodeName(data.localSocketFileName); } else { //we're not using local socket hostName = "127.0.0.1"; } } QTemporaryFile confFile(QDir::tempPath() + QLatin1String("/kdb_sybase_XXXXXX.conf")); confFile.open(); QTextStream out(&confFile); // write global portion out << "[global]" << "\n"; out << " text size = " << 64512 << "\n" ; // Copied from default freetds.conf. is there a more reasonable number? // write server portion out << '[' << data.serverName << ']' << "\n"; out << " host = " << hostName << "\n"; if (data.port == 0) out << " port = " << 5000 << "\n"; // default port to be used else out << " port = " << data.port << "\n"; out << " tds version = " << 5.0 << "\n"; // set the file to be read as confFile dbsetifile(confFile.fileName().toLatin1().data()); // set Login parameters QByteArray pwd(data.password.isNull() ? QByteArray() : data.password.toLatin1()); DBSETLUSER(login, data.userName.toLatin1()); DBSETLPWD(login, pwd); DBSETLAPP(login, qApp->applicationName().toLatin1()); // make the connection // Host name assumed to be same as servername // where are ports specified ? ( in the interfaces file ? ) dbProcess = dbopen(login, data.serverName.toLatin1().data()); dbloginfree(login); // Set/ Unset quoted identifier ? ? - if (dbProcess != NULL) { + if (dbProcess) { // add to map SybaseConnectionInternal::dbProcessConnectionMap[dbProcess] = this; // set buffering to be true // what's a reasonable value of no. of rows to be kept in buffer ? // dbsetopt( dbProcess, DBBUFFER, "500", -1 ); // set quoted identifier to be true dbsetopt(dbProcess, DBQUOTEDIDENT, "1", -1); return true; } storeResult(); //dbexit(); // setError(ERR_DB_SPECIFIC,err); return false; } bool SybaseConnectionInternal::db_disconnect() { dbclose(dbProcess); dbProcess = 0; return true; } bool SybaseConnectionInternal::useDatabase(const QString &dbName) { if (dbuse(dbProcess, dbName.toLatin1().data()) == SUCCEED) { return true; } return false; } bool SybaseConnectionInternal::executeSQL(const KDbEscapedString& sql) { // remove queries in buffer if any. flush existing results if any dbcancel(dbProcess); // put query in command bufffer dbcmd(dbProcess, sql.constData()); if (dbsqlexec(dbProcess) == SUCCEED) { while (dbresults(dbProcess) != NO_MORE_RESULTS) { /* nop */ } return true; } // Error.handling storeResult(); return false; } QString SybaseConnectionInternal::escapeIdentifier(const QString& str) const { return QString(str).replace("'", "''"); } //-------------------------------------- SybaseCursorData::SybaseCursorData(KDbConnection* connection) : SybaseConnectionInternal(connection) , numRows(0) { sybase_owned = false; } SybaseCursorData::~SybaseCursorData() { } diff --git a/src/drivers/xbase/XbaseConnection.h b/src/drivers/xbase/XbaseConnection.h index f0dcd09d..1c039565 100644 --- a/src/drivers/xbase/XbaseConnection.h +++ b/src/drivers/xbase/XbaseConnection.h @@ -1,74 +1,76 @@ /* This file is part of the KDE project Copyright (C) 2008 Sharan Rao This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef XBASECONNECTION_H #define XBASECONNECTION_H #include #include "KDbConnection.h" #include "XbaseCursor.h" class xBaseConnectionInternal; /*! @short Provides database connection, allowing queries and data modification. */ class xBaseConnection : public KDbConnection { public: virtual ~xBaseConnection(); - KDbCursor* prepareQuery(const KDbEscapedString& sql, int cursor_options = 0) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; - KDbCursor* prepareQuery(KDbQuerySchema* query, int cursor_options = 0) Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(const KDbEscapedString &sql, + int cursor_options = 0) override Q_REQUIRED_RESULT; + KDbCursor *prepareQuery(KDbQuerySchema *query, + int cursor_options = 0) override Q_REQUIRED_RESULT; - //! @todo returns 0 for now - KDbPreparedStatementInterface* prepareStatementInternal() Q_DECL_OVERRIDE Q_REQUIRED_RESULT; + //! @todo returns @c nullptr for now + KDbPreparedStatementInterface *prepareStatementInternal() override Q_REQUIRED_RESULT; - protected: +protected: /*! Used by driver */ xBaseConnection(KDbDriver *driver, KDbDriver* internalDriver, const KDbConnectionData& connData); virtual bool drv_connect(KDbServerVersionInfo* version); virtual bool drv_disconnect(); virtual bool drv_getDatabasesList(QStringList* list); virtual bool drv_createDatabase( const QString &dbName = QString() ); virtual bool drv_useDatabase( const QString &dbName = QString(), bool *cancelled = 0, KDbMessageHandler* msgHandler = 0 ); virtual bool drv_closeDatabase(); virtual bool drv_dropDatabase(const QString &dbName = QString()); virtual bool drv_executeSQL(const KDbEscapedString& sql); virtual quint64 drv_lastInsertRecordId(); //! Implemented for KDbResultable virtual QString serverResultName() const; // virtual void drv_clearServerResult(); //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_getTablesList(QStringList* list); //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_containsTable(const QString &tableName); xBaseConnectionInternal* d; friend class xBaseDriver; friend class xBaseCursor; }; #endif diff --git a/src/expression/KDbBinaryExpression.cpp b/src/expression/KDbBinaryExpression.cpp index 19796d50..f218bbad 100644 --- a/src/expression/KDbBinaryExpression.cpp +++ b/src/expression/KDbBinaryExpression.cpp @@ -1,429 +1,429 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbExpression.h" #include "KDb.h" #include "KDbQuerySchema.h" #include "KDbDriver.h" #include "KDbParser_p.h" #include "kdb_debug.h" #include "generated/sqlparser.h" KDbBinaryExpressionData::KDbBinaryExpressionData() : KDbExpressionData() { ExpressionDebug << "BinaryExpressionData" << ref; } KDbBinaryExpressionData::~KDbBinaryExpressionData() { } bool KDbBinaryExpressionData::validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack) { if (children.count() != 2) return false; if (!left()->validate(parseInfo, callStack)) return false; if (!right()->validate(parseInfo, callStack)) return false; //update type for query parameters //! @todo IMPORTANT: update type for query parameters #if 0 if (left()->isQueryParameter()) { KDbQueryParameterExpression queryParameter = left()->toQueryParameter(); queryParameter->setType(left()->type()); } if (right()->isQueryParameter()) { KDbQueryParameterExpression queryParameter = right()->toQueryParameter(); queryParameter->setType(right()->type()); } #endif if (typeInternal(callStack) == KDbField::InvalidType) { parseInfo->setErrorMessage(tr("Incompatible types of arguments")); parseInfo->setErrorDescription( tr("Expression \"%1\" requires compatible types of arguments. " "Specified arguments are of type %2 and %3.", "Binary expression arguments type error") - .arg(toStringInternal(0, 0, callStack).toString(), + .arg(toStringInternal(nullptr, nullptr, callStack).toString(), KDbField::typeName(left()->type()), KDbField::typeName(right()->type()))); return false; } return true; } KDbField::Type KDbBinaryExpressionData::typeInternal(KDb::ExpressionCallStack* callStack) const { if (children.count() != 2 || expressionClass == KDb::UnknownExpression) return KDbField::InvalidType; const KDbField::Type lt = left()->type(callStack); const KDbField::Type rt = right()->type(callStack); if (lt == KDbField::InvalidType || rt == KDbField::InvalidType) return KDbField::InvalidType; const bool ltNull = lt == KDbField::Null; const bool rtNull = rt == KDbField::Null; const bool ltText = KDbField::isTextType(lt); const bool rtText = KDbField::isTextType(rt); const bool ltInt = KDbField::isIntegerType(lt); const bool rtInt = KDbField::isIntegerType(rt); const bool ltFP = KDbField::isFPNumericType(lt); const bool rtFP = KDbField::isFPNumericType(rt); const bool ltBool = lt == KDbField::Boolean; const bool rtBool = rt == KDbField::Boolean; const KDbField::TypeGroup ltGroup = KDbField::typeGroup(lt); const KDbField::TypeGroup rtGroup = KDbField::typeGroup(rt); const bool lAny = left()->convertConst(); const bool rAny = right()->convertConst(); if (ltNull || rtNull) { switch (token.value()) { //! @todo add general support, e.g. for "NULL AND (1 == 1)"; for now we only support //! constants because there's no evaluation and operations with NULL depend on whether we have TRUE or FALSE //! See http://www.postgresql.org/docs/9.4/static/functions-logical.html //! https://dev.mysql.com/doc/refman/5.0/en/logical-operators.html case OR: { const KDbConstExpressionData *leftConst = left()->convertConst(); const KDbConstExpressionData *rightConst = right()->convertConst(); if ((ltBool && leftConst && leftConst->value.toBool()) // true OR NULL is true || (rtBool && rightConst && rightConst->value.toBool())) // NULL OR true is true { return KDbField::Boolean; } else if ((ltBool && leftConst && !leftConst->value.toBool()) // false OR NULL is NULL || (rtBool && rightConst && !rightConst->value.toBool()) // NULL OR false is NULL || lAny // Any OR NULL may be NULL || rAny) // NULL OR Any may be NULL //! @todo Any OR NULL may be also TRUE -- but this needs support of fuzzy/multivalue types //! @todo NULL OR Any may be also TRUE -- but this needs support of fuzzy/multivalue types { return KDbField::Null; } break; } case AND: { const KDbConstExpressionData *leftConst = left()->convertConst(); const KDbConstExpressionData *rightConst = right()->convertConst(); if ((ltBool && leftConst && !leftConst->value.toBool()) // false AND NULL is false || (rtBool && rightConst && !rightConst->value.toBool())) // NULL AND false is false { return KDbField::Boolean; } else if ((ltBool && leftConst && leftConst->value.toBool()) // true AND NULL is NULL || (rtBool && rightConst && rightConst->value.toBool()) // NULL AND true is NULL || lAny // Any AND NULL may be NULL || rAny) // NULL AND Any may be NULL //! @todo Any AND NULL may be also FALSE -- but this needs support of fuzzy/multivalue types //! @todo NULL AND Any may be also FALSE -- but this needs support of fuzzy/multivalue types { return KDbField::Null; } break; } case XOR: {// Logical XOR. Returns NULL if either operand is NULL. For non-NULL operands, // evaluates to 1 if an odd number of operands is nonzero, otherwise 0 is returned. // a XOR b is mathematically equal to (a AND (NOT b)) OR ((NOT a) and b). // https://dev.mysql.com/doc/refman/5.0/en/logical-operators.html#operator_xor return KDbField::Null; } default: return KDbField::Null; } } switch (token.value()) { case OR: case AND: case XOR: { if (ltNull && rtNull) { return KDbField::Null; } else if ((ltBool && rtBool) || (ltBool && rAny) || (lAny && rtBool) || (lAny && rAny)) { return KDbField::Boolean; } return KDbField::InvalidType; } case '+': case CONCATENATION: if (lt == KDbField::Text && rt == KDbField::Text) { return KDbField::Text; } else if ((ltText && rtText) || (ltText && rAny) || (lAny && rtText)) { return KDbField::LongText; } else if ((ltText && rtNull) || (ltNull && rtText) || (lAny && rtNull) || (ltNull && rAny)) { return KDbField::Null; } else if (token.value() == CONCATENATION) { if (lAny && rAny) { return KDbField::LongText; } return KDbField::InvalidType; } break; // '+' can still be handled below for non-text types default:; } if (expressionClass == KDb::RelationalExpression) { if ((ltText && rtText) || (ltText && rAny) || (lAny && rtText) || (lAny && rAny) || (ltInt && rtInt) || (ltInt && rAny) || (lAny && rtInt) || (ltFP && rtFP) || (ltInt && rtFP) || (ltFP && rtInt) || (ltFP && rAny) || (lAny && rtFP) || (ltBool && rtBool) || (ltBool && rtInt) || (ltInt && rtBool) || (ltBool && rAny) || (lAny && rtBool) || (ltBool && rtFP) || (ltFP && rtBool) || (ltGroup == KDbField::DateTimeGroup && rtGroup == KDbField::DateTimeGroup) || (ltGroup == KDbField::DateTimeGroup && rAny) || (lAny && rtGroup == KDbField::DateTimeGroup)) { return KDbField::Boolean; } return KDbField::InvalidType; } if (expressionClass == KDb::ArithmeticExpression) { if (lAny && rAny) { return KDbField::Integer; } else if ((ltInt && rtInt) || (ltInt && rAny) || (lAny && rtInt)) { /* From documentation of KDb::maximumForIntegerFieldTypes(): In case of KDb::ArithmeticExpression: returned type may not fit to the result of evaluated expression that involves the arguments. For example, 100 is within Byte type, maximumForIntegerFieldTypes(Byte, Byte) is Byte but result of 100 * 100 exceeds the range of Byte. Solution: for types smaller than Integer (e.g. Byte and ShortInteger) we are returning Integer type. */ KDbField::Type t; if (lAny) { t = rt; } else if (rAny) { t = lt; } else { t = KDb::maximumForIntegerFieldTypes(lt, rt); } if (t == KDbField::Byte || t == KDbField::ShortInteger) { return KDbField::Integer; } return t; } switch (token.value()) { case '&': case BITWISE_SHIFT_RIGHT: case BITWISE_SHIFT_LEFT: if ((ltFP && rtFP) || (ltFP && rAny) //! @todo can be other Integer too || (lAny && rtFP)) //! @todo can be other Integer too { return KDbField::Integer; } else if ((ltFP && rtInt) // inherit from right || (lAny && rtInt)) { return rt; } else if ((ltInt && rtFP) // inherit from left || (ltInt && rAny)) { return lt; } break; default:; } /* inherit floating point (Float or Double) type */ if (ltFP && (rtInt || lt == rt || rAny)) return lt; if (rtFP && (ltInt || lt == rt || lAny)) return rt; } return KDbField::InvalidType; } KDbBinaryExpressionData* KDbBinaryExpressionData::clone() { ExpressionDebug << "BinaryExpressionData::clone" << *this; return new KDbBinaryExpressionData(*this); } void KDbBinaryExpressionData::debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const { dbg.nospace() << "BinaryExp(class=" << expressionClassName(expressionClass) << ","; if (children.count() == 2 && left().constData()) { left()->debug(dbg, callStack); } else { dbg.nospace() << ""; } dbg.nospace() << "," << token << ","; if (children.count() == 2 && right().constData()) { right()->debug(dbg, callStack); } else { dbg.nospace() << ""; } dbg.nospace() << ",type=" << KDbDriver::defaultSQLTypeName(type()) << ")"; } KDbEscapedString KDbBinaryExpressionData::toStringInternal( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { switch (token.value()) { case '+': case CONCATENATION: { if (driver && KDbField::isTextType(type())) { const KDbBinaryExpression binaryExpr(const_cast(this)); return driver->concatenateFunctionToString(binaryExpr, params, callStack); } break; } default:; } #define INFIX(a) \ (left().constData() ? left()->toString(driver, params, callStack) : KDbEscapedString("")) \ + " " + a + " " + (right().constData() ? right()->toString(driver, params, callStack) : KDbEscapedString("")) return INFIX(token.toString(driver)); #undef INFIX } void KDbBinaryExpressionData::getQueryParameters(QList* params) { Q_ASSERT(params); if (left().constData()) left()->getQueryParameters(params); if (right().constData()) right()->getQueryParameters(params); } ExplicitlySharedExpressionDataPointer KDbBinaryExpressionData::left() const { return (children.count() > 0) ? children.at(0) : ExplicitlySharedExpressionDataPointer(); } ExplicitlySharedExpressionDataPointer KDbBinaryExpressionData::right() const { return (children.count() > 1) ? children.at(1) : ExplicitlySharedExpressionDataPointer(); } //========================================= static KDb::ExpressionClass classForArgs(const KDbExpression& leftExpr, KDbToken token, const KDbExpression& rightExpr) { if (leftExpr.isNull()) { kdbWarning() << "KDbBinaryExpression set to null because left argument is not specified"; return KDb::UnknownExpression; } if (rightExpr.isNull()) { kdbWarning() << "KDbBinaryExpression set to null because right argument is not specified"; return KDb::UnknownExpression; } return KDbExpression::classForToken(token); } KDbBinaryExpression::KDbBinaryExpression() : KDbExpression(new KDbBinaryExpressionData) { ExpressionDebug << "KDbBinaryExpression() ctor" << *this; } KDbBinaryExpression::KDbBinaryExpression(const KDbExpression& leftExpr, KDbToken token, const KDbExpression& rightExpr) : KDbExpression(new KDbBinaryExpressionData, classForArgs(leftExpr, token, rightExpr), token) { if (!isNull()) { appendChild(leftExpr.d); appendChild(rightExpr.d); } } KDbBinaryExpression::KDbBinaryExpression(const KDbBinaryExpression& expr) : KDbExpression(expr) { } KDbBinaryExpression::KDbBinaryExpression(KDbExpressionData* data) : KDbExpression(data) { ExpressionDebug << "KDbBinaryExpression(KDbExpressionData*) ctor" << *this; } KDbBinaryExpression::KDbBinaryExpression(const ExplicitlySharedExpressionDataPointer &ptr) : KDbExpression(ptr) { } KDbBinaryExpression::~KDbBinaryExpression() { } KDbExpression KDbBinaryExpression::left() const { return (d->children.count() > 0) ? KDbExpression(d->children.at(0)) : KDbExpression(); } void KDbBinaryExpression::setLeft(const KDbExpression& leftExpr) { KDbExpression::setLeftOrRight(leftExpr, 0); } KDbExpression KDbBinaryExpression::right() const { return (d->children.count() > 1) ? KDbExpression(d->children.at(1)) : KDbExpression(); } void KDbBinaryExpression::setRight(const KDbExpression& rightExpr) { KDbExpression::setLeftOrRight(rightExpr, 1); } diff --git a/src/expression/KDbConstExpression.cpp b/src/expression/KDbConstExpression.cpp index fb78d991..0f83b9db 100644 --- a/src/expression/KDbConstExpression.cpp +++ b/src/expression/KDbConstExpression.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2003-2012 Jarosław Staniek Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbExpression.h" #include "KDb.h" #include "KDbQuerySchema.h" #include "KDbDriver.h" #include "kdb_debug.h" #include "generated/sqlparser.h" #include KDbConstExpressionData::KDbConstExpressionData(const QVariant& aValue) : KDbExpressionData() , value(aValue) { ExpressionDebug << "ConstExpressionData" << ref; } KDbConstExpressionData::~KDbConstExpressionData() { ExpressionDebug << "~ConstExpressionData" << ref; } KDbConstExpressionData* KDbConstExpressionData::clone() { ExpressionDebug << "ConstExpressionData::clone" << *this; return new KDbConstExpressionData(*this); } KDbField::Type KDbConstExpressionData::typeInternal(KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); switch (token.value()) { case SQL_NULL: return KDbField::Null; case INTEGER_CONST: //! @todo ok? //! @todo add sign info? if (value.type() == QVariant::Int || value.type() == QVariant::UInt) { qint64 v = value.toInt(); if (v <= 0xff && v > -0x80) return KDbField::Byte; if (v <= 0xffff && v > -0x8000) return KDbField::ShortInteger; return KDbField::Integer; } return KDbField::BigInteger; case CHARACTER_STRING_LITERAL: if (KDbField::defaultMaxLength() > 0 && value.toString().length() > KDbField::defaultMaxLength()) { return KDbField::LongText; } else { return KDbField::Text; } case SQL_TRUE: case SQL_FALSE: return KDbField::Boolean; case REAL_CONST: return KDbField::Double; case DATE_CONST: return KDbField::Date; case DATETIME_CONST: return KDbField::DateTime; case TIME_CONST: return KDbField::Time; } return KDbField::InvalidType; } void KDbConstExpressionData::debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); QString res = QLatin1String("ConstExp(") + token.name() - + QLatin1String(",") + toString(0).toString() + + QLatin1String(",") + toString(nullptr).toString() + QString::fromLatin1(",type=%1").arg(KDbDriver::defaultSQLTypeName(type())); if (value.type() == QVariant::Point && token.value() == REAL_CONST) { res += QLatin1String(",DECIMAL"); } res += QLatin1String(")"); dbg.nospace() << qPrintable(res); } KDbEscapedString KDbConstExpressionData::toStringInternal( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(driver); Q_UNUSED(params); Q_UNUSED(callStack); switch (token.value()) { case SQL_NULL: return KDb::valueToSQL(driver, KDbField::Null, QVariant()); case CHARACTER_STRING_LITERAL: //! @todo better escaping! return KDb::valueToSQL(driver, KDbField::Text, value); case SQL_TRUE: return KDb::valueToSQL(driver, KDbField::Boolean, 1); case SQL_FALSE: return KDb::valueToSQL(driver, KDbField::Boolean, 0); case REAL_CONST: return KDbEscapedString(value.toByteArray()); case DATE_CONST: return KDb::valueToSQL(driver, KDbField::Date, value); case DATETIME_CONST: return driver ? driver->valueToSQL(KDbField::DateTime, value) : KDbEscapedString('\'') + KDbEscapedString(value.toDateTime().date().toString(Qt::ISODate)) + ' ' + value.toDateTime().time().toString(Qt::ISODate) + '\''; case TIME_CONST: return KDb::valueToSQL(driver, KDbField::Time, value); case INTEGER_CONST: default: break; } return KDbEscapedString(value.toByteArray()); } void KDbConstExpressionData::getQueryParameters(QList* params) { Q_UNUSED(params); } bool KDbConstExpressionData::validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack) { Q_UNUSED(parseInfo); return typeInternal(callStack) != KDbField::InvalidType; } //========================================= KDbConstExpression::KDbConstExpression() : KDbExpression(new KDbConstExpressionData(QVariant())) { ExpressionDebug << "KDbConstExpression() ctor" << *this; } KDbConstExpression::KDbConstExpression(KDbToken token, const QVariant& value) : KDbExpression(new KDbConstExpressionData(value), KDb::ConstExpression, token) { } KDbConstExpression::KDbConstExpression(KDbExpressionData* data, KDb::ExpressionClass aClass, KDbToken token) : KDbExpression(data, aClass, token) { } KDbConstExpression::KDbConstExpression(KDbExpressionData* data) : KDbExpression(data) { } KDbConstExpression::KDbConstExpression(const ExplicitlySharedExpressionDataPointer &ptr) : KDbExpression(ptr) { } KDbConstExpression::KDbConstExpression(const KDbConstExpression& expr) : KDbExpression(expr) { } KDbConstExpression::~KDbConstExpression() { } QVariant KDbConstExpression::value() const { return d->convert()->value; } void KDbConstExpression::setValue(const QVariant& value) { d->convert()->value = value; } diff --git a/src/expression/KDbExpression.cpp b/src/expression/KDbExpression.cpp index f02ee671..c3b077eb 100644 --- a/src/expression/KDbExpression.cpp +++ b/src/expression/KDbExpression.cpp @@ -1,626 +1,626 @@ /* This file is part of the KDE project Copyright (C) 2003-2015 Jarosław Staniek Copyright (C) 2014 Radoslaw Wicik Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to * Boston, MA 02110-1301, USA. the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, */ #include "KDbExpression.h" #include "KDb.h" #include "KDbDriver.h" #include "KDbQuerySchema.h" #include "KDbParser_p.h" #include "kdb_debug.h" #include "generated/sqlparser.h" #include //! @internal A cache class KDbExpressionClassNames { public: KDbExpressionClassNames() : names({ QLatin1String("Unknown"), QLatin1String("Unary"), QLatin1String("Arithm"), QLatin1String("Logical"), QLatin1String("Relational"), QLatin1String("SpecialBinary"), QLatin1String("Const"), QLatin1String("Variable"), QLatin1String("Function"), QLatin1String("Aggregation"), QLatin1String("FieldList"), QLatin1String("TableList"), QLatin1String("ArgumentList"), QLatin1String("QueryParameter")}) { } const std::vector names; }; Q_GLOBAL_STATIC(KDbExpressionClassNames, KDb_expressionClassNames) KDB_EXPORT QString expressionClassName(KDb::ExpressionClass c) { Q_ASSERT(size_t(c) < KDb_expressionClassNames->names.size()); return KDb_expressionClassNames->names[c]; } KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbExpression& expr) { KDb::ExpressionCallStack callStack; return expr.debug(dbg.nospace(), &callStack); } //========================================= KDbExpressionData::KDbExpressionData() : expressionClass(KDb::UnknownExpression) { //ExpressionDebug << "KDbExpressionData" << ref; } /*Data(const Data& other) : QSharedData(other) , token(other.token) , expressionClass(other.expressionClass) , parent(other.parent) , children(other.children) { ExpressionDebug << "KDbExpressionData" << ref; }*/ KDbExpressionData::~KDbExpressionData() { //ExpressionDebug << "~KDbExpressionData" << ref; } KDbExpressionData* KDbExpressionData::clone() { ExpressionDebug << "KDbExpressionData::clone" << *this; return new KDbExpressionData(*this); } KDbField::Type KDbExpressionData::typeInternal(KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); return KDbField::InvalidType; } KDbField::Type KDbExpressionData::type(KDb::ExpressionCallStack* callStack) const { - if (!addToCallStack(0, callStack)) { + if (!addToCallStack(nullptr, callStack)) { return KDbField::InvalidType; } const KDbField::Type t = typeInternal(callStack); callStack->removeLast(); return t; } KDbField::Type KDbExpressionData::type() const { KDb::ExpressionCallStack callStack; return type(&callStack); } bool KDbExpressionData::isValid() const { return type() != KDbField::InvalidType; } bool KDbExpressionData::isTextType() const { return KDbField::isTextType(type()); } bool KDbExpressionData::isIntegerType() const { return KDbField::isIntegerType(type()); } bool KDbExpressionData::isNumericType() const { return KDbField::isNumericType(type()); } bool KDbExpressionData::isFPNumericType() const { return KDbField::isFPNumericType(type()); } bool KDbExpressionData::isDateTimeType() const { return KDbField::isDateTimeType(type()); } bool KDbExpressionData::validate(KDbParseInfo *parseInfo) { KDb::ExpressionCallStack callStack; return validate(parseInfo, &callStack); } bool KDbExpressionData::validate(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack) { - if (!addToCallStack(0, callStack)) { + if (!addToCallStack(nullptr, callStack)) { return false; } bool result = validateInternal(parseInfo, callStack); callStack->removeLast(); return result; } bool KDbExpressionData::validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack) { Q_UNUSED(parseInfo); Q_UNUSED(callStack); return true; } KDbEscapedString KDbExpressionData::toString( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { const bool owned = !callStack; if (owned) { callStack = new KDb::ExpressionCallStack(); } - if (!addToCallStack(0, callStack)) { + if (!addToCallStack(nullptr, callStack)) { if (owned) { delete callStack; } return KDbEscapedString(""); } KDbEscapedString s = toStringInternal(driver, params, callStack); callStack->removeLast(); if (owned) { delete callStack; } return s; } KDbEscapedString KDbExpressionData::toStringInternal( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(driver); Q_UNUSED(params); Q_UNUSED(callStack); return KDbEscapedString(""); } void KDbExpressionData::getQueryParameters(QList* params) { Q_UNUSED(params); } bool KDbExpressionData::addToCallStack(QDebug *dbg, QList* callStack) const { if (callStack->contains(this)) { if (dbg) dbg->nospace() << ""; kdbWarning() << "Cycle detected in" << expressionClassName(expressionClass) << token.value(); return false; } callStack->append(this); return true; } QDebug KDbExpressionData::debug(QDebug dbg, KDb::ExpressionCallStack* callStack) const { if (!addToCallStack(&dbg, callStack)) { return dbg.nospace(); } debugInternal(dbg, callStack); callStack->removeLast(); return dbg.space(); } QDebug operator<<(QDebug dbg, const KDbExpressionData& expr) { KDb::ExpressionCallStack callStack; return expr.debug(dbg.nospace(), &callStack); } void KDbExpressionData::debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); dbg.nospace() << QString::fromLatin1("Exp(%1,type=%2)") .arg(token.value()).arg(KDbDriver::defaultSQLTypeName(type())); } //========================================= KDbExpression::KDbExpression() : d(new KDbExpressionData) { ExpressionDebug << "KDbExpression ctor ()" << *this << d->ref; } KDbExpression::KDbExpression(KDbExpressionData* data, KDb::ExpressionClass aClass, KDbToken token) : d(data) { d->expressionClass = aClass; d->token = token; } KDbExpression::KDbExpression(KDbExpressionData* data) : d(data) { ExpressionDebug << "KDbExpression ctor (KDbExpressionData*)" << *this; } KDbExpression::KDbExpression(const ExplicitlySharedExpressionDataPointer &ptr) : d(ptr ? ptr : ExplicitlySharedExpressionDataPointer(new KDbExpressionData)) { } KDbExpression::~KDbExpression() { //kdbDebug() << *this << d->ref; if (d->parent && d->ref == 1) { d->parent->children.removeOne(d); } } bool KDbExpression::isNull() const { return d->expressionClass == KDb::UnknownExpression; } KDbExpression KDbExpression::clone() const { return KDbExpression(d->clone()); } KDbToken KDbExpression::token() const { return d->token; } void KDbExpression::setToken(KDbToken token) { d->token = token; } KDb::ExpressionClass KDbExpression::expressionClass() const { return d->expressionClass; } void KDbExpression::setExpressionClass(KDb::ExpressionClass aClass) { d->expressionClass = aClass; } bool KDbExpression::validate(KDbParseInfo *parseInfo) { return d->validate(parseInfo); } KDbField::Type KDbExpression::type() const { return d->type(); } bool KDbExpression::isValid() const { return d->isValid(); } bool KDbExpression::isTextType() const { return d->isTextType(); } bool KDbExpression::isIntegerType() const { return d->isIntegerType(); } bool KDbExpression::isNumericType() const { return d->isNumericType(); } bool KDbExpression::isFPNumericType() const { return d->isFPNumericType(); } bool KDbExpression::isDateTimeType() const { return d->isDateTimeType(); } KDbExpression KDbExpression::parent() const { return d->parent.data() ? KDbExpression(d->parent) : KDbExpression(); } QList KDbExpression::children() const { return d->children; } void KDbExpression::appendChild(const KDbExpression& child) { appendChild(child.d); } void KDbExpression::prependChild(const KDbExpression& child) { if (!checkBeforeInsert(child.d)) return; d->children.prepend(child.d); child.d->parent = d; } void KDbExpression::insertChild(int i, const KDbExpression& child) { if (!checkBeforeInsert(child.d)) return; if (i < 0 || i > d->children.count()) return; d->children.insert(i, child.d); child.d->parent = d; } void KDbExpression::insertEmptyChild(int i) { if (i < 0 || i > d->children.count()) return; KDbExpression child; d->children.insert(i, child.d); child.d->parent = d; } bool KDbExpression::removeChild(const KDbExpression& child) { if (isNull() || child.isNull()) return false; child.d->parent.reset(); // no longer parent return d->children.removeOne(child.d); } void KDbExpression::removeChild(int i) { if (isNull()) return; if (i < 0 || i >= d->children.count()) return; //kdbDebug() << d->children.count() << d->children.at(i); d->children.removeAt(i); } KDbExpression KDbExpression::takeChild(int i) { if (isNull()) return KDbExpression(); if (i < 0 || i >= d->children.count()) return KDbExpression(); ExplicitlySharedExpressionDataPointer child = d->children.takeAt(i); if (!child) return KDbExpression(); child->parent.reset(); return KDbExpression(child); } int KDbExpression::indexOfChild(const KDbExpression& child, int from) const { return d->children.indexOf(child.d, from); } int KDbExpression::lastIndexOfChild(const KDbExpression& child, int from) const { return d->children.lastIndexOf(child.d, from); } bool KDbExpression::checkBeforeInsert(const ExplicitlySharedExpressionDataPointer& child) { if (!child) return false; if (d == child) // expression cannot be own child return false; if (child->parent == d) // cannot insert child twice return false; if (child->parent) // remove from old parent child->parent->children.removeOne(child); return true; } void KDbExpression::appendChild(const ExplicitlySharedExpressionDataPointer& child) { if (!checkBeforeInsert(child)) return; d->children.append(child); child->parent = d; } KDbEscapedString KDbExpression::toString(const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { if (isNull()) return KDbEscapedString(""); return d->toString(driver, params, callStack); } void KDbExpression::getQueryParameters(QList* params) { Q_ASSERT(params); d->getQueryParameters(params); } QDebug KDbExpression::debug(QDebug dbg, KDb::ExpressionCallStack* callStack) const { if (d) d->debug(dbg, callStack); return dbg.space(); } bool KDbExpression::operator==(const KDbExpression& e) const { return d == e.d; } bool KDbExpression::operator!=(const KDbExpression& e) const { return !operator==(e); } bool KDbExpression::isNArg() const { return d->convertConst(); } bool KDbExpression::isUnary() const { return d->convertConst(); } bool KDbExpression::isBinary() const { return d->convertConst(); } bool KDbExpression::isConst() const { return d->convertConst(); } bool KDbExpression::isVariable() const { return d->convertConst(); } bool KDbExpression::isFunction() const { return d->convertConst(); } bool KDbExpression::isQueryParameter() const { return d->convertConst(); } #define CAST(T) \ d->convert() ? T(d) : T() KDbNArgExpression KDbExpression::toNArg() const { return CAST(KDbNArgExpression); } KDbUnaryExpression KDbExpression::toUnary() const { return CAST(KDbUnaryExpression); } KDbBinaryExpression KDbExpression::toBinary() const { return CAST(KDbBinaryExpression); } KDbConstExpression KDbExpression::toConst() const { return CAST(KDbConstExpression); } KDbQueryParameterExpression KDbExpression::toQueryParameter() const { return CAST(KDbQueryParameterExpression); } KDbVariableExpression KDbExpression::toVariable() const { return CAST(KDbVariableExpression); } KDbFunctionExpression KDbExpression::toFunction() const { return CAST(KDbFunctionExpression); } void KDbExpression::setLeftOrRight(const KDbExpression& e, int index) { if (this == &e) { kdbWarning() << "KDbExpression::setLeftOrRight(): Expression cannot be own child"; return; } if (d->children.indexOf(e.d) == index) { // cannot set twice return; } if (d->children[index == 0 ? 1 : 0] == e.d) { // this arg was at right, remove d->children[index] = e.d; d->children[index == 0 ? 1 : 0] = new KDbExpressionData; } else { if (e.d->parent) { // remove from old parent e.d->parent->children.removeOne(e.d); } d->children[index] = e.d; } } // static KDb::ExpressionClass KDbExpression::classForToken(KDbToken token) { switch (token.value()) { case '+': case '-': case '*': case '/': case '&': case '|': case '%': case BITWISE_SHIFT_RIGHT: case BITWISE_SHIFT_LEFT: case CONCATENATION: return KDb::ArithmeticExpression; case '=': case '<': case '>': case NOT_EQUAL: case NOT_EQUAL2: case LESS_OR_EQUAL: case GREATER_OR_EQUAL: case LIKE: case NOT_LIKE: case SQL_IN: case SIMILAR_TO: case NOT_SIMILAR_TO: return KDb::RelationalExpression; case OR: case AND: case XOR: return KDb::LogicalExpression; case AS: case AS_EMPTY: return KDb::SpecialBinaryExpression; default:; } return KDb::UnknownExpression; } #undef CAST diff --git a/src/expression/KDbExpression.h b/src/expression/KDbExpression.h index ec6e5765..7f8e519b 100644 --- a/src/expression/KDbExpression.h +++ b/src/expression/KDbExpression.h @@ -1,570 +1,570 @@ /* This file is part of the KDE project Copyright (C) 2003-2015 Jarosław Staniek Copyright (C) 2014 Radoslaw Wicik Design based on nexp.h : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_EXPRESSION_H #define KDB_EXPRESSION_H #include "KDbField.h" #include "KDbEscapedString.h" #include "KDbExpressionData.h" //! Maximum number of arguments in function //! Reasonable number, set after https://www.sqlite.org/limits.html#max_function_arg #define KDB_MAX_FUNCTION_ARGS 100 //! @return class name of class @a c KDB_EXPORT QString expressionClassName(KDb::ExpressionClass c); class KDbBinaryExpression; class KDbConstExpression; class KDbFunctionExpression; class KDbNArgExpression; class KDbParseInfo; class KDbQueryParameterExpression; class KDbQuerySchemaParameter; class KDbQuerySchemaParameterValueListIterator; class KDbToken; class KDbUnaryExpression; class KDbVariableExpression; //! The KDbExpression class represents a base class for all expressions. class KDB_EXPORT KDbExpression { public: /*! Constructs a null expression. @see KDbExpression::isNull() */ KDbExpression(); virtual ~KDbExpression(); //! @return true if this expression is null. //! Equivalent of expressionClass() == KDb::UnknownExpression. //! @note Returns false for expressions of type KDbField::Null (SQL's NULL). bool isNull() const; //! Creates a deep (not shallow) copy of the KDbExpression. KDbExpression clone() const; /*! @return the token for this expression. Tokens are characters (e.g. '+', '-') or identifiers (e.g. SQL_NULL) of elements used by the KDbSQL parser. By default token is 0. */ KDbToken token() const; /*! Sets token @a token for this expression. */ void setToken(KDbToken token); /*! @return class identifier of this expression. Default expressionClass is KDb::UnknownExpression. */ KDb::ExpressionClass expressionClass() const; /*! Sets expression class @a aClass for this expression. */ void setExpressionClass(KDb::ExpressionClass aClass); /*! @return type of this expression, based on effect of its evaluation. Default type is KDbField::InvalidType. @see isValid() */ KDbField::Type type() const; //! @return true if type of this object is not KDbField::InvalidType. /*! A covenience method. @see type() */ bool isValid() const; //! @return true if type of this object belong to a group of text types. /*! A covenience method. @see type() */ bool isTextType() const; //! \return true if type of this object belong to a group of integer types. /*! A covenience method. @see type() */ bool isIntegerType() const; //! @return true if type of this object belong to a group of numeric types. /*! A covenience method. @see type() */ bool isNumericType() const; //! @return true if type of this object belong to a group of floating-point numeric types. /*! A covenience method. @see type() */ bool isFPNumericType() const; //! @return true if type of this object belong to a group of time, date and date/time types. /*! A covenience method. @see type() */ bool isDateTimeType() const; /*! @return true if evaluation of this expression succeeded. */ bool validate(KDbParseInfo *parseInfo); /*! @return string as a representation of this expression element by running recursive calls. @a param, if not 0, points to a list item containing value of a query parameter (used in QueryParameterExpr). The result may depend on the optional @a driver parameter. If @a driver is 0, representation for portable KDbSQL dialect is returned. */ - KDbEscapedString toString(const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params = 0, - KDb::ExpressionCallStack* callStack = 0) const; + KDbEscapedString toString(const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params = nullptr, + KDb::ExpressionCallStack* callStack = nullptr) const; /*! Collects query parameters (messages and types) recursively and saves them to @a params. The leaf nodes are objects of QueryParameterExpr class. @note @a params must not be 0. */ void getQueryParameters(QList* params); //! @return expression class for token @a token. //! @todo support more tokens static KDb::ExpressionClass classForToken(KDbToken token); //! Convenience type casts. KDbNArgExpression toNArg() const; KDbUnaryExpression toUnary() const; KDbBinaryExpression toBinary() const; KDbConstExpression toConst() const; KDbVariableExpression toVariable() const; KDbFunctionExpression toFunction() const; KDbQueryParameterExpression toQueryParameter() const; bool isNArg() const; bool isUnary() const; bool isBinary() const; bool isConst() const; bool isVariable() const; bool isFunction() const; bool isQueryParameter() const; QDebug debug(QDebug dbg, KDb::ExpressionCallStack* callStack) const; bool operator==(const KDbExpression& e) const; bool operator!=(const KDbExpression& e) const; /*! @return the parent expression. */ KDbExpression parent() const; protected: /*! @return the list of children expressions. */ QList children() const; void appendChild(const KDbExpression& child); void prependChild(const KDbExpression& child); KDbExpression takeChild(int i); bool removeChild(const KDbExpression& child); void removeChild(int i); void insertChild(int i, const KDbExpression& child); //! Used for inserting placeholders, e.g. in KDbBinaryExpression::KDbBinaryExpression() void insertEmptyChild(int i); void appendChild(const ExplicitlySharedExpressionDataPointer& child); int indexOfChild(const KDbExpression& child, int from = 0) const; int lastIndexOfChild(const KDbExpression& child, int from = -1) const; bool checkBeforeInsert(const ExplicitlySharedExpressionDataPointer& child); //! Only for KDbBinaryExpression::setLeft() and KDbBinaryExpression::setRight() void setLeftOrRight(const KDbExpression& right, int index); explicit KDbExpression(KDbExpressionData* data); KDbExpression(KDbExpressionData* data, KDb::ExpressionClass aClass, KDbToken token); explicit KDbExpression(const ExplicitlySharedExpressionDataPointer &ptr); //! @internal ExplicitlySharedExpressionDataPointer d; friend class KDbNArgExpression; friend class KDbUnaryExpression; friend class KDbBinaryExpression; friend class KDbConstExpression; friend class KDbQueryParameterExpression; friend class KDbVariableExpression; friend class KDbFunctionExpression; }; //! The KDbNArgExpression class represents a base class N-argument expression. class KDB_EXPORT KDbNArgExpression : public KDbExpression { public: /*! Constructs a null N-argument expression. @see KDbExpression::isNull() */ KDbNArgExpression(); //! Constructs an N-argument expression of class @a aClass and token @a token. KDbNArgExpression(KDb::ExpressionClass aClass, KDbToken token); /*! Constructs a copy of other N-argument expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbNArgExpression(const KDbNArgExpression& expr); //! Destroys the expression. - virtual ~KDbNArgExpression(); + ~KDbNArgExpression() override; //! Inserts expression argument @a expr at the end of this expression. void append(const KDbExpression& expr); //! Inserts expression argument @a expr at the beginning of this expression. void prepend(const KDbExpression& expr); /*! Inserts expression argument @a expr at index position @a i in this expression. If @a i is 0, the expression is prepended to the list of arguments. If @a i is argCount(), the value is appended to the list of arguments. @a i must be a valid index position in the list (i.e., 0 <= i < argCount()). */ void insert(int i, const KDbExpression& expr); //! Replaces expression argument at index @a i with expression @a expr. //! @a i must be a valid index position in the list (i.e., 0 <= i < argCount()). */ void replace(int i, const KDbExpression& expr); /*! Removes the expression argument @a expr and returns true on success; otherwise returns false. */ bool remove(const KDbExpression& expr); /*! Removes the expression at index position @a i. @a i must be a valid index position in the list (i.e., 0 <= i < argCount()). */ void removeAt(int i); /*! Removes the expression at index position @a i and returns it. @a i must be a valid index position in the list (i.e., 0 <= i < argCount()). If you don't use the return value, removeAt() is more efficient. */ KDbExpression takeAt(int i); /*! @return the index position of the first occurrence of expression argument @a expr in this expression, searching forward from index position @a from. @return -1 if no argument matched. @see lastIndexOf() */ int indexOf(const KDbExpression& expr, int from = 0) const; /*! @return the index position of the last occurrence of expression argument @a expr in this expression, searching backward from index position @a from. If from is -1 (the default), the search starts at the last item. Returns -1 if no argument matched. @see indexOf() */ int lastIndexOf(const KDbExpression& expr, int from = -1) const; //! @return expression index @a i in the list of arguments. //! If the index @a i is out of bounds, the function returns null expression. KDbExpression arg(int i) const; //! @return the number of expression arguments in this expression. int argCount() const; //! @return true if the expression contains no arguments; otherwise returns false. bool isEmpty() const; //! @return true if any argument is invalid (!KDbExpression::isValid()). bool containsInvalidArgument() const; //! @return true if any argument is NULL (type KDbField::Null). bool containsNullArgument() const; protected: explicit KDbNArgExpression(KDbExpressionData* data); explicit KDbNArgExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; friend class KDbFunctionExpression; friend class KDbFunctionExpressionData; }; //! The KDbUnaryExpression class represents unary expression (with a single argument). /*! operation: + - NOT (or !) ~ "IS NULL" "IS NOT NULL" */ class KDB_EXPORT KDbUnaryExpression : public KDbExpression { public: /*! Constructs a null unary expression. @see KDbExpression::isNull() */ KDbUnaryExpression(); //! Constructs unary expression with token @a token and argument @a arg. KDbUnaryExpression(KDbToken token, const KDbExpression& arg); /*! Constructs a copy of other unary expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbUnaryExpression(const KDbUnaryExpression& expr); - virtual ~KDbUnaryExpression(); + ~KDbUnaryExpression() override; //! @return expression that is argument for this unary expression KDbExpression arg() const; //! Sets expression argument @a expr for this unary expression. void setArg(const KDbExpression &arg); protected: explicit KDbUnaryExpression(KDbExpressionData* data); explicit KDbUnaryExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; }; //! The KDbBinaryExpression class represents binary operation. /* - arithmetic operations: + - / * % << >> & | || - relational operations: = (or ==) < > <= >= <> (or !=) LIKE 'NOT LIKE' IN 'SIMILAR TO' 'NOT SIMILAR TO' - logical operations: OR (or ||) AND (or &&) XOR - SpecialBinary "pseudo operators": * e.g. "f1 f2" : token == 0 * e.g. "f1 AS f2" : token == AS */ class KDB_EXPORT KDbBinaryExpression : public KDbExpression { public: /*! Constructs a null binary expression. @see KDbExpression::isNull() */ KDbBinaryExpression(); /*! Constructs binary expression with left expression @a leftExpr, token @a token, and right expression @a rightExpr. */ KDbBinaryExpression(const KDbExpression& leftExpr, KDbToken token, const KDbExpression& rightExpr); /*! Constructs a copy of other unary expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbBinaryExpression(const KDbBinaryExpression& expr); - virtual ~KDbBinaryExpression(); + ~KDbBinaryExpression() override; KDbExpression left() const; void setLeft(const KDbExpression& leftExpr); KDbExpression right() const; void setRight(const KDbExpression& rightExpr); protected: explicit KDbBinaryExpression(KDbExpressionData* data); explicit KDbBinaryExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; friend class KDbBinaryExpressionData; }; //! The KDbConstExpression class represents const expression. /*! Types are string, integer, float constants. Also includes NULL value. Token can be: IDENTIFIER, SQL_NULL, SQL_TRUE, SQL_FALSE, CHARACTER_STRING_LITERAL, INTEGER_CONST, REAL_CONST, DATE_CONST, DATETIME_CONST, TIME_CONST. @note For REAL_CONST accepted values can be of type qreal, double and QPoint. In the case of QPoint, integer value (with a sign) is stored in QPoint::x and the fraction part (that should be always positive) is stored in QPoint::y. This gives 31 bits for the integer part (10 decimal digits) and 31 bits for the part (10 decimal digits). */ class KDB_EXPORT KDbConstExpression : public KDbExpression { public: /*! Constructs a null const expression. @see KDbExpression::isNull() */ KDbConstExpression(); /*! Constructs const expression token @a token and value @a value. */ KDbConstExpression(KDbToken token, const QVariant& value); /*! Constructs a copy of other const expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbConstExpression(const KDbConstExpression& expr); - virtual ~KDbConstExpression(); + ~KDbConstExpression() override; QVariant value() const; void setValue(const QVariant& value); protected: //! Internal, used by KDbQueryParameterExpression(const QString& message). KDbConstExpression(KDbExpressionData* data, KDb::ExpressionClass aClass, KDbToken token); explicit KDbConstExpression(KDbExpressionData* data); explicit KDbConstExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; }; //! The KDbQueryParameterExpression class represents query parameter expression. /*! Query parameter is used to getting user input of constant values. It contains a message that is displayed to the user. */ class KDB_EXPORT KDbQueryParameterExpression : public KDbConstExpression { public: /*! Constructs a null query parameter expression. @see KDbExpression::isNull() */ KDbQueryParameterExpression(); /*! Constructs query parameter expression with message @a message. */ explicit KDbQueryParameterExpression(const QString& message); /*! Constructs a copy of other query parameter expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbQueryParameterExpression(const KDbQueryParameterExpression& expr); - virtual ~KDbQueryParameterExpression(); + ~KDbQueryParameterExpression() override; /*! Sets expected type of the parameter. The default is String. This method is called from parent's expression validate(). This depends on the type of the related expression. For instance: query "SELECT * FROM cars WHERE name=[enter name]", "[enter name]" has parameter of the same type as "name" field. "=" binary expression's validate() will be called for the left side of the expression and then the right side will have type set to String. */ void setType(KDbField::Type type); protected: explicit KDbQueryParameterExpression(KDbExpressionData* data); explicit KDbQueryParameterExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; }; //! The KDbVariableExpression class represents variables such as fieldname or tablename.fieldname class KDB_EXPORT KDbVariableExpression : public KDbExpression { public: /*! Constructs a null variable expression. @see KDbExpression::isNull() */ KDbVariableExpression(); /*! Constructs variable expression with name @a name. */ explicit KDbVariableExpression(const QString& name); /*! Constructs a copy of other variable expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbVariableExpression(const KDbVariableExpression& expr); - virtual ~KDbVariableExpression(); + ~KDbVariableExpression() override; /*! Verbatim name as returned by scanner. */ QString name() const; /*! 0 by default. After successful validate() it returns a field, if the variable is of a form "tablename.fieldname" or "fieldname", otherwise (eg. for asterisks) still 0. Only meaningful for column expressions within a query. */ KDbField *field() const; /*! -1 by default. After successful validate() it returns a position of a table within query that needs to be bound to the field. This value can be either be -1 if no binding is needed. This value is used in the Parser to call KDbQuerySchema::addField(KDbField* field, int bindToTable); Only meaningful for column expressions within a query. */ int tablePositionForField() const; - /*! 0 by default. After successful validate() it returns table that + /*! @c nullptr by default. After successful validate() it returns table that is referenced by asterisk, i.e. "*.tablename". - It is 0 if this variable is not an asterisk of that form. */ + It is @c nullptr if this variable is not an asterisk of that form. */ KDbTableSchema *tableForQueryAsterisk() const; protected: explicit KDbVariableExpression(KDbExpressionData* data); explicit KDbVariableExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; }; //! The KDbFunctionExpression class represents expression that use functional notation F(x, ...) /*! The functions list include: - aggregation functions like SUM, COUNT, MAX, ... - builtin functions like CURRENT_TIME() - user defined functions */ class KDB_EXPORT KDbFunctionExpression : public KDbExpression { public: /*! Constructs a null function expression. @see KDbExpression::isNull() */ KDbFunctionExpression(); /*! Constructs function expression with name @a name, without arguments. */ explicit KDbFunctionExpression(const QString& name); /*! Constructs function expression with name @a name and arguments @a arguments. */ KDbFunctionExpression(const QString& name, const KDbNArgExpression &arguments); /*! Constructs a copy of other function expression @a expr. Resulting object is not a deep copy but rather a reference to the object @a expr. */ KDbFunctionExpression(const KDbFunctionExpression& expr); - virtual ~KDbFunctionExpression(); + ~KDbFunctionExpression() override; //! @return name of the function. QString name() const; //! Sets name of the function to @a name. void setName(const QString &name); //! @return list of arguments of the function. KDbNArgExpression arguments(); //! Sets the list of arguments to @a arguments. void setArguments(const KDbNArgExpression &arguments); static QStringList builtInAggregates(); static bool isBuiltInAggregate(const QString& function); using KDbExpression::toString; /*! Constructs function expression with name @a name and args @a args. */ static KDbEscapedString toString(const QString &name, const KDbDriver *driver, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack); //! @return a native (driver-specific) GREATEST() and LEAST() function calls generated //! to string using CASE WHEN... keywords. //! This is a workaround for cases when LEAST()/GREATEST() function ignores //! NULL values and only returns NULL if all the expressions evaluate to NULL. //! Instead of using F(v0,..,vN), this is used: //! (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE F(v0,..,vN) END) //! where F == GREATEST or LEAST. //! Actually it is needed by MySQL < 5.0.13 and PostgreSQL. static KDbEscapedString greatestOrLeastFunctionUsingCaseToString( const QString &name, const KDbDriver *driver, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack); protected: explicit KDbFunctionExpression(KDbExpressionData* data); explicit KDbFunctionExpression(const ExplicitlySharedExpressionDataPointer &ptr); friend class KDbExpression; friend class KDbFunctionExpressionData; }; //! Sends information about expression @a expr to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbExpression& expr); #endif diff --git a/src/expression/KDbExpressionData.h b/src/expression/KDbExpressionData.h index 0f9e1936..ab58e612 100644 --- a/src/expression/KDbExpressionData.h +++ b/src/expression/KDbExpressionData.h @@ -1,360 +1,361 @@ /* This file is part of the KDE project Copyright (C) 2003-2015 Jarosław Staniek Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_EXPRESSION_P_H #define KDB_EXPRESSION_P_H #include "config-kdb.h" #include "KDbToken.h" #include "KDbField.h" class KDbConstExpressionData; class KDbEscapedString; class KDbExpressionData; class KDbParseInfo; class KDbQueryParameterExpressionData; class KDbQuerySchemaParameter; class KDbQuerySchemaParameterValueListIterator; class KDbUnaryExpressionData; namespace KDb { typedef QList ExpressionCallStack; //! Classes of expressions enum ExpressionClass { UnknownExpression, UnaryExpression, ArithmeticExpression, LogicalExpression, RelationalExpression, SpecialBinaryExpression, ConstExpression, VariableExpression, FunctionExpression, AggregationExpression, FieldListExpression, TableListExpression, ArgumentListExpression, QueryParameterExpression, LastExpressionClass = QueryParameterExpression //!< This line should be at the end of the list }; } typedef QExplicitlySharedDataPointer ExplicitlySharedExpressionDataPointer; //! Internal data class used to implement implicitly shared class KDbExpression. //! Provides thread-safe reference counting. class KDB_TESTING_EXPORT KDbExpressionData : public QSharedData { public: KDbExpressionData(); /*Data(const Data& other) : QSharedData(other) , token(other.token) , expressionClass(other.expressionClass) , parent(other.parent) , children(other.children) { kdbDebug() << "KDbExpressionData" << ref; }*/ virtual ~KDbExpressionData(); //! @see KDbExpression::token() KDbToken token; //! @see KDbExpression::expressionClass() KDb::ExpressionClass expressionClass; ExplicitlySharedExpressionDataPointer parent; QList children; KDbField::Type type() const; //!< @return type of this expression; bool isValid() const; bool isTextType() const; bool isIntegerType() const; bool isNumericType() const; bool isFPNumericType() const; bool isDateTimeType() const; KDbEscapedString toString(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params = 0, - KDb::ExpressionCallStack* callStack = 0) const; - virtual void getQueryParameters(QList* params); + KDbQuerySchemaParameterValueListIterator *params = nullptr, + KDb::ExpressionCallStack *callStack = nullptr) const; + virtual void getQueryParameters(QList *params); bool validate(KDbParseInfo *parseInfo); virtual KDbExpressionData* clone(); template const T* convertConst() const { return dynamic_cast(this); } template T* convert() { return dynamic_cast(this); } //! Sends information about this expression to debug output @a dbg. - QDebug debug(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + QDebug debug(QDebug dbg, KDb::ExpressionCallStack *callStack) const; - KDbField::Type type(KDb::ExpressionCallStack* callStack) const; + KDbField::Type type(KDb::ExpressionCallStack *callStack) const; - bool validate(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validate(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack); protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + virtual KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const; virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack); - bool addToCallStack(QDebug *dbg, QList* callStack) const; + bool addToCallStack(QDebug *dbg, QList *callStack) const; }; //! Internal data class used to implement implicitly shared class KDbNArgExpression. //! Provides thread-safe reference counting. class KDbNArgExpressionData : public KDbExpressionData { Q_DECLARE_TR_FUNCTIONS(KDbNArgExpressionData) public: KDbNArgExpressionData(); - virtual ~KDbNArgExpressionData(); + ~KDbNArgExpressionData() override; - virtual void getQueryParameters(QList* params); - virtual KDbNArgExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbNArgExpressionData* clone() override; bool containsInvalidArgument() const; bool containsNullArgument() const; protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbUnaryExpression. //! Provides thread-safe reference counting. class KDbUnaryExpressionData : public KDbExpressionData { public: KDbUnaryExpressionData(); - virtual ~KDbUnaryExpressionData(); + ~KDbUnaryExpressionData() override; - virtual void getQueryParameters(QList* params); - virtual KDbUnaryExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbUnaryExpressionData* clone() override; inline ExplicitlySharedExpressionDataPointer arg() const { return children.isEmpty() ? ExplicitlySharedExpressionDataPointer() : children.first(); } protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbBinaryExpression. //! Provides thread-safe reference counting. class KDbBinaryExpressionData : public KDbExpressionData { Q_DECLARE_TR_FUNCTIONS(KDbBinaryExpressionData) public: KDbBinaryExpressionData(); - virtual ~KDbBinaryExpressionData(); + ~KDbBinaryExpressionData() override; - virtual void getQueryParameters(QList* params); - virtual KDbBinaryExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbBinaryExpressionData* clone() override; ExplicitlySharedExpressionDataPointer left() const; ExplicitlySharedExpressionDataPointer right() const; protected: void setLeft(const KDbExpressionData& left); //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbConstExpression. //! Provides thread-safe reference counting. class KDbConstExpressionData : public KDbExpressionData { public: explicit KDbConstExpressionData(const QVariant& aValue = QVariant()); - virtual ~KDbConstExpressionData(); + ~KDbConstExpressionData() override; QVariant value; - virtual void getQueryParameters(QList* params); - virtual KDbConstExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbConstExpressionData* clone() override; protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbQueryParameterExpression. //! Provides thread-safe reference counting. class KDbQueryParameterExpressionData : public KDbConstExpressionData { public: KDbQueryParameterExpressionData(); KDbQueryParameterExpressionData(KDbField::Type type, const QVariant& value); - virtual ~KDbQueryParameterExpressionData(); + ~KDbQueryParameterExpressionData() override; KDbField::Type m_type; - virtual void getQueryParameters(QList* params); - virtual KDbQueryParameterExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbQueryParameterExpressionData* clone() override; protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbVariableExpression. //! Provides thread-safe reference counting. class KDbVariableExpressionData : public KDbExpressionData { Q_DECLARE_TR_FUNCTIONS(KDbVariableExpressionData) public: KDbVariableExpressionData(); explicit KDbVariableExpressionData(const QString& aName); - virtual ~KDbVariableExpressionData(); + ~KDbVariableExpressionData() override; /*! Verbatim name as returned by scanner. */ QString name; /*! 0 by default. After successful validate() it will point to a field, if the variable is of a form "tablename.fieldname" or "fieldname", otherwise (eg. for asterisks) still 0. Only meaningful for column expressions within a query. */ KDbField *field; /*! -1 by default. After successful validate() it will contain a position of a table within query that needs to be bound to the field. This value can be either be -1 if no binding is needed. This value is used in the Parser to call KDbQuerySchema::addField(KDbField* field, int bindToTable); Only meaningful for column expressions within a query. */ int tablePositionForField; /*! 0 by default. After successful validate() it will point to a table that is referenced by asterisk, i.e. "*.tablename". - This is set to NULL if this variable is not an asterisk of that form. */ + This is set to @c nullptr if this variable is not an asterisk of that form. */ KDbTableSchema *tableForQueryAsterisk; - virtual void getQueryParameters(QList* params); - virtual KDbVariableExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbVariableExpressionData* clone() override; protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; /*! Validation. Sets field, tablePositionForField and tableForQueryAsterisk members. See addColumn() in parse.y to see how it's used on column adding. */ - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; //! Internal data class used to implement implicitly shared class KDbFunctionExpression. //! Provides thread-safe reference counting. class KDbFunctionExpressionData : public KDbExpressionData { Q_DECLARE_TR_FUNCTIONS(KDbFunctionExpressionData) public: KDbFunctionExpressionData(); - explicit KDbFunctionExpressionData(const QString& aName, - ExplicitlySharedExpressionDataPointer arguments = ExplicitlySharedExpressionDataPointer()); - virtual ~KDbFunctionExpressionData(); + explicit KDbFunctionExpressionData(const QString &aName, + ExplicitlySharedExpressionDataPointer arguments + = ExplicitlySharedExpressionDataPointer()); + ~KDbFunctionExpressionData() override; QString name; ExplicitlySharedExpressionDataPointer args; - virtual void getQueryParameters(QList* params); - virtual KDbFunctionExpressionData* clone(); + void getQueryParameters(QList *params) override; + KDbFunctionExpressionData* clone() override; void setArguments(ExplicitlySharedExpressionDataPointer arguments); static KDbEscapedString toString(const QString &name, const KDbDriver *driver, const KDbNArgExpressionData *args, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack); + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack); protected: //! Sends information about this expression to debug output @a dbg (internal). - virtual void debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const; + void debugInternal(QDebug dbg, KDb::ExpressionCallStack *callStack) const override; - virtual KDbEscapedString toStringInternal(const KDbDriver *driver, - KDbQuerySchemaParameterValueListIterator* params, - KDb::ExpressionCallStack* callStack) const; + KDbEscapedString toStringInternal(const KDbDriver *driver, + KDbQuerySchemaParameterValueListIterator *params, + KDb::ExpressionCallStack *callStack) const override; - virtual KDbField::Type typeInternal(KDb::ExpressionCallStack* callStack) const; + KDbField::Type typeInternal(KDb::ExpressionCallStack *callStack) const override; /*! Validation. Sets field, tablePositionForField and tableForQueryAsterisk members. See addColumn() in parse.y to see how it's used on column adding. */ - virtual bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack); + bool validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack *callStack) override; }; QDebug operator<<(QDebug dbg, const KDbExpressionData& expr); #endif diff --git a/src/expression/KDbFunctionExpression.cpp b/src/expression/KDbFunctionExpression.cpp index d74f75a8..eaad4c9c 100644 --- a/src/expression/KDbFunctionExpression.cpp +++ b/src/expression/KDbFunctionExpression.cpp @@ -1,1407 +1,1412 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbExpression.h" #include "KDb.h" #include "KDbQuerySchema.h" #include "KDbDriver.h" #include "KDbParser.h" #include "KDbParser_p.h" #include "kdb_debug.h" #include #include #include // Enable to add SQLite-specific functions //#define KDB_ENABLE_SQLITE_SPECIFIC_FUNCTIONS //! A set of names of aggregation SQL functions class BuiltInAggregates : public QSet { public: BuiltInAggregates() { insert(QLatin1String("SUM")); insert(QLatin1String("MIN")); insert(QLatin1String("MAX")); insert(QLatin1String("AVG")); insert(QLatin1String("COUNT")); insert(QLatin1String("STD")); insert(QLatin1String("STDDEV")); insert(QLatin1String("VARIANCE")); } }; Q_GLOBAL_STATIC(BuiltInAggregates, _builtInAggregates) //! Type of a single function argument, used with KDbField::Type values. //! Used to indicate that multiple types are allowed. enum BuiltInFunctionArgumentType { AnyText = KDbField::LastType + 1, AnyInt, AnyFloat, AnyNumber, Any }; //! @return any concrete type matching rule @a argType static KDbField::Type anyMatchingType(int argType) { if (argType == AnyText || argType == Any) { return KDbField::Text; } else if (argType == AnyInt || argType == AnyNumber) { return KDbField::Integer; } else if (argType == AnyFloat) { return KDbField::Double; } return KDbField::InvalidType; } //! Declaration of a single built-in function. It can offer multiple signatures. class BuiltInFunctionDeclaration { public: inline BuiltInFunctionDeclaration() : defaultReturnType(KDbField::InvalidType), copyReturnTypeFromArg(-1) { } virtual ~BuiltInFunctionDeclaration() {} virtual KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const { Q_UNUSED(parseInfo); const KDbNArgExpressionData *argsData = f->args.constData()->convertConst(); if (argsData->containsNullArgument()) { return KDbField::Null; } if (copyReturnTypeFromArg >= 0 && copyReturnTypeFromArg < argsData->children.count()) { - KDbQueryParameterExpressionData *queryParameterExpressionData = argsData->children.at(copyReturnTypeFromArg)->convert(); + KDbQueryParameterExpressionData *queryParameterExpressionData + = argsData->children.at(copyReturnTypeFromArg) + ->convert(); if (queryParameterExpressionData) { // Set query parameter type (if there are any) to deduced result type //! @todo Most likely but can be also other type for (size_t i = 0; i < signatures.size(); ++i) { int** signature = signatures[i]; const KDbField::Type t = anyMatchingType(signature[copyReturnTypeFromArg][0]); if (t != KDbField::InvalidType) { queryParameterExpressionData->m_type = t; return t; } } } return argsData->children.at(copyReturnTypeFromArg)->type(); } return defaultReturnType; } std::vector signatures; protected: KDbField::Type defaultReturnType; int copyReturnTypeFromArg; friend class BuiltInFunctions; private: Q_DISABLE_COPY(BuiltInFunctionDeclaration) }; //! Declaration of a single built-in function COALESCE() and similar ones. class CoalesceFunctionDeclaration : public BuiltInFunctionDeclaration { public: CoalesceFunctionDeclaration() {} - virtual KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const { + KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const override { Q_UNUSED(parseInfo); // Find type //! @todo Most likely but can be also other type KDbField::Type t = KDbField::Integer; const KDbNArgExpressionData *argsData = f->args.constData()->convertConst(); foreach(const ExplicitlySharedExpressionDataPointer &expr, argsData->children) { KDbQueryParameterExpressionData *queryParameterExpressionData = expr->convert(); const KDbField::Type currentType = expr->type(); if (!queryParameterExpressionData && currentType != KDbField::Null) { t = currentType; break; } } foreach(const ExplicitlySharedExpressionDataPointer &expr, argsData->children) { KDbQueryParameterExpressionData *queryParameterExpressionData = expr->convert(); if (queryParameterExpressionData) { // Set query parameter type (if there are any) to deduced result type queryParameterExpressionData->m_type = t; } } return t; } private: Q_DISABLE_COPY(CoalesceFunctionDeclaration) }; //! Declaration of a single built-in function MIN(), MAX() and similar ones. //! Its return type is: //! - NULL if any argument is NULL //! - valid type if types of all arguments are compatible (e.g. text, numeric, date...) //! - InvalidType if types of any two are incompatible class MinMaxFunctionDeclaration : public BuiltInFunctionDeclaration { Q_DECLARE_TR_FUNCTIONS(MinMaxFunctionDeclaration) public: MinMaxFunctionDeclaration() {} - virtual KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const { + KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const override { const KDbNArgExpressionData *argsData = f->args.constData()->convertConst(); if (argsData->children.isEmpty()) { return KDbField::Null; } const KDbField::Type type0 = argsData->children.at(0)->type(); // cache: evaluating type of expressions can be expensive if (nullOrInvalid(type0)) { return type0; } KDbField::TypeGroup prevTg = KDbField::typeGroup(type0); // use typegroup for simplicity bool prevTgIsAny = argsData->children.at(0)->convertConst(); for(int i = 1; i < argsData->children.count(); ++i) { const ExplicitlySharedExpressionDataPointer expr = argsData->children.at(i); const KDbField::Type t = expr->type(); if (nullOrInvalid(t)) { return t; } const KDbField::TypeGroup tg = KDbField::typeGroup(t); const bool tgIsAny = argsData->children.at(i)->convertConst(); if (prevTgIsAny) { if (!tgIsAny) { // no longer "Any" (query parameter) prevTgIsAny = false; prevTg = tg; } continue; } else if (tgIsAny) { continue; // use previously found concrete type } if ((prevTg == KDbField::IntegerGroup || prevTg == KDbField::FloatGroup) && (tg == KDbField::IntegerGroup || tg == KDbField::FloatGroup)) { if (prevTg == KDbField::IntegerGroup && tg == KDbField::FloatGroup) { prevTg = KDbField::FloatGroup; // int -> float } continue; } if (prevTg == tg) { continue; } if (parseInfo) { parseInfo->setErrorMessage( tr("Incompatible types in %1() function").arg(f->name)); parseInfo->setErrorDescription( tr("Argument #%1 of type \"%2\" in function %3() is not " "compatible with previous arguments of type \"%4\".") .arg(i+1) .arg(KDbField::typeName(simpleTypeForGroup(tg)), f->name, KDbField::typeName(simpleTypeForGroup(prevTg)))); } return KDbField::InvalidType; } if (prevTgIsAny) { //! @todo Most likely Integer but can be also Float/Double/Text/Date... return KDbField::Integer; } const KDbField::Type resultType = safeTypeForGroup(prevTg); // Set query parameter types (if there are any) to deduced result type for(ExplicitlySharedExpressionDataPointer expr : argsData->children) { KDbQueryParameterExpressionData *queryParameterExpressionData = expr->convert(); if (queryParameterExpressionData) { queryParameterExpressionData->m_type = resultType; } } return resultType; } private: static bool nullOrInvalid(KDbField::Type type) { return type == KDbField::Null || type == KDbField::InvalidType; } //! @return safe default type for type group @a tg (too big sizes better than too small) static KDbField::Type safeTypeForGroup(KDbField::TypeGroup tg) { switch (tg) { case KDbField::TextGroup: return KDbField::LongText; case KDbField::IntegerGroup: return KDbField::BigInteger; case KDbField::FloatGroup: return KDbField::Double; case KDbField::BooleanGroup: return KDbField::Boolean; case KDbField::DateTimeGroup: return KDbField::DateTime; case KDbField::BLOBGroup: return KDbField::BLOB; default: break; } return KDbField::InvalidType; } //! @return resonable default type for type group @a tg (used for displaying in error message) static KDbField::Type simpleTypeForGroup(KDbField::TypeGroup tg) { switch (tg) { case KDbField::TextGroup: return KDbField::Text; case KDbField::IntegerGroup: return KDbField::Integer; case KDbField::FloatGroup: return KDbField::Double; case KDbField::BooleanGroup: return KDbField::Boolean; case KDbField::DateTimeGroup: return KDbField::DateTime; case KDbField::BLOBGroup: return KDbField::BLOB; default: break; } return KDbField::InvalidType; } Q_DISABLE_COPY(MinMaxFunctionDeclaration) }; //! Declaration of a single built-in function RANDOM() and RANDOM(X,Y). //! Its return type is: //! - Double when number of arguments is zero //! - integer if there are two integer arguments (see KDb::maximumForIntegerFieldTypes()) //! - InvalidType for other number of arguments class RandomFunctionDeclaration : public BuiltInFunctionDeclaration { Q_DECLARE_TR_FUNCTIONS(RandomFunctionDeclaration) public: RandomFunctionDeclaration() {} - virtual KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const { + KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const override { Q_UNUSED(parseInfo); const KDbNArgExpressionData *argsData = f->args.constData()->convertConst(); if (argsData->children.isEmpty()) { return KDbField::Double; } if (argsData->children.count() == 2) { const KDbConstExpressionData *const0 = argsData->children.at(0)->convertConst(); const KDbConstExpressionData *const1 = argsData->children.at(1)->convertConst(); if (const0 && const1) { bool ok0; const qlonglong val0 = const0->value.toLongLong(&ok0); bool ok1; const qlonglong val1 = const1->value.toLongLong(&ok1); if (ok0 && ok1) { if (val0 >= val1) { if (parseInfo) { parseInfo->setErrorMessage( tr("Invalid arguments of %1() function").arg(f->name)); parseInfo->setErrorDescription( tr("Value of the first argument should be less than " "value of the second argument.")); } return KDbField::InvalidType; } } } KDbField::Type t0; KDbField::Type t1; // deduce query parameter types - KDbQueryParameterExpressionData *queryParameterExpressionData0 = argsData->children.at(0)->convert(); - KDbQueryParameterExpressionData *queryParameterExpressionData1 = argsData->children.at(1)->convert(); + KDbQueryParameterExpressionData *queryParameterExpressionData0 + = argsData->children.at(0)->convert(); + KDbQueryParameterExpressionData *queryParameterExpressionData1 + = argsData->children.at(1)->convert(); if (queryParameterExpressionData0 && queryParameterExpressionData1) { queryParameterExpressionData0->m_type = KDbField::Integer; queryParameterExpressionData1->m_type = KDbField::Integer; t0 = KDbField::Integer; t1 = KDbField::Integer; } else if (queryParameterExpressionData0 && !queryParameterExpressionData1) { queryParameterExpressionData0->m_type = KDbField::Integer; t0 = queryParameterExpressionData0->m_type; t1 = argsData->children.at(1)->type(); } else if (!queryParameterExpressionData0 && queryParameterExpressionData1) { queryParameterExpressionData1->m_type = KDbField::Integer; t0 = argsData->children.at(0)->type(); t1 = queryParameterExpressionData1->m_type; } else { t0 = argsData->children.at(0)->type(); t1 = argsData->children.at(1)->type(); } return KDb::maximumForIntegerFieldTypes(t0, t1); } return KDbField::InvalidType; } private: Q_DISABLE_COPY(RandomFunctionDeclaration) }; //! Declaration of a single built-in function CEILING(X) and FLOOR(X). //! Its return type is: //! - integer if there are two integer arguments (see KDb::maximumForIntegerFieldTypes()) //! - InvalidType for other number of arguments class CeilingFloorFunctionDeclaration : public BuiltInFunctionDeclaration { public: CeilingFloorFunctionDeclaration() {} - virtual KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const { + KDbField::Type returnType(const KDbFunctionExpressionData* f, KDbParseInfo* parseInfo) const override { Q_UNUSED(parseInfo); const KDbNArgExpressionData *argsData = f->args.constData()->convertConst(); if (argsData->children.count() == 1) { - KDbQueryParameterExpressionData *queryParameterExpressionData = argsData->children.at(0)->convert(); + KDbQueryParameterExpressionData *queryParameterExpressionData + = argsData->children.at(0)->convert(); if (queryParameterExpressionData) { // Set query parameter type (if there are any) to deduced result type //! @todo Most likely but can be also other type queryParameterExpressionData->m_type = KDbField::Double; return KDbField::BigInteger; } const KDbField::Type type = argsData->children.at(0)->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isFPNumericType(type)) { return KDbField::BigInteger; } switch (type) { case KDbField::Byte: return KDbField::ShortInteger; case KDbField::ShortInteger: return KDbField::Integer; case KDbField::Integer: return KDbField::BigInteger; case KDbField::Null: return KDbField::Null; case KDbField::InvalidType: return KDbField::InvalidType; default:; } } return KDbField::InvalidType; } private: Q_DISABLE_COPY(CeilingFloorFunctionDeclaration) }; //! A map of built-in SQL functions //! See https://community.kde.org/Kexi/Plugins/Queries/SQL_Functions for the status. class BuiltInFunctions : public QHash { public: BuiltInFunctions(); ~BuiltInFunctions() { qDeleteAll(*this); } //! @return function declaration's structure for name @a name //! If @a name is alias of the function, e.g. "MIN" for "LEAST", the original //! function's declaration is returned. BuiltInFunctionDeclaration* value(const QString &name) const; //! @return a list of function aliases. QStringList aliases() const; static int multipleArgs[]; private: QHash m_aliases; Q_DISABLE_COPY(BuiltInFunctions) }; int BuiltInFunctions::multipleArgs[] = { 0 }; BuiltInFunctions::BuiltInFunctions() : QHash() { BuiltInFunctionDeclaration *decl; #define _TYPES(name, ...) static int name[] = { __VA_ARGS__, KDbField::InvalidType } _TYPES(argAnyTextOrNull, AnyText, KDbField::Null); _TYPES(argAnyIntOrNull, AnyInt, KDbField::Null); _TYPES(argAnyNumberOrNull, AnyNumber, KDbField::Null); _TYPES(argAnyFloatOrNull, AnyFloat, KDbField::Null); Q_UNUSED(argAnyFloatOrNull); _TYPES(argAnyOrNull, Any, KDbField::Null); _TYPES(argBLOBOrNull, KDbField::BLOB, KDbField::Null); Q_UNUSED(argBLOBOrNull); _TYPES(argAnyTextBLOBOrNull, AnyText, KDbField::BLOB, KDbField::Null); #undef _TYPES //! Adds a signature named @a name with specified arguments to declaration decl #define _SIG(name, ...) \ static int* name[] = { __VA_ARGS__, 0 }; \ decl->signatures.push_back(name) //! Adds a signature with no arguments to declaration decl #define _SIG0 \ decl->signatures.push_back(sig0) - static int* sig0[] = { 0 }; + static int* sig0[] = { nullptr }; insert(QLatin1String("ABS"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The abs(X) function returns the absolute value of the numeric argument X. Abs(X) returns NULL if X is NULL. Abs(X) returns 0.0 if X is a string or blob that cannot be converted to a numeric value. If X is the integer -9223372036854775808 then abs(X) throws an integer overflow error since there is no equivalent positive 64-bit two complement value. */ // example: SELECT ABS(-27), ABS(-3.1415), ABS(NULL + 1) // result: 27, 3.1415, NULL decl->copyReturnTypeFromArg = 0; _SIG(abs_1, argAnyNumberOrNull); insert(QLatin1String("CEILING"), decl = new CeilingFloorFunctionDeclaration); /* ceiling(X) returns the largest integer value not less than X. */ // See also https://dev.mysql.com/doc/refman/5.1/en/mathematical-functions.html#function_ceiling // See also http://www.postgresql.org/docs/9.5/static/functions-math.html#FUNCTIONS-MATH-FUNC-TABLE // SQLite has no equivalent of ceiling() so this is used: // (CASE WHEN X = CAST(X AS INT) THEN CAST(X AS INT) WHEN X >= 0 THEN CAST(X AS INT) + 1 ELSE CAST(X AS INT) END) //! @todo add a custom function to SQLite to optimize/simplify things // example: SELECT CEILING(3.14), CEILING(-99.001) // result: 4, -99 _SIG(ceiling, argAnyNumberOrNull); insert(QLatin1String("CHAR"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The char(X1,X2,...,XN) function returns a string composed of characters having the unicode code point values of integers X1 through XN, respectively. */ // example: SELECT CHAR(75,69,88,73), CHAR() // result: "KEXI" "" decl->defaultReturnType = KDbField::LongText; static int char_min_args[] = { 0 }; _SIG(char_N, argAnyIntOrNull, multipleArgs, char_min_args); insert(QLatin1String("COALESCE"), decl = new CoalesceFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The coalesce() function returns a copy of its first non-NULL argument, or NULL if all arguments are NULL. Coalesce() must have at least 2 arguments. */ // example: SELECT COALESCE(NULL, 17, NULL, "A") // result: 17 static int coalesce_min_args[] = { 2 }; _SIG(coalesce_N, argAnyOrNull, multipleArgs, coalesce_min_args); insert(QLatin1String("FLOOR"), decl = new CeilingFloorFunctionDeclaration); /* floor(X) returns the largest integer value not greater than X. */ // See also https://dev.mysql.com/doc/refman/5.1/en/mathematical-functions.html#function_floor // See also http://www.postgresql.org/docs/9.5/static/functions-math.html#FUNCTIONS-MATH-FUNC-TABLE // SQLite has no equivalent of floor() so this is used: // (CASE WHEN X >= 0 OR X = CAST(X AS INT) THEN CAST(X AS INT) ELSE CAST(X AS INT) - 1 END) //! @todo add a custom function to SQLite to optimize/simplify things // example: SELECT FLOOR(3.14), FLOOR(-99.001) // result: 3, -100 _SIG(floor, argAnyNumberOrNull); insert(QLatin1String("GREATEST"), decl = new MinMaxFunctionDeclaration); m_aliases.insert(QLatin1String("MAX"), decl); // From https://www.sqlite.org/lang_corefunc.html // For SQLite MAX() is used. // If arguments are of text type, to each argument default (unicode) collation // is assigned that is configured for SQLite by KDb. // Example: SELECT MAX('ą' COLLATE '', 'z' COLLATE ''). // Example: SELECT MAX('ą' COLLATE '', 'z' COLLATE ''). /* The multi-argument max() function returns the argument with the maximum value, or return NULL if any argument is NULL. The multi-argument max() function searches its arguments from left to right for an argument that defines a collating function and uses that collating function for all string comparisons. If none of the arguments to max() define a collating function, then the BINARY collating function is used. Note that max() is a simple function when it has 2 or more arguments but operates as an aggregate function if given only a single argument. */ // For pgsql GREATEST() function ignores NULL values, it only returns NULL // if all the expressions evaluate to NULL. So this is used for MAX(v0,..,vN): // (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE GREATEST(v0,..,vN) END) // See also http://www.postgresql.org/docs/9.5/static/functions-conditional.html#FUNCTIONS-GREATEST-LEAST //! @todo for pgsql CREATE FUNCTION can be used to speed up and simplify things // For mysql GREATEST() is used. // See https://dev.mysql.com/doc/refman/5.1/en/comparison-operators.html#function_greatest // Note: Before MySQL 5.0.13, GREATEST() returns NULL only if all arguments are NULL // (like pgsql). As of 5.0.13, it returns NULL if any argument is NULL (like sqlite's MAX()). // See also https://bugs.mysql.com/bug.php?id=15610 //! @todo MySQL: check for server version and don't use the pgsql's approach for ver >= 5.0.13 //! We cannot do that now because we only have access to driver, not the connection. // example: SELECT GREATEST("Z", "ą", "AA"), MAX(0.1, 7.1, 7), GREATEST(9, NULL, -1) // result: "Z", 7.1, NULL static int greatest_min_args[] = { 2 }; _SIG(greatest_N, argAnyOrNull, multipleArgs, greatest_min_args); insert(QLatin1String("HEX"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_hex /* The hex() function interprets its argument as a BLOB and returns a string which is the upper-case hexadecimal rendering of the content of that blob. */ /* For pgsql UPPER(ENCODE(val, 'hex')) is used, See http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-OTHER */ // example: SELECT HEX(X'BEEF'), HEX('DEAD') // result: "BEEF", "44454144" //! @todo HEX(int) for SQLite is not the same as HEX(int) for MySQL so we disable it //! -- maybe can be wrapped? decl->defaultReturnType = KDbField::LongText; _SIG(hex_1, argAnyTextBLOBOrNull); insert(QLatin1String("IFNULL"), decl = new CoalesceFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The ifnull() function returns a copy of its first non-NULL argument, or NULL if both arguments are NULL. Ifnull() must have exactly 2 arguments. The ifnull() function is equivalent to coalesce() with two arguments. */ // For postgresql coalesce() is used. // example: SELECT IFNULL(NULL, 17), IFNULL(NULL, NULL) // result: 17, NULL _SIG(ifnull_2, argAnyOrNull, argAnyOrNull); insert(QLatin1String("INSTR"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The instr(X,Y) function finds the first occurrence of string Y within string X and returns the number of prior characters plus 1, or 0 if Y is nowhere found within X. If both arguments X and Y to instr(X,Y) are non-NULL and are not BLOBs then both are interpreted as strings. If either X or Y are NULL in instr(X,Y) then the result is NULL. */ //! @todo PostgreSQL does not have instr() but CREATE FUNCTION can be used, //! see http://www.postgresql.org/docs/9.5/static/plpgsql-porting.html //! @todo support (BLOB, BLOB)? /* From the same docs: Or, if X and Y are both BLOBs, then instr(X,Y) returns one more than the number bytes prior to the first occurrence of Y, or 0 if Y does not occur anywhere within X. */ // example: SELECT INSTR("KEXI", "X"), INSTR("KEXI", "ZZ") // result: 3, 0 decl->defaultReturnType = KDbField::Integer; _SIG(instr_2, argAnyTextOrNull, argAnyTextOrNull); insert(QLatin1String("LEAST"), decl = new MinMaxFunctionDeclaration); m_aliases.insert(QLatin1String("MIN"), decl); // From https://www.sqlite.org/lang_corefunc.html // For SQLite uses MIN(). /* The multi-argument min() function returns the argument with the minimum value, or return NULL if any argument is NULL. The multi-argument min() function searches its arguments from left to right for an argument that defines a collating function and uses that collating function for all string comparisons. If none of the arguments to max() define a collating function, then the BINARY collating function is used. Note that max() is a simple function when it has 2 or more arguments but operates as an aggregate function if given only a single argument. */ // For pgsql LEAST() function ignores NULL values, it only returns NULL // if all the expressions evaluate to NULL. So this is used for MAX(v0,..,vN): // (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE LEAST(v0,..,vN) END) // See also http://www.postgresql.org/docs/9.5/static/functions-conditional.html#FUNCTIONS-GREATEST-LEAST //! @todo for pgsql CREATE FUNCTION can be used to speed up and simplify things // For mysql LEAST() is used. // See https://dev.mysql.com/doc/refman/5.1/en/comparison-operators.html#function_least // Note: Before MySQL 5.0.13, LEAST() returns NULL only if all arguments are NULL // (like pgsql). As of 5.0.13, it returns NULL if any argument is NULL (like sqlite's MIN()). //! @todo MySQL: check for server version and don't use the pgsql's approach for ver >= 5.0.13 //! We cannot do that now because we only have access to driver, not the connection. // See also https://bugs.mysql.com/bug.php?id=15610 // example: SELECT LEAST("Z", "ą", "AA"), MIN(0.1, 7.1, 7), LEAST(9, NULL, -1) // result: "ą", 0.1, NULL static int least_min_args[] = { 2 }; _SIG(least_N, argAnyOrNull, multipleArgs, least_min_args); insert(QLatin1String("LENGTH"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_length /* For a string value X, the length(X) function returns the number of characters (not bytes) in X prior to the first NUL character. Since SQLite strings do not normally contain NUL characters, the length(X) function will usually return the total number of characters in the string X. For a blob value X, length(X) returns the number of bytes in the blob. If X is NULL then length(X) is NULL. If X is numeric then length(X) returns the length of a string representation of X. */ /* For postgres octet_length(val) is used if val is a of BLOB type. length(val) for BLOB cannot be used because it returns number of bits. */ /* For mysql char_length(val) is used. This is because length(val) in mysql returns number of bytes, what is not right for multibyte (unicode) encodings. */ // example: SELECT LENGTH('Straße'), LENGTH(X'12FE') // result: 6, 2 decl->defaultReturnType = KDbField::Integer; _SIG(length_1, argAnyTextBLOBOrNull); insert(QLatin1String("LOWER"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The lower(X) function returns a copy of string X with all characters converted to lower case. */ // Note: SQLite such as 3.8 without ICU extension does not convert non-latin1 characters // too well; Kexi uses ICU extension by default so the results are very good. // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_lower // See also http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-SQL // example: SELECT LOWER("MEGSZENTSÉGTELENÍTHETETLENSÉGESKEDÉSEITEKÉRT") // result: "megszentségteleníthetetlenségeskedéseitekért" decl->defaultReturnType = KDbField::LongText; _SIG(lower_1, argAnyTextOrNull); insert(QLatin1String("LTRIM"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The ltrim(X,Y) function returns a string formed by removing any and all characters that appear in Y from the left side of X. If the Y argument is omitted, ltrim(X) removes spaces from the left side of X.*/ // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_ltrim //! @todo MySQL's LTRIM only supports one arg. TRIM() does not work too //! https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_trim // See also http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-SQL // example: SELECT LTRIM(" John Smith") // result: "John Smith" // example: SELECT LTRIM("a b or c", "ab ") // result: "or c" decl->defaultReturnType = KDbField::LongText; _SIG(ltrim_1, argAnyTextOrNull); _SIG(ltrim_2, argAnyTextOrNull, argAnyTextOrNull); insert(QLatin1String("NULLIF"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The nullif(X,Y) function returns its first argument if the arguments are different and NULL if the arguments are the same. The nullif(X,Y) function searches its arguments from left to right for an argument that defines a collating function and uses that collating function for all string comparisons. If neither argument to nullif() defines a collating function then the BINARY is used. */ // See also https://dev.mysql.com/doc/refman/5.1/en/control-flow-functions.html#function_nullif // See also http://www.postgresql.org/docs/9.5/static/functions-conditional.html#FUNCTIONS-NULLIF // example: SELECT NULLIF("John", "Smith"), NULLIF(177, 177) // result: "John", NULL decl->copyReturnTypeFromArg = 0; _SIG(nullif_2, argAnyOrNull, argAnyOrNull); insert(QLatin1String("RANDOM"), decl = new RandomFunctionDeclaration); /* RANDOM() returns a random floating-point value v in the range 0 <= v < 1.0. RANDOM(X,Y) - returns returns a random integer that is equal or greater than X and less than Y. */ // For MySQL RANDOM() is equal to RAND(). // For MySQL RANDOM(X,Y) is equal to (X + FLOOR(RAND() * (Y - X)) // For PostreSQL RANDOM() is equal to RANDOM(). // For PostreSQL RANDOM(X,Y) is equal to (X + FLOOR(RANDOM() * (Y - X)) // Because SQLite returns integer between -9223372036854775808 and +9223372036854775807, // so RANDOM() for SQLite is equal to (RANDOM()+9223372036854775807)/18446744073709551615. // Similarly, RANDOM(X,Y) for SQLite is equal // to (X + CAST((Y - X) * (RANDOM()+9223372036854775807)/18446744073709551615 AS INT)). // See also https://dev.mysql.com/doc/refman/5.1/en/mathematical-functions.html#function_rand // See also http://www.postgresql.org/docs/9.5/static/functions-math.html#FUNCTIONS-MATH-RANDOM-TABLE //! @note rand(X) (where X is a seed value to set) isn't portable between MySQL and PostgreSQL, //! and does not exist in SQLite, so we don't support it. // example: SELECT RANDOM(), RANDOM(2, 5) // result: (some random floating-point value v where 0 <= v < 1.0) // example: SELECT RANDOM(2, 5) // result: (some random integer value v where 2 <= v < 5) decl->defaultReturnType = KDbField::Double; _SIG0; _SIG(random_2, argAnyIntOrNull, argAnyIntOrNull); insert(QLatin1String("ROUND"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The round(X,Y) function returns a floating-point value X rounded to Y digits to the right of the decimal point. If the Y argument is omitted, it is assumed to be 0. */ // See also https://dev.mysql.com/doc/refman/5.1/en/mathematical-functions.html#function_round // See also http://www.postgresql.org/docs/9.5/static/functions-math.html#FUNCTIONS-MATH-FUNC-TABLE //! @note round(X,Y) where Y < 0 is supported only by MySQL so we ignore this case // example: SELECT ROUND(-1.13), ROUND(-5.51), ROUND(5.51), ROUND(1.298, 1), ROUND(1.298, 0), ROUND(7) // result: -1, -6, 6, 1.3, 1, 7 decl->copyReturnTypeFromArg = 0; _SIG(round_1, argAnyNumberOrNull); _SIG(round_2, argAnyNumberOrNull, argAnyIntOrNull); insert(QLatin1String("RTRIM"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The rtrim(X,Y) function returns a string formed by removing any and all characters that appear in Y from the right side of X. If the Y argument is omitted, rtrim(X) removes spaces from the right side of X. */ // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_ltrim //! @todo MySQL's RTRIM only supports one arg. TRIM() does not work too //! https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_trim // See also http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-SQL // example: SELECT RTRIM("John Smith ") // result: "John Smith" // example: SELECT RTRIM("a b or c", "orc ") // result: "a b" decl->defaultReturnType = KDbField::LongText; _SIG(rtrim_1, argAnyTextOrNull); _SIG(rtrim_2, argAnyTextOrNull, argAnyTextOrNull); insert(QLatin1String("SOUNDEX"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The soundex(X) function returns a string that is the soundex encoding of the string X. The string "?000" is returned if the argument is NULL or contains non-ASCII alphabetic characters. */ // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_soundex // See also http://www.postgresql.org/docs/9.5/static/fuzzystrmatch.html#AEN165853 //! @todo we call drv_executeVoidSQL("CREATE EXTENSION IF NOT EXISTS fuzzystrmatch") on connection, //! do that on first use of SOUNDEX() // example: SELECT SOUNDEX("John") // result: "J500" decl->defaultReturnType = KDbField::Text; _SIG(soundex, argAnyTextOrNull); insert(QLatin1String("SUBSTR"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The substr(X,Y) returns all characters through the end of the string X beginning with the Y-th. The left-most character of X is number 1. If Y is negative then the first character of the substring is found by counting from the right rather than the left. If Z is negative then the abs(Z) characters preceding the Y-th character are returned. If X is a string then characters indices refer to actual UTF-8 characters. If X is a BLOB then the indices refer to bytes. */ _SIG(substr_2, argAnyTextOrNull, argAnyIntOrNull); /* The substr(X,Y,Z) function returns a substring of input string X that begins with the Y-th character and which is Z characters long. */ _SIG(substr_3, argAnyTextOrNull, argAnyIntOrNull, argAnyIntOrNull); decl->copyReturnTypeFromArg = 0; insert(QLatin1String("TRIM"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The trim(X,Y) function returns a string formed by removing any and all characters that appear in Y from both ends of X. If the Y argument is omitted, trim(X) removes spaces from both ends of X. */ // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_trim //! @todo MySQL's TRIM only supports one arg. TRIM() does not work too //! https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_trim // See also http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-SQL // example: SELECT TRIM(" John Smith ") // result: "John Smith" // example: SELECT TRIM("a b or c", "orca ") // result: "b" decl->defaultReturnType = KDbField::LongText; _SIG(trim_1, argAnyTextOrNull); _SIG(trim_2, argAnyTextOrNull, argAnyTextOrNull); insert(QLatin1String("UNICODE"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The unicode(X) function returns the numeric unicode code point corresponding to the first character of the string X. If the argument to unicode(X) is not a string then the result is undefined. */ // For MySQL ORD(CONVERT(X USING UTF16)) is used (ORD(X) returns a UTF-16 number) // For PostreSQL ASCII(X) is used. // example: SELECT UNICODE('A'), UNICODE('ą'), UNICODE('Δ'), UNICODE('葉') // result: 65, 261, 916, 33865 decl->defaultReturnType = KDbField::Integer; _SIG(unicode_1, argAnyTextOrNull); insert(QLatin1String("UPPER"), decl = new BuiltInFunctionDeclaration); // From https://www.sqlite.org/lang_corefunc.html /* The upper(X) function returns a copy of string X with all characters converted to upper case. */ // Note: SQLite such as 3.8 without ICU extension does not convert non-latin1 characters // too well; Kexi uses ICU extension by default so the results are very good. // See also https://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_upper // See also http://www.postgresql.org/docs/9.5/static/functions-string.html#FUNCTIONS-STRING-SQL // example: SELECT UPPER("megszentségteleníthetetlenségeskedéseitekért") // result: "MEGSZENTSÉGTELENÍTHETETLENSÉGESKEDÉSEITEKÉRT" decl->defaultReturnType = KDbField::LongText; _SIG(upper_1, argAnyTextOrNull); #ifdef KDB_ENABLE_SQLITE_SPECIFIC_FUNCTIONS insert(QLatin1String("GLOB"), decl = new BuiltInFunctionDeclaration); //! @todo GLOB(X,Y) is SQLite-specific and is not present in MySQL so we don't expose it; use GLOB operator instead. //! We may want to address it in raw SQL generation time. // From https://www.sqlite.org/lang_corefunc.html /* The glob(X,Y) function is equivalent to the expression "Y GLOB X". Note that the X and Y arguments are reversed in the glob() function relative to the infix GLOB operator. */ // example: SELECT GLOB("Foo*", "FooBar"), GLOB("Foo*", "foobar") // result: TRUE, FALSE decl->defaultReturnType = KDbField::Boolean; _SIG(glob_2, argAnyTextOrNull, argAnyOrNull /* will be casted to text */); insert(QLatin1String("LIKE"), decl = new BuiltInFunctionDeclaration); //! @todo LIKE(X,Y,[Z]) not present in MySQL so we don't expose it; use LIKE operator instead. //! We may want to address it in raw SQL generation time. // From https://www.sqlite.org/lang_corefunc.html /* The like() function is used to implement the "Y LIKE X [ESCAPE Z]" expression. If the optional ESCAPE clause is present, then the like() function is invoked with three arguments. Otherwise, it is invoked with two arguments only. Note that the X and Y parameters are reversed in the like() function relative to the infix LIKE operator.*/ decl->defaultReturnType = KDbField::Boolean; _SIG(like_2, argAnyTextOrNull, argAnyTextOrNull); _SIG(like_3, argAnyTextOrNull, argAnyTextOrNull, argAnyTextOrNull); #endif } BuiltInFunctionDeclaration* BuiltInFunctions::value(const QString &name) const { BuiltInFunctionDeclaration* f = QHash::value(name); if (!f) { f = m_aliases.value(name); } return f; } QStringList BuiltInFunctions::aliases() const { return m_aliases.keys(); } Q_GLOBAL_STATIC(BuiltInFunctions, _builtInFunctions) //========================================= KDbFunctionExpressionData::KDbFunctionExpressionData() : KDbExpressionData() { ExpressionDebug << "FunctionExpressionData" << ref; setArguments(ExplicitlySharedExpressionDataPointer()); } KDbFunctionExpressionData::KDbFunctionExpressionData(const QString& aName, ExplicitlySharedExpressionDataPointer arguments) : KDbExpressionData() , name(aName) { setArguments(arguments); ExpressionDebug << "FunctionExpressionData" << ref << *args; } KDbFunctionExpressionData::~KDbFunctionExpressionData() { ExpressionDebug << "~FunctionExpressionData" << ref; } KDbFunctionExpressionData* KDbFunctionExpressionData::clone() { ExpressionDebug << "FunctionExpressionData::clone" << *this; KDbFunctionExpressionData *cloned = new KDbFunctionExpressionData(*this); ExpressionDebug << "FunctionExpressionData::clone" << *cloned; cloned->args = args->clone(); return cloned; } void KDbFunctionExpressionData::debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const { dbg.nospace() << "FunctionExp(" << name; if (args.data()) { dbg.nospace() << ','; args.data()->debug(dbg, callStack); } dbg.nospace() << QString::fromLatin1(",type=%1)").arg(KDbDriver::defaultSQLTypeName(type())); } static QByteArray greatestOrLeastName(const QByteArray &name) { if (name == "MAX") { return "GREATEST"; } if (name == "MIN") { return "LEAST"; } return name; } KDbEscapedString KDbFunctionExpressionData::toStringInternal( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { KDbNArgExpressionData *argsData = args->convert(); if (name == QLatin1String("HEX")) { if (driver) { return driver->hexFunctionToString(KDbNArgExpression(args), params, callStack); } } else if (name == QLatin1String("IFNULL")) { if (driver) { return driver->ifnullFunctionToString(KDbNArgExpression(args), params, callStack); } } else if (name == QLatin1String("LENGTH")) { if (driver) { return driver->lengthFunctionToString(KDbNArgExpression(args), params, callStack); } } else if (name == QLatin1String("GREATEST") || name == QLatin1String("MAX") || name == QLatin1String("LEAST") || name == QLatin1String("MIN")) { if (driver) { return driver->greatestOrLeastFunctionToString( QString::fromLatin1(greatestOrLeastName(name.toLatin1())), KDbNArgExpression(args), params, callStack); } // else: don't change MIN/MAX } else if (name == QLatin1String("RANDOM")) { if (driver) { return driver->randomFunctionToString(KDbNArgExpression(args), params, callStack); } } else if (name == QLatin1String("CEILING") || name == QLatin1String("FLOOR")) { if (driver) { return driver->ceilingOrFloorFunctionToString(name, KDbNArgExpression(args), params, callStack); } } else if (name == QLatin1String("UNICODE")) { if (driver) { return driver->unicodeFunctionToString(KDbNArgExpression(args), params, callStack); } } return KDbFunctionExpressionData::toString(name, driver, argsData, params, callStack); } void KDbFunctionExpressionData::getQueryParameters(QList* params) { Q_ASSERT(params); args->getQueryParameters(params); } KDbField::Type KDbFunctionExpressionData::typeInternal(KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); const BuiltInFunctionDeclaration *decl = _builtInFunctions->value(name); if (decl) { - return decl->returnType(this, 0); + return decl->returnType(this, nullptr); } //! @todo return KDbField::InvalidType; } static void setIncorrectNumberOfArgumentsErrorMessage(KDbParseInfo *parseInfo, int count, const std::vector &argCounts, const QString &name) { parseInfo->setErrorMessage( KDbFunctionExpressionData::tr("Incorrect number of arguments (%1)").arg(count)); const int maxArgCount = argCounts[argCounts.size() - 1]; const int minArgCount = argCounts[0]; QString firstSentence; if (count > maxArgCount) { firstSentence = KDbFunctionExpressionData::tr("Too many arguments.%1", "don't use space before %1") .arg(QLatin1String(" ")); } if (count < minArgCount) { firstSentence = KDbFunctionExpressionData::tr("Too few arguments.%1", "don't use space before %1") .arg(QLatin1String(" ")); } if (argCounts.size() == 1) { const int c = argCounts[0]; if (c == 0) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function does not accept any arguments.") .arg(firstSentence, name)); } else if (c == 1) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires 1 argument.") .arg(firstSentence, name)); } else { //~ singular %1%2() function requires %3 argument. //~ plural %1%2() function requires %3 arguments. parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires %3 argument(s).", "", c) .arg(firstSentence, name).arg(c)); } } else if (argCounts.size() == 2) { const int c1 = argCounts[0]; const int c2 = argCounts[1]; if (c2 == 1) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires 0 or 1 argument.", "the function requires zero or one argument") .arg(firstSentence, name)); } else { //~ singular %1%2() function requires %3 or %4 argument. //~ plural %1%2() function requires %3 or %4 arguments. parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires %3 or %4 argument(s).", "", c2) .arg(firstSentence, name).arg(c1).arg(c2)); } } else if (argCounts.size() == 3) { //~ singular %1%2() function requires %3 or %4 or %5 argument. //~ plural %1%2() function requires %3 or %4 or %5 arguments. parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires %3 or %4 or %5 argument(s).", "", argCounts[2]) .arg(firstSentence, name).arg(argCounts[0]) .arg(argCounts[1]).arg(argCounts[2])); } else { QString listCounts; for(std::vector::const_iterator it(argCounts.begin()); it != argCounts.end(); ++it) { if (listCounts.isEmpty()) { listCounts += QString::number(*it); } else { listCounts = KDbFunctionExpressionData::tr("%1 or %2").arg(listCounts).arg(*it); } } parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1%2() function requires %3 argument(s).", "", argCounts[argCounts.size() - 1]) .arg(firstSentence, name, listCounts)); } } static void setIncorrectTypeOfArgumentsErrorMessage(KDbParseInfo *parseInfo, int argNum, KDbField::Type type, int *argTypes, const QString &name) { QString listTypes; int *argType = argTypes; while(*argType != KDbField::InvalidType) { if (!listTypes.isEmpty()) { listTypes += KDbFunctionExpressionData::tr(" or "); } const KDbField::Type realFieldType = KDb::intToFieldType(*argType); if (realFieldType != KDbField::InvalidType) { listTypes += KDbFunctionExpressionData::tr("\"%1\"") .arg(KDbField::typeName(realFieldType)); } else if (*argType == KDbField::Null) { listTypes += KDbFunctionExpressionData::tr("\"%1\"") .arg(KDbField::typeName(KDbField::Null)); } else if (*argType == AnyText) { listTypes += KDbFunctionExpressionData::tr("\"%1\"") .arg(KDbField::typeName(KDbField::Text)); } else if (*argType == AnyInt) { listTypes += KDbFunctionExpressionData::tr("\"%1\"") .arg(KDbField::typeName(KDbField::Integer)); } else if (*argType == AnyFloat) { listTypes += KDbFunctionExpressionData::tr("\"%1\"") .arg(KDbField::typeGroupName(KDbField::FloatGroup)); // better than typeName() in this case } else if (*argType == AnyNumber) { listTypes += KDbFunctionExpressionData::tr("\"Number\""); } else if (*argType == Any) { listTypes += KDbFunctionExpressionData::tr("\"Any\"", "Any data type"); } ++argType; } parseInfo->setErrorMessage(KDbFunctionExpressionData::tr("Incorrect type of argument")); QString lastSentence = KDbFunctionExpressionData::tr("Specified argument is of type \"%1\".") .arg(KDbField::typeName(type)); if (argNum == 0) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's first argument should be of type %2. %3") .arg(name, listTypes, lastSentence)); } else if (argNum == 1) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's second argument should be of type %2. %3") .arg(name, listTypes, lastSentence)); } else if (argNum == 2) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's third argument should be of type %2. %3") .arg(name, listTypes, lastSentence)); } else if (argNum == 3) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's fourth argument should be of type %2. %3") .arg(name, listTypes, lastSentence)); } else if (argNum == 4) { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's fifth argument should be of type %2. %3") .arg(name, listTypes, lastSentence)); } else { parseInfo->setErrorDescription( KDbFunctionExpressionData::tr("%1() function's %2 argument should be of type %3. %4") .arg(name).arg(argNum + 1).arg(listTypes, lastSentence)); } } //! @return true if type rule @a argType matches concrete type @a actualType static bool typeMatches(int argType, KDbField::Type actualType) { if (argType == AnyText) { if (KDbField::isTextType(actualType)) { return true; } } else if (argType == AnyInt) { if (KDbField::isIntegerType(actualType)) { return true; } } else if (argType == AnyFloat) { if (KDbField::isFPNumericType(actualType)) { return true; } } else if (argType == AnyNumber) { if (KDbField::isNumericType(actualType)) { return true; } } else if (argType == Any) { return true; } else { if (argType == actualType) { return true; } } return false; } static int findMatchingType(int *argTypePtr, KDbField::Type actualType) { for (; *argTypePtr != KDbField::InvalidType; ++argTypePtr) { if (typeMatches(*argTypePtr, actualType)) { break; } } return *argTypePtr; } bool KDbFunctionExpressionData::validateInternal(KDbParseInfo *parseInfo, KDb::ExpressionCallStack* callStack) { if (!args->validate(parseInfo, callStack)) { return false; } if (args->token != ',') { // arguments required: NArgExpr with token ',' return false; } if (args->children.count() > KDB_MAX_FUNCTION_ARGS) { parseInfo->setErrorMessage( tr("Too many arguments for function.")); parseInfo->setErrorDescription( tr("Maximum number of arguments for function %1() is %2.") .arg(args->children.count()).arg(KDB_MAX_FUNCTION_ARGS)); return false; } if (!args->validate(parseInfo)) { return false; } if (name.isEmpty()) { return false; } const BuiltInFunctionDeclaration *decl = _builtInFunctions->value(name); if (!decl) { return false; } const KDbNArgExpressionData *argsData = args->convertConst(); if (argsData->containsInvalidArgument()) { return false; } // Find matching signature const int count = args->children.count(); bool properArgCount = false; std::vector argCounts; int i = 0; argCounts.resize(decl->signatures.size()); - int **signature = 0; + int **signature = nullptr; bool multipleArgs = false; // special case, e.g. for CHARS(v1, ... vN) for(std::vector::const_iterator it(decl->signatures.begin()); it != decl->signatures.end(); ++it, ++i) { signature = *it; int **arg = signature; int expectedCount = 0; while(*arg && *arg != BuiltInFunctions::multipleArgs) { ++arg; ++expectedCount; } multipleArgs = *arg == BuiltInFunctions::multipleArgs; if (multipleArgs) { ++arg; const int minArgs = arg[0][0]; properArgCount = count >= minArgs; if (!properArgCount) { parseInfo->setErrorMessage( tr("Incorrect number of arguments (%1)").arg(count)); if (minArgs == 1) { parseInfo->setErrorDescription( tr("Too few arguments. %1() function requires " "at least one argument.").arg(name)); } else if (minArgs == 2) { parseInfo->setErrorDescription( tr("Too few arguments. %1() function requires " "at least two arguments.").arg(name)); } else if (minArgs == 3) { parseInfo->setErrorDescription( tr("Too few arguments. %1() function requires " "at least three arguments.").arg(name)); } else { parseInfo->setErrorDescription( tr("Too few arguments. %1() function requires " "at least %2 arguments.").arg(name).arg(minArgs)); } return false; } break; } else if (count == expectedCount) { // arg # matches properArgCount = true; break; } else { argCounts[i] = expectedCount; } } if (!properArgCount) { std::unique(argCounts.begin(), argCounts.end()); std::sort(argCounts.begin(), argCounts.end()); // sort so we can easier check the case setIncorrectNumberOfArgumentsErrorMessage(parseInfo, count, argCounts, name); return false; } // Verify types if (multipleArgs) { // special signature: {typesForAllArgs, [multipleArgs-token], MIN, 0} int **arg = signature; int *typesForAllArgs = arg[0]; int i = 0; foreach(const ExplicitlySharedExpressionDataPointer &expr, args->children) { const KDbField::Type exprType = expr->type(); // cache: evaluating type of expressions can be expensive const bool isQueryParameter = expr->convertConst(); if (!isQueryParameter) { // (query parameter always matches) const int matchingType = findMatchingType(typesForAllArgs, exprType); if (matchingType == KDbField::InvalidType) { setIncorrectTypeOfArgumentsErrorMessage(parseInfo, i, exprType, typesForAllArgs, name); return false; } } ++i; } } else { // typical signature: array of type-lists int **arg = signature; int i=0; foreach(const ExplicitlySharedExpressionDataPointer &expr, args->children) { const KDbField::Type exprType = expr->type(); // cache: evaluating type of expressions can be expensive const bool isQueryParameter = expr->convertConst(); if (!isQueryParameter) { // (query parameter always matches) const int matchingType = findMatchingType(arg[0], exprType); if (matchingType == KDbField::InvalidType) { setIncorrectTypeOfArgumentsErrorMessage(parseInfo, i, exprType, arg[0], name); return false; } } ++arg; ++i; } } // Check type just now. If we checked earlier, possible error message would be less informative. if (decl->returnType(this, parseInfo) == KDbField::InvalidType) { return false; } return true; } void KDbFunctionExpressionData::setArguments(ExplicitlySharedExpressionDataPointer arguments) { args = (arguments && arguments->convert()) ? arguments : ExplicitlySharedExpressionDataPointer(new KDbNArgExpressionData); children.append(args); args->parent = this; args->token = ','; args->expressionClass = KDb::ArgumentListExpression; } //static KDbEscapedString KDbFunctionExpressionData::toString( const QString &name, const KDbDriver *driver, const KDbNArgExpressionData *args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) { return KDbEscapedString(name + QLatin1Char('(')) + args->toString(driver, params, callStack) + KDbEscapedString(')'); } //========================================= inline KDb::ExpressionClass classForFunctionName(const QString& name) { if (KDbFunctionExpression::isBuiltInAggregate(name)) return KDb::AggregationExpression; else return KDb::FunctionExpression; } KDbFunctionExpression::KDbFunctionExpression() : KDbExpression(new KDbFunctionExpressionData) { ExpressionDebug << "KDbFunctionExpression() ctor" << *this; } KDbFunctionExpression::KDbFunctionExpression(const QString& name) : KDbExpression(new KDbFunctionExpressionData(name), classForFunctionName(name), KDbToken()/*undefined*/) { } KDbFunctionExpression::KDbFunctionExpression(const QString& name, const KDbNArgExpression& arguments) : KDbExpression(new KDbFunctionExpressionData(name.toUpper(), arguments.d), classForFunctionName(name), KDbToken()/*undefined*/) { } KDbFunctionExpression::KDbFunctionExpression(const KDbFunctionExpression& expr) : KDbExpression(expr) { } KDbFunctionExpression::KDbFunctionExpression(KDbExpressionData* data) : KDbExpression(data) { ExpressionDebug << "KDbFunctionExpression ctor (KDbExpressionData*)" << *this; } KDbFunctionExpression::KDbFunctionExpression(const ExplicitlySharedExpressionDataPointer &ptr) : KDbExpression(ptr) { } KDbFunctionExpression::~KDbFunctionExpression() { } // static bool KDbFunctionExpression::isBuiltInAggregate(const QString& function) { return _builtInAggregates->contains(function.toUpper()); } // static QStringList KDbFunctionExpression::builtInAggregates() { return _builtInAggregates->toList(); } //static KDbEscapedString KDbFunctionExpression::toString( const QString &name, const KDbDriver *driver, const KDbNArgExpression& args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) { const KDbNArgExpressionData *argsData = args.d.constData()->convertConst(); return KDbFunctionExpressionData::toString(name, driver, argsData, params, callStack); } QString KDbFunctionExpression::name() const { return d->convert()->name; } void KDbFunctionExpression::setName(const QString &name) { d->convert()->name = name; } KDbNArgExpression KDbFunctionExpression::arguments() { return KDbNArgExpression(d->convert()->args); } void KDbFunctionExpression::setArguments(const KDbNArgExpression &arguments) { d->convert()->setArguments(arguments.d); } // static KDbEscapedString KDbFunctionExpression::greatestOrLeastFunctionUsingCaseToString( const QString &name, const KDbDriver *driver, const KDbNArgExpression &args, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) { // (CASE WHEN (v0) IS NULL OR .. OR (vN) IS NULL THEN NULL ELSE F(v0,..,vN) END) if (args.argCount() >= 2) { KDbEscapedString whenSQL; whenSQL.reserve(256); foreach(const ExplicitlySharedExpressionDataPointer &child, args.d.constData()->children) { if (!whenSQL.isEmpty()) { whenSQL += " OR "; } whenSQL += QLatin1Char('(') + child->toString(driver, params, callStack) + QLatin1String(") IS NULL"); } return KDbEscapedString("(CASE WHEN (") + whenSQL + QLatin1String(") THEN NULL ELSE (") + KDbFunctionExpression::toString(name, driver, args, params, callStack) + QLatin1String(") END)"); } return KDbFunctionExpression::toString(name, driver, args, params, callStack); } diff --git a/src/expression/KDbVariableExpression.cpp b/src/expression/KDbVariableExpression.cpp index cfe872c6..909ebaed 100644 --- a/src/expression/KDbVariableExpression.cpp +++ b/src/expression/KDbVariableExpression.cpp @@ -1,294 +1,294 @@ /* This file is part of the KDE project Copyright (C) 2003-2012 Jarosław Staniek Based on nexp.cpp : Parser module of Python-like language (C) 2001 Jarosław Staniek, MIMUW (www.mimuw.edu.pl) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbExpression.h" #include "KDb.h" #include "KDbDriver.h" #include "KDbQuerySchema.h" #include "KDbParser_p.h" #include "kdb_debug.h" KDbVariableExpressionData::KDbVariableExpressionData() : KDbExpressionData() - , field(0) + , field(nullptr) , tablePositionForField(-1) - , tableForQueryAsterisk(0) + , tableForQueryAsterisk(nullptr) { ExpressionDebug << "VariableExpressionData" << ref; } KDbVariableExpressionData::KDbVariableExpressionData(const QString& aName) : KDbExpressionData() , name(aName) - , field(0) + , field(nullptr) , tablePositionForField(-1) - , tableForQueryAsterisk(0) + , tableForQueryAsterisk(nullptr) { ExpressionDebug << "VariableExpressionData" << ref; } KDbVariableExpressionData::~KDbVariableExpressionData() { ExpressionDebug << "~VariableExpressionData" << ref; } KDbVariableExpressionData* KDbVariableExpressionData::clone() { ExpressionDebug << "VariableExpressionData::clone" << *this; return new KDbVariableExpressionData(*this); } void KDbVariableExpressionData::debugInternal(QDebug dbg, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); dbg.nospace() << qPrintable(QString::fromLatin1("VariableExp(\"%1\",type=%2)") .arg(name, field ? KDbDriver::defaultSQLTypeName(type()) : QLatin1String("FIELD_NOT_DEFINED_YET"))); } KDbEscapedString KDbVariableExpressionData::toStringInternal( const KDbDriver *driver, KDbQuerySchemaParameterValueListIterator* params, KDb::ExpressionCallStack* callStack) const { Q_UNUSED(driver); Q_UNUSED(params); Q_UNUSED(callStack); return KDbEscapedString(name); } void KDbVariableExpressionData::getQueryParameters(QList* params) { Q_UNUSED(params); } //! We're assuming it's called after VariableExpr::validate() KDbField::Type KDbVariableExpressionData::typeInternal(KDb::ExpressionCallStack* callStack) const { Q_UNUSED(callStack); if (field) return field->type(); //BTW, asterisks are not stored in VariableExpr outside of parser, so ok. return KDbField::InvalidType; } static void validateImplError(KDbParseInfo *parseInfo_, const QString &errmsg) { KDbParseInfoInternal *parseInfo = static_cast(parseInfo_); parseInfo->setErrorMessage(QLatin1String("Implementation error")); parseInfo->setErrorDescription(errmsg); } bool KDbVariableExpressionData::validateInternal(KDbParseInfo *parseInfo_, KDb::ExpressionCallStack* callStack) { Q_UNUSED(callStack); KDbParseInfoInternal *parseInfo = static_cast(parseInfo_); - field = 0; + field = nullptr; tablePositionForField = -1; - tableForQueryAsterisk = 0; + tableForQueryAsterisk = nullptr; /* taken from parser's addColumn(): */ kdbDebug() << "checking variable name: " << name; QString tableName, fieldName; if (!KDb::splitToTableAndFieldParts(name, &tableName, &fieldName, KDb::SetFieldNameIfNoTableName)) { return false; } //! @todo shall we also support db name? if (tableName.isEmpty()) {//fieldname only if (fieldName == QLatin1String("*")) { return true; } //find first table that has this field - KDbField *firstField = 0; + KDbField *firstField = nullptr; foreach(KDbTableSchema *table, *parseInfo->querySchema()->tables()) { KDbField *f = table->field(fieldName); if (f) { if (!firstField) { firstField = f; } else if (f->table() != firstField->table()) { //ambiguous field name parseInfo->setErrorMessage(tr("Ambiguous field name")); parseInfo->setErrorDescription( tr("Both table \"%1\" and \"%2\" have defined \"%3\" field. " "Use \".%4\" notation to specify table name.", "Note: translate also ") .arg(firstField->table()->name(), f->table()->name(), fieldName, fieldName)); return false; } } } if (!firstField) { parseInfo->setErrorMessage(tr("Field not found")); parseInfo->setErrorDescription( tr("Could not find table containing field \"%1\".").arg(fieldName)); return false; } //ok field = firstField; //store return true; } //table.fieldname or tableAlias.fieldname KDbTableSchema *ts = parseInfo->querySchema()->table(tableName); int tablePosition = -1; if (ts) {//table.fieldname //check if "table" is covered by an alias const QList tPositions = parseInfo->querySchema()->tablePositions(tableName); QString tableAlias; bool covered = true; foreach(int position, tPositions) { tableAlias = parseInfo->querySchema()->tableAlias(position); if (tableAlias.isEmpty() || tableAlias.toLower() == tableName) { covered = false; //uncovered break; } kdbDebug() << " --" << "covered by " << tableAlias << " alias"; } if (covered) { parseInfo->setErrorMessage(tr("Could not access the table directly using its name")); parseInfo->setErrorDescription( tr("Table name \"%1\" is covered by aliases. " "Instead of \"%2\", \"%3\" can be used.") .arg(tableName, tableName + QLatin1Char('.') + fieldName, tableAlias + QLatin1Char('.') + fieldName)); return false; } if (!tPositions.isEmpty()) { tablePosition = tPositions.first(); } } else {//try to find tableAlias tablePosition = parseInfo->querySchema()->tablePositionForAlias(tableName); if (tablePosition >= 0) { ts = parseInfo->querySchema()->tables()->at(tablePosition); if (ts) { // kdbDebug() << " --it's a tableAlias.name"; } } } if (!ts) { parseInfo->setErrorMessage(tr("Table not found")); parseInfo->setErrorDescription(tr("Unknown table \"%1\".").arg(tableName)); return false; } const QList positionsList(parseInfo->tablesAndAliasesForName(tableName)); if (positionsList.isEmpty()) { //for sanity validateImplError(parseInfo, QString::fromLatin1("%1.%2, !positionsList ").arg(tableName, fieldName)); return false; } //it's a table.* if (fieldName == QLatin1String("*")) { if (positionsList.count() > 1) { parseInfo->setErrorMessage(tr("Ambiguous \"%1.*\" expression").arg(tableName)); parseInfo->setErrorDescription(tr("More than one \"%1\" table or alias defined.").arg(tableName)); return false; } tableForQueryAsterisk = ts; return true; } // kdbDebug() << " --it's a table.name"; KDbField *realField = ts->field(fieldName); if (!realField) { parseInfo->setErrorMessage(tr("Field not found")); parseInfo->setErrorDescription( tr("Table \"%1\" has no \"%2\" field.").arg(tableName, fieldName)); return false; } // check if table or alias is used twice and both have the same column // (so the column is ambiguous) if (positionsList.count() > 1) { parseInfo->setErrorMessage(tr("Ambiguous \"%1.%2\" expression").arg(tableName, fieldName)); parseInfo->setErrorDescription( tr("More than one \"%1\" table or alias defined containing \"%2\" field.") .arg(tableName, fieldName)); return false; } field = realField; //store tablePositionForField = tablePosition; return true; } //========================================= KDbVariableExpression::KDbVariableExpression() : KDbExpression(new KDbVariableExpressionData) { ExpressionDebug << "KDbVariableExpression() ctor" << *this; } KDbVariableExpression::KDbVariableExpression(const QString& name) : KDbExpression(new KDbVariableExpressionData(name), KDb::VariableExpression, KDbToken()/*undefined*/) { } KDbVariableExpression::KDbVariableExpression(const KDbVariableExpression& expr) : KDbExpression(expr) { } KDbVariableExpression::KDbVariableExpression(KDbExpressionData* data) : KDbExpression(data) { ExpressionDebug << "KDbVariableExpression ctor (KDbExpressionData*)" << *this; } KDbVariableExpression::KDbVariableExpression(const ExplicitlySharedExpressionDataPointer &ptr) : KDbExpression(ptr) { } KDbVariableExpression::~KDbVariableExpression() { } QString KDbVariableExpression::name() const { return d->convert()->name; } KDbField *KDbVariableExpression::field() const { return d->convert()->field; } int KDbVariableExpression::tablePositionForField() const { return d->convert()->tablePositionForField; } KDbTableSchema *KDbVariableExpression::tableForQueryAsterisk() const { return d->convert()->tableForQueryAsterisk; } diff --git a/src/generated/sqlkeywords.cpp b/src/generated/sqlkeywords.cpp index 4b84c2b5..fa46076b 100644 --- a/src/generated/sqlkeywords.cpp +++ b/src/generated/sqlkeywords.cpp @@ -1,108 +1,108 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2004 Jarosław Staniek This file has been automatically generated from tools/sql_keywords.sh and src/parser/KDbSqlScanner.l and tools/kdb_keywords.txt. Please edit the sql_keywords.sh, not this file! This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbDriver_p.h" const char* const DriverPrivate::kdbSQLKeywords[] = { "AND", "AS", "CREATE", "FROM", "IN", "INTEGER", "IS", "JOIN", "LEFT", "LIKE", "NOT", "NULL", "ON", "OR", "RIGHT", "SELECT", "SIMILAR", "TABLE", "TO", "WHERE", "XOR", "AFTER", "ALL", "ASC", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CHECK", "COLLATE", "COMMIT", "CONSTRAINT", "CROSS", "DATABASE", "DEFAULT", "DELETE", "DESC", "DISTINCT", "DROP", "END", "ELSE", "EXPLAIN", "FOR", "FOREIGN", "FULL", "GROUP", "HAVING", "IGNORE", "INDEX", "INNER", "INSERT", "INTO", "KEY", "LIMIT", "MATCH", "NATURAL", "OFFSET", "ORDER", "OUTER", "PRIMARY", "REFERENCES", "REPLACE", "RESTRICT", "ROLLBACK", "ROW", "SET", "TEMPORARY", "THEN", "TRANSACTION", "UNION", "UNIQUE", "UPDATE", "USING", "VALUES", "WHEN", - 0 + nullptr }; diff --git a/src/interfaces/KDbPreparedStatementInterface.h b/src/interfaces/KDbPreparedStatementInterface.h index 760976cc..414d9a66 100644 --- a/src/interfaces/KDbPreparedStatementInterface.h +++ b/src/interfaces/KDbPreparedStatementInterface.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2008-2010 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_PREPAREDSTATEMENT_IFACE_H #define KDB_PREPAREDSTATEMENT_IFACE_H #include #include "KDbResult.h" #include "KDbPreparedStatement.h" class KDbSqlResult; //! Prepared statement interface for backend-dependent implementations. class KDB_EXPORT KDbPreparedStatementInterface : public KDbResultable { protected: KDbPreparedStatementInterface() {} - virtual ~KDbPreparedStatementInterface() {} + ~KDbPreparedStatementInterface() override {} /*! For implementation. Initializes the prepared statement in a backend-dependent way using recently generated @a sql statement. It should be guaranteed that @a sql is valid and not empty. For example sqlite3_prepare() is used for SQLite. This is called only when d->dirty == true is encountered on execute(), i.e. when attributes of the object (like WHERE field names) change. */ virtual bool prepare(const KDbEscapedString& sql) = 0; //! For implementation, executes the prepared statement //! Type of statement is specified by the @a type parameter. //! @a selectFieldList specifies fields for SELECT statement. //! @a insertFieldList is set to list of fields in INSERT statement. //! Parameters @a parameters are passed to the statement, usually using binding. //! The value pointed by @a resultOwned is set to true if the returned SQL result is owned //! by the prepared statement object. This is expected and the default behaviour. //! If the value pointed by @a resultOwned is set to @c false, the KDbSqlResult object //! will be deleted by the KDbPreparedStatement object before returning. virtual KDbSqlResult* execute( KDbPreparedStatement::Type type, const KDbField::List& selectFieldList, KDbFieldList* insertFieldList, const KDbPreparedStatementParameters& parameters, bool *resultOwned) Q_REQUIRED_RESULT = 0; friend class KDbConnection; friend class KDbPreparedStatement; private: Q_DISABLE_COPY(KDbPreparedStatementInterface) }; #endif diff --git a/src/parser/KDbParser.cpp b/src/parser/KDbParser.cpp index 2d373673..6382cd4f 100644 --- a/src/parser/KDbParser.cpp +++ b/src/parser/KDbParser.cpp @@ -1,215 +1,215 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2004-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbParser.h" #include "KDbParser_p.h" #include "generated/sqlparser.h" #include "KDbConnection.h" #include "KDbTableSchema.h" #include bool parseData(KDbParser *p, const KDbEscapedString &sql); //! Cache class ParserStatic { public: ParserStatic() : statementTypeStrings({ QLatin1String("None"), QLatin1String("Select"), QLatin1String("CreateTable"), QLatin1String("AlterTable"), QLatin1String("Insert"), QLatin1String("Update"), QLatin1String("Delete")}) { } const std::vector statementTypeStrings; private: Q_DISABLE_COPY(ParserStatic) }; Q_GLOBAL_STATIC(ParserStatic, KDb_parserStatic) KDbParser::KDbParser(KDbConnection *connection) : d(new KDbParserPrivate) { d->connection = connection; } KDbParser::~KDbParser() { delete d; } KDbParser::StatementType KDbParser::statementType() const { return d->statementType; } QString KDbParser::statementTypeString() const { Q_ASSERT(size_t(d->statementType) < sizeof(KDb_parserStatic->statementTypeStrings)); return KDb_parserStatic->statementTypeStrings[d->statementType]; } KDbTableSchema *KDbParser::table() { KDbTableSchema *t = d->table; - d->table = 0; + d->table = nullptr; return t; } KDbQuerySchema *KDbParser::query() { KDbQuerySchema *s = d->query; - d->query = 0; + d->query = nullptr; return s; } KDbConnection *KDbParser::connection() const { return d->connection; } KDbParserError KDbParser::error() const { return d->error; } KDbEscapedString KDbParser::statement() const { return d->sql; } void KDbParser::init() { if (d->initialized) return; // nothing to do d->initialized = true; } bool KDbParser::parse(const KDbEscapedString &sql) { init(); reset(); d->sql = sql; KDbParser *oldParser = globalParser; KDbField *oldField = globalField; bool res = parseData(this, sql); globalParser = oldParser; globalField = oldField; return res; } void KDbParser::reset() { d->reset(); } //------------------------------------- class Q_DECL_HIDDEN KDbParserError::Private { public: Private() {} Private(const Private &other) { copy(other); } #define KDbParserErrorPrivateArgs(o) std::tie(o.type, o.message, o.token, o.position) void copy(const Private &other) { KDbParserErrorPrivateArgs((*this)) = KDbParserErrorPrivateArgs(other); } bool operator==(const Private &other) const { return KDbParserErrorPrivateArgs((*this)) == KDbParserErrorPrivateArgs(other); } QString type; QString message; QByteArray token; int position = -1; }; KDbParserError::KDbParserError() : d(new Private) { } KDbParserError::KDbParserError(const QString &type, const QString &message, const QByteArray &token, int position) : d(new Private) { d->type = type; d->message = message; d->token = token; d->position = position; } KDbParserError::KDbParserError(const KDbParserError &other) : d(new Private(*other.d)) { *d = *other.d; } KDbParserError::~KDbParserError() { delete d; } KDbParserError& KDbParserError::operator=(const KDbParserError &other) { if (this != &other) { d->copy(*other.d); } return *this; } bool KDbParserError::operator==(const KDbParserError &other) const { return *d == *other.d; } QString KDbParserError::type() const { return d->type; } QString KDbParserError::message() const { return d->message; } int KDbParserError::position() const { return d->position; } QDebug operator<<(QDebug dbg, const KDbParserError& error) { if (error.type().isEmpty() && error.message().isEmpty()) { return dbg.space() << "KDb:KDbParserError: None"; } return dbg.space() << "KDb:KDbParserError: type=" << error.type() << "message=" << error.message() << "pos=" << error.position() << ")"; } diff --git a/src/parser/KDbParser_p.cpp b/src/parser/KDbParser_p.cpp index da547d8d..85b7a252 100644 --- a/src/parser/KDbParser_p.cpp +++ b/src/parser/KDbParser_p.cpp @@ -1,528 +1,528 @@ /* This file is part of the KDE project Copyright (C) 2004-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbParser_p.h" #include "KDb.h" #include "KDbConnection.h" #include "KDbTableSchema.h" #include "KDbQueryAsterisk.h" #include "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbOrderByColumn.h" #include "kdb_debug.h" #include "generated/sqlparser.h" #include #include -KDbParser *globalParser = 0; -KDbField *globalField = 0; +KDbParser *globalParser = nullptr; +KDbField *globalField = nullptr; QList fieldList; int globalCurrentPos = 0; QByteArray globalToken; extern int yylex_destroy(void); //------------------------------------- KDbParserPrivate::KDbParserPrivate() - : table(0), query(0), connection(0), initialized(false) + : table(nullptr), query(nullptr), connection(nullptr), initialized(false) { reset(); } KDbParserPrivate::~KDbParserPrivate() { reset(); } void KDbParserPrivate::reset() { statementType = KDbParser::NoType; sql.clear(); error = KDbParserError(); delete table; - table = 0; + table = nullptr; delete query; - query = 0; + query = nullptr; } void KDbParserPrivate::setStatementType(KDbParser::StatementType type) { statementType = type; } void KDbParserPrivate::setError(const KDbParserError &err) { error = err; } void KDbParserPrivate::setTableSchema(KDbTableSchema *table) { delete this->table; this->table = table; } void KDbParserPrivate::setQuerySchema(KDbQuerySchema *query) { delete this->query; this->query = query; } //------------------------------------- KDbParseInfo::KDbParseInfo(KDbQuerySchema *query) : d(new Private) { d->querySchema = query; } KDbParseInfo::~KDbParseInfo() { delete d; } QList KDbParseInfo::tablesAndAliasesForName(const QString &tableOrAliasName) const { QList result; const QList *list = d->repeatedTablesAndAliases.value(tableOrAliasName); if (list) { result = *list; } if (result.isEmpty()) { int position = d->querySchema->tablePositionForAlias(tableOrAliasName); if (position == -1) { position = d->querySchema->tablePosition(tableOrAliasName); if (position != -1) { result.append(position); } } else { result.append(position); } } return result; } KDbQuerySchema* KDbParseInfo::querySchema() const { return d->querySchema; } QString KDbParseInfo::errorMessage() const { return d->errorMessage; } QString KDbParseInfo::errorDescription() const { return d->errorDescription; } void KDbParseInfo::setErrorMessage(const QString &message) { d->errorMessage = message; } void KDbParseInfo::setErrorDescription(const QString &description) { d->errorDescription = description; } //------------------------------------- KDbParseInfoInternal::KDbParseInfoInternal(KDbQuerySchema *query) : KDbParseInfo(query) { } KDbParseInfoInternal::~KDbParseInfoInternal() { } void KDbParseInfoInternal::appendPositionForTableOrAliasName(const QString &tableOrAliasName, int pos) { QList *list = d->repeatedTablesAndAliases.value(tableOrAliasName); if (!list) { list = new QList(); d->repeatedTablesAndAliases.insert(tableOrAliasName, list); } list->append(pos); } //------------------------------------- extern int yyparse(); extern void tokenize(const char *data); void yyerror(const char *str) { kdbDebug() << "error: " << str; kdbDebug() << "at character " << globalCurrentPos << " near tooken " << globalToken; KDbParserPrivate::get(globalParser)->setStatementType(KDbParser::NoType); const bool otherError = (qstrnicmp(str, "other error", 11) == 0); const bool syntaxError = qstrnicmp(str, "syntax error", 12) == 0; - if (( globalParser->error().type().isEmpty() && (str == 0 || strlen(str) == 0 || syntaxError)) + if (( globalParser->error().type().isEmpty() && (str == nullptr || strlen(str) == 0 || syntaxError)) || otherError) { kdbDebug() << globalParser->statement(); QString ptrline(globalCurrentPos, QLatin1Char(' ')); ptrline += QLatin1String("^"); kdbDebug() << ptrline; #if 0 //lexer may add error messages QString lexerErr = globalParser->error().message(); QString errtypestr = QLatin1String(str); if (lexerErr.isEmpty()) { if (errtypestr.startsWith(QString::fromLatin1("parse error, expecting `IDENTIFIER'"))) { lexerErr = KDbParser::tr("identifier was expected"); } } #endif //! @todo exact invalid expression can be selected in the editor, based on KDbParseInfo data if (!otherError) { const bool isKDbSQLKeyword = KDb::isKDbSQLKeyword(globalToken); if (isKDbSQLKeyword || syntaxError) { if (isKDbSQLKeyword) { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"), KDbParser::tr("\"%1\" is a reserved keyword.").arg(QLatin1String(globalToken)), globalToken, globalCurrentPos)); } else { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"), KDbParser::tr("Syntax error."), globalToken, globalCurrentPos)); } } else { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Error"), KDbParser::tr("Error near \"%1\".").arg(QLatin1String(globalToken)), globalToken, globalCurrentPos)); } } } } void setError(const QString& errName, const QString& errDesc) { KDbParserPrivate::get(globalParser)->setError(KDbParserError(errName, errDesc, globalToken, globalCurrentPos)); yyerror(qPrintable(errName)); } void setError(const QString& errDesc) { setError(KDbParser::tr("Other error"), errDesc); } /* this is better than assert() */ #define IMPL_ERROR(errmsg) setError(KDbParser::tr("Implementation error"), QLatin1String(errmsg)) //! @internal Parses @a data for parser @a p //! @todo Make it REENTRANT bool parseData(KDbParser *p, const KDbEscapedString &sql) { globalParser = p; globalParser->reset(); - globalField = 0; + globalField = nullptr; fieldList.clear(); if (sql.isEmpty()) { KDbParserError err(KDbParser::tr("Error"), KDbParser::tr("No query statement specified."), globalToken, globalCurrentPos); KDbParserPrivate::get(globalParser)->setError(err); yyerror(""); - globalParser = 0; + globalParser = nullptr; return false; } const char *data = sql.constData(); tokenize(data); if (!globalParser->error().type().isEmpty()) { - globalParser = 0; + globalParser = nullptr; return false; } bool ok = yyparse() == 0; if (ok && globalCurrentPos < sql.length()) { kdbDebug() << "Parse error: tokens left" << "globalCurrentPos:" << globalCurrentPos << "sql.length():" << sql.length() << "globalToken:" << QString::fromUtf8(globalToken); KDbParserError err(KDbParser::tr("Error"), KDbParser::tr("Unexpected character."), globalToken, globalCurrentPos); KDbParserPrivate::get(globalParser)->setError(err); yyerror(""); ok = false; } if (ok && globalParser->statementType() == KDbParser::Select) { kdbDebug() << "parseData(): ok"; // kdbDebug() << "parseData(): " << tableDict.count() << " loaded tables"; /* KDbTableSchema *ts; for(QDictIterator it(tableDict); KDbTableSchema *s = tableList.first(); s; s = tableList.next()) { kdbDebug() << " " << s->name(); }*/ } else { ok = false; } yylex_destroy(); - globalParser = 0; + globalParser = nullptr; return ok; } /*! Adds @a columnExpr to @a parseInfo The column can be in a form table.field, tableAlias.field or field. @return true on success. On error message in globalParser object is updated. */ bool addColumn(KDbParseInfo *parseInfo, const KDbExpression &columnExpr) { if (!KDbExpression(columnExpr).validate(parseInfo)) { // (KDbExpression(columnExpr) used to avoid constness problem) setError(parseInfo->errorMessage(), parseInfo->errorDescription()); return false; } const KDbVariableExpression v_e(columnExpr.toVariable()); if (columnExpr.expressionClass() == KDb::VariableExpression && !v_e.isNull()) { //it's a variable: if (v_e.name() == QLatin1String("*")) {//all tables asterisk if (parseInfo->querySchema()->tables()->isEmpty()) { setError(KDbParser::tr("\"*\" could not be used if no tables are specified.")); return false; } KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema()); if (!parseInfo->querySchema()->addAsterisk(a)) { delete a; setError(KDbParser::tr("\"*\" could not be added.")); return false; } } else if (v_e.tableForQueryAsterisk()) {//one-table asterisk KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema(), *v_e.tableForQueryAsterisk()); if (!parseInfo->querySchema()->addAsterisk(a)) { delete a; setError(KDbParser::tr("\".*\" could not be added.")); return false; } } else if (v_e.field()) {//"table.field" or "field" (bound to a table or not) if (!parseInfo->querySchema()->addField(v_e.field(), v_e.tablePositionForField())) { setError(KDbParser::tr("Could not add binding to a field.")); return false; } } else { IMPL_ERROR("addColumn(): unknown case!"); return false; } return true; } //it's complex expression return parseInfo->querySchema()->addExpression(columnExpr); } KDbQuerySchema* buildSelectQuery( KDbQuerySchema* querySchema, KDbNArgExpression* _colViews, KDbNArgExpression* _tablesList, SelectOptionsInternal* options) { KDbParseInfoInternal parseInfo(querySchema); // remove from heap (using heap was requered because parser uses union) KDbNArgExpression colViews; if (_colViews) { colViews = *_colViews; delete _colViews; } KDbNArgExpression tablesList; if (_tablesList) { tablesList = *_tablesList; delete _tablesList; } QScopedPointer optionsPtr(options); QScopedPointer querySchemaPtr(querySchema); // destroy query on any error //-------tables list int columnNum = 0; /*! @todo use this later if there are columns that use database fields, e.g. "SELECT 1 from table1 t, table2 t") is ok however. */ //used to collect information about first repeated table name or alias: if (!tablesList.isEmpty()) { for (int i = 0; i < tablesList.argCount(); i++, columnNum++) { KDbExpression e(tablesList.arg(i)); KDbVariableExpression t_e; QString aliasString; if (e.expressionClass() == KDb::SpecialBinaryExpression) { KDbBinaryExpression t_with_alias = e.toBinary(); Q_ASSERT(e.isBinary()); Q_ASSERT(t_with_alias.left().expressionClass() == KDb::VariableExpression); Q_ASSERT(t_with_alias.right().expressionClass() == KDb::VariableExpression && (t_with_alias.token() == KDbToken::AS || t_with_alias.token() == KDbToken::AS_EMPTY)); t_e = t_with_alias.left().toVariable(); aliasString = t_with_alias.right().toVariable().name(); } else { t_e = e.toVariable(); } Q_ASSERT(t_e.isVariable()); QString tname = t_e.name(); KDbTableSchema *s = globalParser->connection()->tableSchema(tname); if (!s) { setError(KDbParser::tr("Table \"%1\" does not exist.").arg(tname)); - return 0; + return nullptr; } QString tableOrAliasName = KDb::iifNotEmpty(aliasString, tname); if (!aliasString.isEmpty()) { // kdbDebug() << "- add alias for table: " << aliasString; } // 1. collect information about first repeated table name or alias // (potential ambiguity) parseInfo.appendPositionForTableOrAliasName(tableOrAliasName, i); // kdbDebug() << "addTable: " << tname; querySchema->addTable(s, aliasString); } } /* set parent table if there's only one */ if (querySchema->tables()->count() == 1) querySchema->setMasterTable(querySchema->tables()->first()); //-------add fields if (!colViews.isEmpty()) { columnNum = 0; bool containsAsteriskColumn = false; // used to check duplicated asterisks (disallowed) for (int i = 0; i < colViews.argCount(); i++, columnNum++) { const KDbExpression e(colViews.arg(i)); KDbExpression columnExpr(e); KDbVariableExpression aliasVariable; if (e.expressionClass() == KDb::SpecialBinaryExpression && e.isBinary() && (e.token() == KDbToken::AS || e.token() == KDbToken::AS_EMPTY)) { //KDb::SpecialBinaryExpression: with alias columnExpr = e.toBinary().left(); aliasVariable = e.toBinary().right().toVariable(); if (aliasVariable.isNull()) { setError(KDbParser::tr("Invalid alias definition for column \"%1\".") - .arg(columnExpr.toString(0).toString())); //ok? + .arg(columnExpr.toString(nullptr).toString())); //ok? break; } } const KDb::ExpressionClass c = columnExpr.expressionClass(); const bool isExpressionField = c == KDb::ConstExpression || c == KDb::UnaryExpression || c == KDb::ArithmeticExpression || c == KDb::LogicalExpression || c == KDb::RelationalExpression || c == KDb::FunctionExpression || c == KDb::AggregationExpression; if (c == KDb::VariableExpression) { if (columnExpr.toVariable().name() == QLatin1String("*")) { if (containsAsteriskColumn) { setError(KDbParser::tr("More than one asterisk \"*\" is not allowed.")); - return 0; + return nullptr; } else { containsAsteriskColumn = true; } } // addColumn() will handle this } else if (isExpressionField) { //expression object will be reused, take, will be owned, do not destroy // kdbDebug() << colViews->list.count() << " " << it.current()->debugString(); //! @todo IMPORTANT: it.remove(); } else if (aliasVariable.isNull()) { setError(KDbParser::tr("Invalid \"%1\" column definition.") - .arg(e.toString(0).toString())); //ok? + .arg(e.toString(nullptr).toString())); //ok? break; } else { //take first (left) argument of the special binary expr, will be owned, do not destroy e.toBinary().setLeft(KDbExpression()); } if (!addColumn(&parseInfo, columnExpr)) { break; } if (!aliasVariable.isNull()) { // kdbDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " // << columnNum; querySchema->setColumnAlias(columnNum, aliasVariable.name()); } } // for if (!globalParser->error().message().isEmpty()) { // we could not return earlier (inside the loop) // because we want run CLEANUP what could crash QMutableListIterator. - return 0; + return nullptr; } } //----- SELECT options if (options) { //----- WHERE expr. if (!options->whereExpr.isNull()) { if (!options->whereExpr.validate(&parseInfo)) { setError(parseInfo.errorMessage(), parseInfo.errorDescription()); - return 0; + return nullptr; } KDbQuerySchema::Private::setWhereExpressionInternal(querySchema, options->whereExpr); } //----- ORDER BY if (options->orderByColumns) { KDbOrderByColumnList *orderByColumnList = querySchema->orderByColumnList(); int count = options->orderByColumns->count(); QList::ConstIterator it(options->orderByColumns->constEnd()); --it; for (;count > 0; --it, --count) /*opposite direction due to parser specifics*/ { //first, try to find a column name or alias (outside of asterisks) KDbQueryColumnInfo *columnInfo = querySchema->columnInfo((*it).aliasOrName, false/*outside of asterisks*/); if (columnInfo) { orderByColumnList->appendColumn(columnInfo, (*it).order); } else { //failed, try to find a field name within all the tables if ((*it).columnNumber != -1) { if (!orderByColumnList->appendColumn(querySchema, (*it).order, (*it).columnNumber - 1)) { setError(KDbParser::tr("Could not define sorting. Column at " "position %1 does not exist.") .arg((*it).columnNumber)); - return 0; + return nullptr; } } else { KDbField * f = querySchema->findTableField((*it).aliasOrName); if (!f) { setError(KDbParser::tr("Could not define sorting. " "Column name or alias \"%1\" does not exist.") .arg((*it).aliasOrName)); - return 0; + return nullptr; } orderByColumnList->appendField(f, (*it).order); } } } } } // kdbDebug() << "Select ColViews=" << (colViews ? colViews->debugString() : QString()) // << " Tables=" << (tablesList ? tablesList->debugString() : QString()s); return querySchemaPtr.take(); } diff --git a/src/parser/KDbParser_p.h b/src/parser/KDbParser_p.h index 632e9e5a..78ec5fe2 100644 --- a/src/parser/KDbParser_p.h +++ b/src/parser/KDbParser_p.h @@ -1,171 +1,171 @@ /* This file is part of the KDE project Copyright (C) 2004-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_PARSER_P_H #define KDB_PARSER_P_H #include "KDbParser.h" #include "KDbSqlTypes.h" #include #include #include #include class KDbQuerySchema; class KDbTableSchema; class KDbConnection; class KDbExpression; //! @internal class KDbParserPrivate { public: KDbParserPrivate(); ~KDbParserPrivate(); void reset(); //! For use by parser's low level C functions inline static KDbParserPrivate* get(KDbParser *parser) { return parser->d; } /** * Sets the type of statement. */ void setStatementType(KDbParser::StatementType type); /** * Sets @a table schema object. */ void setTableSchema(KDbTableSchema *table); /** * Sets @a query schema object. */ //! @todo Add other query types void setQuerySchema(KDbQuerySchema *query); /** * Sets a error. */ void setError(const KDbParserError &err); friend class KDbParser; private: KDbParser::StatementType statementType; KDbTableSchema *table; KDbQuerySchema *query; KDbConnection *connection; KDbEscapedString sql; KDbParserError error; bool initialized; Q_DISABLE_COPY(KDbParserPrivate) }; /*! Info used on parsing. */ class KDB_TESTING_EXPORT KDbParseInfo { public: ~KDbParseInfo(); //! @return positions of tables/aliases having the same name @a tableOrAliasName. //! First tries to use information provided by appendPositionForTableOrAliasName(), //! then information from the query schema. QList tablesAndAliasesForName(const QString &tableOrAliasName) const; //! @return query schema for this parsing KDbQuerySchema* querySchema() const; //! @return error message for the parsing process QString errorMessage() const; //! Sets error message for the parsing process to @a message void setErrorMessage(const QString &message); //! @return detailed error description for the parsing process QString errorDescription() const; //! Sets detailed error description for the parsing process to @a description void setErrorDescription(const QString &description); protected: //! Constructs parse info structure for query @a query. explicit KDbParseInfo(KDbQuerySchema *query); class Private; Private * const d; private: Q_DISABLE_COPY(KDbParseInfo) }; class Q_DECL_HIDDEN KDbParseInfo::Private { public: Private() {} ~Private() { qDeleteAll(repeatedTablesAndAliases); } //! collects positions of tables/aliases with the same names QHash< QString, QList* > repeatedTablesAndAliases; QString errorMessage, errorDescription; // helpers KDbQuerySchema *querySchema; private: Q_DISABLE_COPY(Private) }; /*! Internal info used on parsing (writable). */ class KDB_TESTING_EXPORT KDbParseInfoInternal : public KDbParseInfo { public: //! Constructs parse info structure for query @a query. explicit KDbParseInfoInternal(KDbQuerySchema *query); ~KDbParseInfoInternal(); //! Appends position @a pos for table or alias @a tableOrAliasName. void appendPositionForTableOrAliasName(const QString &tableOrAliasName, int pos); private: Q_DISABLE_COPY(KDbParseInfoInternal) }; KDB_TESTING_EXPORT const char* g_tokenName(unsigned int offset); void yyerror(const char *str); void setError(const QString& errName, const QString& errDesc); void setError(const QString& errDesc); bool addColumn(KDbParseInfo* parseInfo, KDbExpression* columnExpr); KDbQuerySchema* buildSelectQuery( KDbQuerySchema* querySchema, KDbNArgExpression* colViews, - KDbNArgExpression* tablesList = 0, SelectOptionsInternal * options = 0); + KDbNArgExpression* tablesList = nullptr, SelectOptionsInternal * options = nullptr); extern KDbParser *globalParser; extern KDbField *globalField; #endif diff --git a/src/parser/KDbSqlParser.y b/src/parser/KDbSqlParser.y index 5a08fbd8..586d437d 100644 --- a/src/parser/KDbSqlParser.y +++ b/src/parser/KDbSqlParser.y @@ -1,1444 +1,1444 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // To keep binary compatibility, do not reorder tokens! Add new only at the end. %token SQL_TYPE %token AS %token AS_EMPTY /* used for aliases with skipped AS keyword */ %token ASC %token AUTO_INCREMENT %token BIT %token BITWISE_SHIFT_LEFT %token BITWISE_SHIFT_RIGHT %token BY %token CHARACTER_STRING_LITERAL %token CONCATENATION /* || */ %token CREATE %token DESC %token DISTINCT %token DOUBLE_QUOTED_STRING %token FROM %token JOIN %token KEY %token LEFT %token LESS_OR_EQUAL %token GREATER_OR_EQUAL %token SQL_NULL %token SQL_IS %token SQL_IS_NULL /*helper */ %token SQL_IS_NOT_NULL /*helper */ %token ORDER %token PRIMARY %token SELECT %token INTEGER_CONST %token REAL_CONST %token RIGHT %token SQL_ON %token DATE_CONST %token DATETIME_CONST %token TIME_CONST %token TABLE %token IDENTIFIER %token IDENTIFIER_DOT_ASTERISK %token QUERY_PARAMETER %token VARCHAR %token WHERE %token SQL %token SQL_TRUE %token SQL_FALSE %token UNION %token SCAN_ERROR //%token SQL_ABS //%token ACOS //%token AMPERSAND //%token SQL_ABSOLUTE //%token ADA //%token ADD //%token ADD_DAYS //%token ADD_HOURS //%token ADD_MINUTES //%token ADD_MONTHS //%token ADD_SECONDS //%token ADD_YEARS //%token ALL //%token ALLOCATE //%token ALTER %token AND //%token ANY //%token ARE //%token ASIN //%token ASCII //%token ASSERTION //%token ATAN //%token ATAN2 //%token AUTHORIZATION //%token AVG //%token BEFORE %token BETWEEN %token NOT_BETWEEN //%token SQL_BEGIN //%token BIGINT //%token BINARY //%token BIT_LENGTH //%token BREAK //%token CASCADE //%token CASCADED //%token CASE //%token CAST //%token CATALOG //%token CEILING //%token CENTER //%token SQL_CHAR //%token CHAR_LENGTH //%token CHECK //%token CLOSE //%token COALESCE //%token COBOL //%token COLLATE //%token COLLATION //%token COLUMN //%token COMMIT //%token COMPUTE //%token CONCAT //%token CONNECT //%token CONNECTION //%token CONSTRAINT //%token CONSTRAINTS //%token CONTINUE //%token CONVERT //%token CORRESPONDING //%token COS //%token COT //%token COUNT //%token CURDATE //%token CURRENT //%token CURRENT_DATE //%token CURRENT_TIME //%token CURRENT_TIMESTAMP //%token CURTIME //%token CURSOR //%token DATABASE //%token SQL_DATE //%token DATE_FORMAT //%token DATE_REMAINDER //%token DATE_VALUE //%token DAY //%token DAYOFMONTH //%token DAYOFWEEK //%token DAYOFYEAR //%token DAYS_BETWEEN //%token DEALLOCATE //%token DEC //%token DECLARE //%token DEFAULT //%token DEFERRABLE //%token DEFERRED //%token SQL_DELETE //%token DESCRIBE //%token DESCRIPTOR //%token DIAGNOSTICS //%token DICTIONARY //%token DIRECTORY //%token DISCONNECT //%token DISPLACEMENT //%token DOMAIN_TOKEN //%token SQL_DOUBLE //%token DROP //%token ELSE //%token END //%token END_EXEC //%token ESCAPE %token EXCEPT //%token SQL_EXCEPTION //%token EXEC //%token EXECUTE //%token EXISTS //%token EXP //%token EXPONENT //%token EXTERNAL //%token EXTRACT //%token FETCH //%token FIRST //%token SQL_FLOAT //%token FLOOR //%token FN //%token FOR //%token FOREIGN //%token FORTRAN //%token FOUND //%token FOUR_DIGITS //%token FULL //%token GET //%token GLOBAL //%token GO //%token GOTO //%token GRANT //conflict %token GROUP //%token HAVING //%token HOUR //%token HOURS_BETWEEN //%token IDENTITY //%token IFNULL //%token SQL_IGNORE //%token IMMEDIATE //%token INCLUDE //%token INDEX //%token INDICATOR //%token INITIALLY //%token INNER //%token SQL_INPUT %token SQL_IN //%token INSENSITIVE //%token INSERT //%token INTEGER %token INTERSECT //%token INTERVAL //%token INTO //%token IS //%token ISOLATION //%token JUSTIFY //%token LANGUAGE //%token LAST //%token LCASE //%token LENGTH //%token LEVEL %token LIKE %token ILIKE %token NOT_LIKE //%token LINE_WIDTH //%token LOCAL //%token LOCATE //%token LOG //%token SQL_LONG //%token LOWER //%token LTRIM //%token LTRIP //%token MATCH //%token SQL_MAX //%token MICROSOFT //%token SQL_MIN //%token MINUTE //%token MINUTES_BETWEEN //%token MOD //%token MODIFY //%token MODULE //%token MONTH //%token MONTHS_BETWEEN //%token MUMPS //%token NAMES //%token NATIONAL //%token NCHAR //%token NEXT //%token NODUP //%token NONE %token NOT %token NOT_EQUAL %token NOT_EQUAL2 //%token NOW //%token NULLIF //%token NUMERIC //%token OCTET_LENGTH //%token ODBC //%token OF //%token SQL_OFF //%token ONLY //%token OPEN //%token OPTION //%token OUTER //%token OUTPUT //%token OVERLAPS //%token PAGE //%token PARTIAL //%token SQL_PASCAL //%token PERSISTENT //%token CQL_PI %token OR //%token PLI //%token POSITION //%token PRECISION //%token PREPARE //%token PRESERVE //%token PRIOR //%token PRIVILEGES //%token PROCEDURE //%token PRODUCT //%token PUBLIC //%token QUARTER //%token QUIT //%token RAND //%token READ_ONLY //%token REAL //%token REFERENCES //%token REPEAT //%token REPLACE //%token RESTRICT //%token REVOKE //%token ROLLBACK //%token ROWS //%token RPAD //%token RTRIM //%token SCHEMA //%token SCREEN_WIDTH //%token SCROLL //%token SECOND //%token SECONDS_BETWEEN //%token SEQUENCE //%token SETOPT //%token SET //%token SHOWOPT //%token SIGN %token SIMILAR_TO %token NOT_SIMILAR_TO //%token SIN //%token SQL_SIZE //%token SMALLINT //%token SOME //%token SPACE //%token SQLCA //%token SQLCODE //%token SQLERROR //%token SQLSTATE //%token SQLWARNING //%token SQRT //%token STDEV //%token SUBSTRING //%token SUM //%token SYSDATE //%token SYSDATE_FORMAT //%token SYSTEM //%token TAN //%token TEMPORARY //%token THEN //%token THREE_DIGITS //%token TIME //%token TIMESTAMP //%token TIMEZONE_HOUR //%token TIMEZONE_MINUTE //%token TINYINT //%token TO //%token TO_CHAR //%token TO_DATE //%token TRANSACTION //%token TRANSLATE //%token TRANSLATION //%token TRUNCATE //%token GENERAL_TITLE //%token TWO_DIGITS //%token UCASE //%token UNIQUE //%token SQL_UNKNOWN //%token UNSIGNED_INTEGER //%token UPDATE //%token UPPER //%token USAGE //%token USER //%token ERROR_DIGIT_BEFORE_IDENTIFIER //%token USING //%token VALUE //%token VALUES //%token VARBINARY //%token VARYING //%token VENDOR //%token VIEW //%token WEEK //%token WHEN //%token WHENEVER //%token WHERE_CURRENT_OF //%token WITH //%token WORD_WRAPPED //%token WORK //%token WRAPPED %token XOR //%token YEAR //%token YEARS_BETWEEN %type IDENTIFIER %type IDENTIFIER_DOT_ASTERISK %type QUERY_PARAMETER %type CHARACTER_STRING_LITERAL %type DOUBLE_QUOTED_STRING /* %type ColExpression %type ColView */ %type ColExpression %type ColWildCard //%type ColView %type ColItem %type ColViews %type aExpr %type aExpr2 %type aExpr3 %type aExpr4 %type aExpr5 %type aExpr6 %type aExpr7 %type aExpr8 %type aExpr9 %type aExpr10 %type aExprList %type aExprList2 %type WhereClause %type OrderByClause %type OrderByOption %type OrderByColumnId %type SelectOptions %type FlatTable %type Tables %type FlatTableList %type SelectStatement %type Select /*todo : list*/ %type StatementList /*todo: not onlu select*/ %type Statement %type SQL_TYPE %type INTEGER_CONST %type REAL_CONST /*%type SIGNED_INTEGER */ %{ #include #include #include #include #include #include //! @todo OK? #ifdef Q_OS_WIN //workaround for bug on msvc # undef LLONG_MIN #endif #ifndef LLONG_MAX # define LLONG_MAX 0x7fffffffffffffffLL #endif #ifndef LLONG_MIN # define LLONG_MIN 0x8000000000000000LL #endif #ifndef LLONG_MAX # define ULLONG_MAX 0xffffffffffffffffLL #endif #ifdef _WIN32 # include #endif #include #include #include #include "KDbConnection.h" #include "KDbExpression.h" #include "KDbField.h" #include "KDbOrderByColumn.h" #include "KDbParser.h" #include "KDbParser_p.h" #include "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbSqlTypes.h" #include "KDbTableSchema.h" #include "kdb_debug.h" struct OrderByColumnInternal; #ifdef Q_OS_SOLARIS #include #endif QDebug operator<<(QDebug dbg, const KDbExpressionPtr& expr) { dbg.nospace() << expr.e; return dbg.space(); } int yylex(); #define YY_NO_UNPUT #define YYSTACK_USE_ALLOCA 1 #define YYMAXDEPTH 255 extern "C" { int yywrap() { return 1; } } %} %union { QString* stringValue; QByteArray* binaryValue; qint64 integerValue; bool booleanValue; KDbOrderByColumn::SortOrder sortOrderValue; KDbField::Type colType; KDbField *field; KDbExpression *expr; KDbNArgExpression *exprList; KDbConstExpression *constExpression; KDbQuerySchema *querySchema; SelectOptionsInternal *selectOptions; QList *orderByColumns; QVariant *variantValue; } /* precedence: lowest to highest */ //%nonassoc SIMILAR //%nonassoc ESCAPE //%nonassoc OVERLAPS //%nonassoc IN_P //%left POSTFIXOP // dummy for postfix Op rules //%left Op OPERATOR // multi-character ops and user-defined operators //%nonassoc NOTNULL //%nonassoc ISNULL //%nonassoc IS // sets precedence for IS NULL, etc //%nonassoc NULL_P //%nonassoc TRUE_P //%nonassoc FALSE_P %token UMINUS // <-- To keep binary compatibility insert new tokens here. /* * These might seem to be low-precedence, but actually they are not part * of the arithmetic hierarchy at all in their use as JOIN operators. * We make them high-precedence to support their use as function names. * They wouldn't be given a precedence at all, were it not that we need * left-associativity among the JOIN rules themselves. */ /* %left JOIN %left UNIONJOIN %left CROSS %left LEFT %left FULL %left RIGHT %left INNER_P %left NATURAL */ %% TopLevelStatement : StatementList { //todo: multiple statements //todo: not only "select" statements KDbParserPrivate::get(globalParser)->setStatementType(KDbParser::Select); KDbParserPrivate::get(globalParser)->setQuerySchema($1); } ; StatementList: Statement ';' StatementList { //todo: multiple statements } | Statement | Statement ';' { $$ = $1; } ; /* Statement CreateTableStatement { YYACCEPT; } | Statement SelectStatement { } */ Statement : /*CreateTableStatement { YYACCEPT; } | */ SelectStatement { $$ = $1; } ; /*CreateTableStatement : CREATE TABLE IDENTIFIER { globalParser->setStatementType(KDbParser::CreateTable); globalParser->createTable($3->toLatin1()); delete $3; } '(' ColDefs ')' ; ColDefs: ColDefs ',' ColDef|ColDef { } ; ColDef: IDENTIFIER ColType { kdbDebug() << "adding field " << *$1; globalField->setName(*$1); globalParser->table()->addField(globalField); - globalField = 0; + globalField = nullptr; delete $1; } | IDENTIFIER ColType ColKeys { kdbDebug() << "adding field " << *$1; globalField->setName(*$1); delete $1; globalParser->table()->addField(globalField); // if(globalField->isPrimaryKey()) // globalParser->table()->addPrimaryKey(globalField->name()); // delete globalField; -// globalField = 0; +// globalField = nullptr; } ; ColKeys: ColKeys ColKey|ColKey { } ; ColKey: PRIMARY KEY { globalField->setPrimaryKey(true); kdbDebug() << "primary"; } | NOT SQL_NULL { globalField->setNotNull(true); kdbDebug() << "not_null"; } | AUTO_INCREMENT { globalField->setAutoIncrement(true); kdbDebug() << "ainc"; } ; ColType: SQL_TYPE { globalField = new KDbField(); globalField->setType($1); } | SQL_TYPE '(' INTEGER_CONST ')' { kdbDebug() << "sql + length"; globalField = new KDbField(); globalField->setPrecision($3); globalField->setType($1); } | VARCHAR '(' INTEGER_CONST ')' { globalField = new KDbField(); globalField->setPrecision($3); globalField->setType(KDbField::Text); } | %empty { // SQLITE compatibillity globalField = new KDbField(); globalField->setType(KDbField::InvalidType); } ;*/ SelectStatement: Select { kdbDebug() << "Select"; if (!($$ = buildSelectQuery( $1, 0 ))) return 0; } | Select ColViews { kdbDebug() << "Select ColViews=" << *$2; if (!($$ = buildSelectQuery( $1, $2 ))) return 0; } | Select ColViews Tables { if (!($$ = buildSelectQuery( $1, $2, $3 ))) return 0; } | Select Tables { kdbDebug() << "Select ColViews Tables"; if (!($$ = buildSelectQuery( $1, 0, $2 ))) return 0; } | Select ColViews SelectOptions { kdbDebug() << "Select ColViews Conditions"; if (!($$ = buildSelectQuery( $1, $2, 0, $3 ))) return 0; } | Select ColViews Tables SelectOptions { kdbDebug() << "Select ColViews Tables SelectOptions"; if (!($$ = buildSelectQuery( $1, $2, $3, $4 ))) return 0; } ; Select: SELECT { kdbDebug() << "SELECT"; $$ = KDbQuerySchema::Private::createQuery(globalParser->connection()); } ; SelectOptions: /* todo: more options (having, group by, limit...) */ WhereClause { kdbDebug() << "WhereClause"; $$ = new SelectOptionsInternal; $$->whereExpr = *$1; delete $1; } | ORDER BY OrderByClause { kdbDebug() << "OrderByClause"; $$ = new SelectOptionsInternal; $$->orderByColumns = $3; } | WhereClause ORDER BY OrderByClause { kdbDebug() << "WhereClause ORDER BY OrderByClause"; $$ = new SelectOptionsInternal; $$->whereExpr = *$1; delete $1; $$->orderByColumns = $4; } | ORDER BY OrderByClause WhereClause { kdbDebug() << "OrderByClause WhereClause"; $$ = new SelectOptionsInternal; $$->whereExpr = *$4; delete $4; $$->orderByColumns = $3; } ; WhereClause: WHERE aExpr { $$ = $2; } ; /* todo: support "ORDER BY NULL" as described here http://dev.mysql.com/doc/refman/5.1/en/select.html */ /* todo: accept expr and position as well */ OrderByClause: OrderByColumnId { kdbDebug() << "ORDER BY IDENTIFIER"; $$ = new QList; OrderByColumnInternal orderByColumn; orderByColumn.setColumnByNameOrNumber( *$1 ); $$->append( orderByColumn ); delete $1; } | OrderByColumnId OrderByOption { kdbDebug() << "ORDER BY IDENTIFIER OrderByOption"; $$ = new QList; OrderByColumnInternal orderByColumn; orderByColumn.setColumnByNameOrNumber( *$1 ); orderByColumn.order = $2; $$->append( orderByColumn ); delete $1; } | OrderByColumnId ',' OrderByClause { $$ = $3; OrderByColumnInternal orderByColumn; orderByColumn.setColumnByNameOrNumber( *$1 ); $$->append( orderByColumn ); delete $1; } | OrderByColumnId OrderByOption ',' OrderByClause { $$ = $4; OrderByColumnInternal orderByColumn; orderByColumn.setColumnByNameOrNumber( *$1 ); orderByColumn.order = $2; $$->append( orderByColumn ); delete $1; } ; OrderByColumnId: IDENTIFIER { $$ = new QVariant( *$1 ); kdbDebug() << "OrderByColumnId: " << *$$; delete $1; } | IDENTIFIER '.' IDENTIFIER { $$ = new QVariant( *$1 + QLatin1Char('.') + *$3 ); kdbDebug() << "OrderByColumnId: " << *$$; delete $1; delete $3; } | INTEGER_CONST { $$ = new QVariant($1); kdbDebug() << "OrderByColumnId: " << *$$; } OrderByOption: ASC { $$ = KDbOrderByColumn::SortOrder::Ascending; } | DESC { $$ = KDbOrderByColumn::SortOrder::Descending; } ; aExpr: aExpr2 ; /* --- binary logical --- */ aExpr2: aExpr3 AND aExpr2 { // kdbDebug() << "AND " << $3.debugString(); $$ = new KDbBinaryExpression(*$1, KDbToken::AND, *$3); delete $1; delete $3; } | aExpr3 OR aExpr2 { $$ = new KDbBinaryExpression(*$1, KDbToken::OR, *$3); delete $1; delete $3; } | aExpr3 XOR aExpr2 { $$ = new KDbBinaryExpression(*$1, KDbToken::XOR, *$3); delete $1; delete $3; } | aExpr3 ; /* relational op precedence */ aExpr3: aExpr4 '>' %prec GREATER_OR_EQUAL aExpr3 { $$ = new KDbBinaryExpression(*$1, '>', *$3); delete $1; delete $3; } | aExpr4 GREATER_OR_EQUAL aExpr3 { $$ = new KDbBinaryExpression(*$1, KDbToken::GREATER_OR_EQUAL, *$3); delete $1; delete $3; } | aExpr4 '<' %prec LESS_OR_EQUAL aExpr3 { $$ = new KDbBinaryExpression(*$1, '<', *$3); delete $1; delete $3; } | aExpr4 LESS_OR_EQUAL aExpr3 { $$ = new KDbBinaryExpression(*$1, KDbToken::LESS_OR_EQUAL, *$3); delete $1; delete $3; } | aExpr4 '=' aExpr3 { $$ = new KDbBinaryExpression(*$1, '=', *$3); delete $1; delete $3; } | aExpr4 ; /* relational (equality) op precedence */ aExpr4: aExpr5 NOT_EQUAL aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_EQUAL, *$3); delete $1; delete $3; } | aExpr5 NOT_EQUAL2 aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_EQUAL2, *$3); delete $1; delete $3; } | aExpr5 LIKE aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::LIKE, *$3); delete $1; delete $3; } | aExpr5 NOT_LIKE aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_LIKE, *$3); delete $1; delete $3; } | aExpr5 SQL_IN aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::SQL_IN, *$3); delete $1; delete $3; } | aExpr5 SIMILAR_TO aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::SIMILAR_TO, *$3); delete $1; delete $3; } | aExpr5 NOT_SIMILAR_TO aExpr4 { $$ = new KDbBinaryExpression(*$1, KDbToken::NOT_SIMILAR_TO, *$3); delete $1; delete $3; } | aExpr5 BETWEEN aExpr4 AND aExpr4 { $$ = new KDbNArgExpression(KDb::RelationalExpression, KDbToken::BETWEEN_AND); $$->toNArg().append( *$1 ); $$->toNArg().append( *$3 ); $$->toNArg().append( *$5 ); delete $1; delete $3; delete $5; } | aExpr5 NOT_BETWEEN aExpr4 AND aExpr4 { $$ = new KDbNArgExpression(KDb::RelationalExpression, KDbToken::NOT_BETWEEN_AND); $$->toNArg().append( *$1 ); $$->toNArg().append( *$3 ); $$->toNArg().append( *$5 ); delete $1; delete $3; delete $5; } | aExpr5 ; /* --- unary logical right --- */ aExpr5: aExpr5 SQL_IS_NULL { $$ = new KDbUnaryExpression( KDbToken::SQL_IS_NULL, *$1 ); delete $1; } | aExpr5 SQL_IS_NOT_NULL { $$ = new KDbUnaryExpression( KDbToken::SQL_IS_NOT_NULL, *$1 ); delete $1; } | aExpr6 ; /* arithm. lowest precedence */ aExpr6: aExpr7 BITWISE_SHIFT_LEFT aExpr6 { $$ = new KDbBinaryExpression(*$1, KDbToken::BITWISE_SHIFT_LEFT, *$3); delete $1; delete $3; } | aExpr7 BITWISE_SHIFT_RIGHT aExpr6 { $$ = new KDbBinaryExpression(*$1, KDbToken::BITWISE_SHIFT_RIGHT, *$3); delete $1; delete $3; } | aExpr7 ; /* arithm. lower precedence */ aExpr7: aExpr8 '+' aExpr7 { $$ = new KDbBinaryExpression(*$1, '+', *$3); delete $1; delete $3; } | aExpr8 CONCATENATION aExpr7 { $$ = new KDbBinaryExpression(*$1, KDbToken::CONCATENATION, *$3); delete $1; delete $3; } | aExpr8 '-' %prec UMINUS aExpr7 { $$ = new KDbBinaryExpression(*$1, '-', *$3); delete $1; delete $3; } | aExpr8 '&' aExpr7 { $$ = new KDbBinaryExpression(*$1, '&', *$3); delete $1; delete $3; } | aExpr8 '|' aExpr7 { $$ = new KDbBinaryExpression(*$1, '|', *$3); delete $1; delete $3; } | aExpr8 ; /* arithm. higher precedence */ aExpr8: aExpr9 '/' aExpr8 { $$ = new KDbBinaryExpression(*$1, '/', *$3); delete $1; delete $3; } | aExpr9 '*' aExpr8 { $$ = new KDbBinaryExpression(*$1, '*', *$3); delete $1; delete $3; } | aExpr9 '%' aExpr8 { $$ = new KDbBinaryExpression(*$1, '%', *$3); delete $1; delete $3; } | aExpr9 ; /* parenthesis, unary operators, and terminals precedence */ aExpr9: /* --- unary logical left --- */ '-' aExpr9 { $$ = new KDbUnaryExpression( '-', *$2 ); delete $2; } | '+' aExpr9 { $$ = new KDbUnaryExpression( '+', *$2 ); delete $2; } | '~' aExpr9 { $$ = new KDbUnaryExpression( '~', *$2 ); delete $2; } | NOT aExpr9 { $$ = new KDbUnaryExpression( KDbToken::NOT, *$2 ); delete $2; } | IDENTIFIER { $$ = new KDbVariableExpression( *$1 ); //! @todo simplify this later if that's 'only one field name' expression kdbDebug() << " + identifier: " << *$1; delete $1; } | QUERY_PARAMETER { $$ = new KDbQueryParameterExpression( *$1 ); kdbDebug() << " + query parameter:" << *$$; delete $1; } | IDENTIFIER aExprList { kdbDebug() << " + function:" << *$1 << "(" << *$2 << ")"; $$ = new KDbFunctionExpression(*$1, *$2); delete $1; delete $2; } /*! @todo shall we also support db name? */ | IDENTIFIER '.' IDENTIFIER { $$ = new KDbVariableExpression( *$1 + QLatin1Char('.') + *$3 ); kdbDebug() << " + identifier.identifier:" << *$1 << "." << *$3; delete $1; delete $3; } | SQL_NULL { $$ = new KDbConstExpression( KDbToken::SQL_NULL, QVariant() ); kdbDebug() << " + NULL"; // $$ = new KDbField(); //$$->setName(QString::null); } | SQL_TRUE { $$ = new KDbConstExpression( KDbToken::SQL_TRUE, true ); } | SQL_FALSE { $$ = new KDbConstExpression( KDbToken::SQL_FALSE, false ); } | CHARACTER_STRING_LITERAL { $$ = new KDbConstExpression( KDbToken::CHARACTER_STRING_LITERAL, *$1 ); kdbDebug() << " + constant " << $1; delete $1; } | INTEGER_CONST { QVariant val; if ($1 <= INT_MAX && $1 >= INT_MIN) val = (int)$1; else if ($1 <= UINT_MAX && $1 >= 0) val = (uint)$1; else if ($1 <= LLONG_MAX && $1 >= LLONG_MIN) val = (qint64)$1; // if ($1 < ULLONG_MAX) // val = (quint64)$1; //! @todo ok? $$ = new KDbConstExpression( KDbToken::INTEGER_CONST, val ); kdbDebug() << " + int constant: " << val.toString(); } | REAL_CONST { $$ = new KDbConstExpression( KDbToken::REAL_CONST, *$1 ); kdbDebug() << " + real constant: " << *$1; delete $1; } | aExpr10 ; aExpr10: '(' aExpr ')' { kdbDebug() << "(expr)"; $$ = new KDbUnaryExpression('(', *$2); delete $2; } ; aExprList: '(' aExprList2 ')' { $$ = $2; } | '(' ')' { $$ = new KDbNArgExpression(KDb::ArgumentListExpression, ','); } ; aExprList2: aExpr ',' aExprList2 { $$ = $3; $$->prepend( *$1 ); delete $1; } | aExpr { $$ = new KDbNArgExpression(KDb::ArgumentListExpression, ','); $$->append( *$1 ); delete $1; } ; Tables: FROM FlatTableList { $$ = $2; } /* | Tables LEFT JOIN IDENTIFIER SQL_ON ColExpression { kdbDebug() << "LEFT JOIN: '" << *$4 << "' ON " << $6; addTable($4->toQString()); delete $4; } | Tables LEFT OUTER JOIN IDENTIFIER SQL_ON ColExpression { kdbDebug() << "LEFT OUTER JOIN: '" << $5 << "' ON " << $7; addTable($5); } | Tables INNER JOIN IDENTIFIER SQL_ON ColExpression { kdbDebug() << "INNER JOIN: '" << *$4 << "' ON " << $6; addTable($4->toQString()); delete $4; } | Tables RIGHT JOIN IDENTIFIER SQL_ON ColExpression { kdbDebug() << "RIGHT JOIN: '" << *$4 << "' ON " << $6; addTable(*$4); delete $4; } | Tables RIGHT OUTER JOIN IDENTIFIER SQL_ON ColExpression { kdbDebug() << "RIGHT OUTER JOIN: '" << *$5 << "' ON " << $7; addTable($5->toQString()); delete $5; }*/ ; /* FlatTableList: aFlatTableList { $$ } ;*/ FlatTableList: FlatTableList ',' FlatTable { $$ = $1; $$->append(*$3); delete $3; } |FlatTable { $$ = new KDbNArgExpression(KDb::TableListExpression, KDbToken::IDENTIFIER); //ok? $$->append(*$1); delete $1; } ; FlatTable: IDENTIFIER { kdbDebug() << "FROM: '" << *$1 << "'"; $$ = new KDbVariableExpression(*$1); //! @todo this isn't ok for more tables: /* KDbField::ListIterator it = globalParser->query()->fieldsIterator(); for(KDbField *item; (item = it.current()); ++it) { if(item->table() == dummy) { item->setTable(schema); } if(item->table() && !item->isQueryAsterisk()) { KDbField *f = item->table()->field(item->name()); if(!f) { KDbParserError err(KDbParser::tr("Field List Error"), KDbParser::tr("Unknown column '%1' in table '%2'",item->name(),schema->name()), ctoken, current); globalParser->setError(err); yyerror("fieldlisterror"); } } }*/ delete $1; } | IDENTIFIER IDENTIFIER { //table + alias $$ = new KDbBinaryExpression( KDbVariableExpression(*$1), KDbToken::AS_EMPTY, KDbVariableExpression(*$2) ); delete $1; delete $2; } | IDENTIFIER AS IDENTIFIER { //table + alias $$ = new KDbBinaryExpression( KDbVariableExpression(*$1), KDbToken::AS, KDbVariableExpression(*$3) ); delete $1; delete $3; } ; ColViews: ColViews ',' ColItem { $$ = $1; $$->append(*$3); delete $3; kdbDebug() << "ColViews: ColViews , ColItem"; } |ColItem { $$ = new KDbNArgExpression(KDb::FieldListExpression, KDbToken()); $$->append(*$1); delete $1; kdbDebug() << "ColViews: ColItem"; } ; ColItem: ColExpression { // $$ = new KDbField(); // dummy->addField($$); // $$->setExpression( $1 ); // globalParser->query()->addField($$); $$ = $1; kdbDebug() << " added column expr:" << *$1; } | ColWildCard { $$ = $1; kdbDebug() << " added column wildcard:" << *$1; } | ColExpression AS IDENTIFIER { $$ = new KDbBinaryExpression( *$1, KDbToken::AS, KDbVariableExpression(*$3) ); kdbDebug() << " added column expr:" << *$$; delete $1; delete $3; } | ColExpression IDENTIFIER { $$ = new KDbBinaryExpression( *$1, KDbToken::AS_EMPTY, KDbVariableExpression(*$2) ); kdbDebug() << " added column expr:" << *$$; delete $1; delete $2; } ; ColExpression: aExpr { $$ = $1; } /* HANDLED BY 'IDENTIFIER aExprList' | IDENTIFIER '(' ColViews ')' { $$ = new KDbFunctionExpression( $1, $3 ); }*/ //! @todo /* | SUM '(' ColExpression ')' { KDbFunctionExpression( // $$ = new AggregationExpression( SUM, ); // $$->setName("SUM(" + $3->name() + ")"); //wait $$->containsGroupingAggregate(true); //wait globalParser->query()->grouped(true); }*/ //! @todo /* | SQL_MIN '(' ColExpression ')' { $$ = $3; // $$->setName("MIN(" + $3->name() + ")"); //wait $$->containsGroupingAggregate(true); //wait globalParser->query()->grouped(true); }*/ //! @todo /* | SQL_MAX '(' ColExpression ')' { $$ = $3; // $$->setName("MAX(" + $3->name() + ")"); //wait $$->containsGroupingAggregate(true); //wait globalParser->query()->grouped(true); }*/ //! @todo /* | AVG '(' ColExpression ')' { $$ = $3; // $$->setName("AVG(" + $3->name() + ")"); //wait $$->containsGroupingAggregate(true); //wait globalParser->query()->grouped(true); }*/ | DISTINCT '(' ColExpression ')' { $$ = $3; //! @todo DISTINCT '(' ColExpression ')' // $$->setName("DISTINCT(" + $3->name() + ")"); } ; ColWildCard: '*' { $$ = new KDbVariableExpression(QLatin1String("*")); kdbDebug() << "all columns"; // KDbQueryAsterisk *ast = new KDbQueryAsterisk(globalParser->query(), dummy); // globalParser->query()->addAsterisk(ast); // requiresTable = true; } | IDENTIFIER '.' '*' { QString s( *$1 ); s += QLatin1String(".*"); $$ = new KDbVariableExpression(s); kdbDebug() << " + all columns from " << s; delete $1; } /*| ERROR_DIGIT_BEFORE_IDENTIFIER { $$ = new KDbVariableExpression($1); kdbDebug() << " Invalid identifier! " << $1; setError(KDbParser::tr("Invalid identifier \"%1\"",$1)); }*/ ; %% diff --git a/src/parser/KDbSqlTypes.h b/src/parser/KDbSqlTypes.h index 99aedc37..fab83742 100644 --- a/src/parser/KDbSqlTypes.h +++ b/src/parser/KDbSqlTypes.h @@ -1,104 +1,104 @@ /* This file is part of the KDE project Copyright (C) 2003, 2006 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SQLTYPES_H #define SQLTYPES_H #include #include #include "KDbExpression.h" #include "KDbOrderByColumn.h" struct dateType { int year; int month; int day; }; struct realType { int integer; int fractional; }; //! @internal struct OrderByColumnInternal { class List : public QList { public: List() {} ~List() {} }; OrderByColumnInternal() : columnNumber(-1) , order(KDbOrderByColumn::SortOrder::Ascending) { } void setColumnByNameOrNumber(const QVariant& nameOrNumber) { if (nameOrNumber.type() == QVariant::String) { aliasOrName = nameOrNumber.toString(); columnNumber = -1; } else { columnNumber = nameOrNumber.toInt(); aliasOrName.clear(); } } //! Can include a "tablename." prefix QString aliasOrName; //! Optional, used instead of aliasOrName to refer to column //! by its number rather than name. int columnNumber; KDbOrderByColumn::SortOrder order; }; //! @internal struct SelectOptionsInternal { - SelectOptionsInternal() : orderByColumns(0) {} + SelectOptionsInternal() : orderByColumns(nullptr) {} ~SelectOptionsInternal() { delete orderByColumns; // delete because this is internal temp. structure } KDbExpression whereExpr; QList* orderByColumns; }; class KDbExpressionPtr { public: inline KDbExpressionPtr(KDbExpression *exp) : e(exp) {} inline KDbExpression toExpr() { KDbExpression exp(*e); delete e; - e = 0; + e = nullptr; return exp; } inline KDbNArgExpression toNArg() { KDbNArgExpression exp(e->toNArg()); delete e; - e = 0; + e = nullptr; return exp; } //private: KDbExpression *e; private: Q_DISABLE_COPY(KDbExpressionPtr) }; QDebug operator<<(QDebug dbg, const KDbExpressionPtr& expr); #endif diff --git a/src/parser/generate_parser_code.sh b/src/parser/generate_parser_code.sh index d4c17f8f..da98b52b 100755 --- a/src/parser/generate_parser_code.sh +++ b/src/parser/generate_parser_code.sh @@ -1,387 +1,387 @@ #!/bin/bash # # Copyright (C) 2006-2015 Jarosław Staniek # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # # Generates parser and lexer code using bison and flex # me=generate_parser_code.sh BISON_MIN=3.0.4 # keep updated for best results BISON_MIN_NUM=30004 # keep updated for best results FLEX_MIN=2.5.37 # keep updated for best results FLEX_MIN_NUM=20537 # keep updated for best results # Check minimum version of bison bisonv=`bison --version | head -n 1| cut -f4 -d" "` bisonv1=`echo $bisonv | cut -f1 -d.` bisonv2=`echo $bisonv | cut -f2 -d.` bisonv3=`echo $bisonv | cut -f3 -d.` if [ -z $bisonv3 ] ; then bisonv3=0; fi bisonvnum=`expr $bisonv1 \* 10000 + $bisonv2 \* 100 + $bisonv3` if [ $bisonvnum -lt $BISON_MIN_NUM ] ; then echo "$bisonv is too old bison version, the minimum is $BISON_MIN." exit 1 fi # Check minimum version of flex flexv=`flex --version | head -n 1| cut -f2 -d" "` flexv1=`echo $flexv | cut -f1 -d.` flexv2=`echo $flexv | cut -f2 -d.` flexv3=`echo $flexv | cut -f3 -d.` flexvnum=`expr $flexv1 \* 10000 + $flexv2 \* 100 + $flexv3` if [ $flexvnum -lt $FLEX_MIN_NUM ] ; then echo "$flexv is too old flex version, the minimum is $FLEX_MIN." exit 1 fi # Generate lexer and parser builddir=$PWD srcdir=`dirname $0` cd $srcdir flex -ogenerated/sqlscanner.cpp KDbSqlScanner.l # Correct a few yy_size_t vs size_t vs int differences that some bisons cause sed --in-place 's/int yyleng/yy_size_t yyleng/g;s/int yyget_leng/yy_size_t yyget_leng/g;s/yyleng = (int)/yyleng = (size_t)/g;' generated/sqlscanner.cpp bison -d KDbSqlParser.y -Wall -fall -rall --report-file=$builddir/KDbSqlParser.output # postprocess cat << EOF > generated/sqlparser.h /**************************************************************************** * Created by $me * WARNING! All changes made in this file will be lost! ****************************************************************************/ #ifndef KDBSQLPARSER_H #define KDBSQLPARSER_H #include "KDbExpression.h" #include "KDbField.h" #include "KDbOrderByColumn.h" struct OrderByColumnInternal; struct SelectOptionsInternal; EOF # Fine-tune the code: extra functions and remove trailing white space cat KDbSqlParser.tab.h >> generated/sqlparser.h echo '#endif' >> generated/sqlparser.h sed --in-place 's/[[:space:]]\+$//;s/\t/ /g' generated/sqlparser.h cat KDbSqlParser.tab.c | sed -e "s/KDbSqlParser\.tab\.c/sqlparser.cpp/g" > generated/sqlparser.cpp cat << EOF >> generated/sqlparser.cpp KDB_TESTING_EXPORT const char* g_tokenName(unsigned int offset) { const int t = YYTRANSLATE(offset); if (t >= YYTRANSLATE(::SQL_TYPE)) { return yytname[t]; } return 0; } //static const int KDbToken::maxCharTokenValue = 253; //static const int KDbToken::maxTokenValue = YYMAXUTOK; EOF sed --in-place 's/[[:space:]]\+$//;s/\t/ /g' generated/sqlparser.cpp # Extract table of SQL tokens # unused ./extract_tokens.sh > generated/tokens.cpp rm -f KDbSqlParser.tab.h KDbSqlParser.tab.c # Create KDbToken.h cat << EOF > generated/KDbToken.h /**************************************************************************** * Created by $me * WARNING! All changes made in this file will be lost! ****************************************************************************/ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOKEN_H #define KDB_TOKEN_H #include "kdb_export.h" #include class KDbDriver; /*! @brief A type-safe KDBSQL token It can be used in KDb expressions @see KDbExpression */ class KDB_EXPORT KDbToken { public: //! @todo add KDbToken(const QByteArray &name) //! Creates an invalid token inline KDbToken() : v(0) {} KDbToken(const KDbToken &other) : v(other.v) {} //! Creates a single-character token //! Only characters that belong to the grammar are accepted: EOF (echo -n " //! "; grep "\"'.'\"," generated/sqlparser.cpp \ | sed -e ":a;N;s/\"\('.'\)\",/\1/g;s/\n//g;s/\".*,//g;s/^ *//g;s/ *$//g;") >> generated/KDbToken.h cat << EOF >> generated/KDbToken.h //! Invalid KDbToken is created for character that is not accepted. KDbToken(char charToken); //! @return true if this token is valid inline bool isValid() const { return v != 0; } //! @return name of this token //! Useful for debugging. //! For example "NOT_EQUAL" string is returned for the NOT_EQUAL token. //! A single character is returned for printable single-character tokens. //! A number is returned for non-printable single-character. //! "" is returned for an invalid string. QString name() const; //! @return string interpretation of this token (as visibe to the user) //! For example "<>" is returned for the NOT_EQUAL token. //! Empty string is returned for an invalid string //! The result may depend on the optional @a driver parameter. - //! If @a driver is 0, representation for portable KDbSQL dialect is returned. - QString toString(const KDbDriver *driver = 0) const; + //! If @a driver is @c nullptr, representation for portable KDbSQL dialect is returned. + QString toString(const KDbDriver *driver = nullptr) const; //! Like toString(const KDbDriver *driver) - static QString toString(KDbToken token, const KDbDriver *driver = 0); + static QString toString(KDbToken token, const KDbDriver *driver = nullptr); //! Maximum character token value (253) static const int maxCharTokenValue; //! Maximum character token value static const int maxTokenValue; //! @return character equivalent of this token //! Only character-based tokens are supported this way (toInt() <= maxCharTokenValue). //! For unsupported tokens 0 is returned. inline char toChar() const { return v <= maxCharTokenValue ? v : 0; } //! @return numeric value of this token inline int value() const { return v; } //! @return true if this token is equal to @a other token inline bool operator==(KDbToken other) const { return v == other.v; } //! @return true if this token is not equal to @a other token inline bool operator!=(KDbToken other) const { return v != other.v; } //! @return true if this token is equal to @a other token inline bool operator==(char charToken) const { return v == charToken; } //! @return true if this token is not equal to @a other token inline bool operator!=(char charToken) const { return v != charToken; } static QList allTokens(); // -- constants go here -- EOF function extractTokens() { grep -E " [A-Z0-9_]+ = [[:digit:]]+" generated/sqlparser.h \ | sed -e "s/^ //g;s/ = / /g;s/,//g" } extractTokens | while read token value; do echo " static const KDbToken $token;" done >> generated/KDbToken.h function customTokens() { cat << EOF BETWEEN_AND 0x1001 NOT_BETWEEN_AND 0x1002 EOF } echo " //! Custom tokens are not used in parser but used as an extension in expression classes." >> generated/KDbToken.h customTokens | while read token value; do echo " static const KDbToken $token;" done >> generated/KDbToken.h cat << EOF >> generated/KDbToken.h // -- end of constants -- class List; private: inline KDbToken(int value) : v(value) {} int v; }; //! Sends information about token @a token to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, KDbToken token); #endif EOF cat << EOF > generated/KDbToken.cpp /**************************************************************************** * Created by $me * WARNING! All changes made in this file will be lost! ****************************************************************************/ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbToken.h" #include "KDbDriver.h" #include "KDbDriverBehavior.h" #include "sqlparser.h" #include "parser/KDbParser_p.h" #include KDbToken::KDbToken(char charToken) - : v(g_tokenName(charToken) == 0 ? 0 : charToken) + : v(g_tokenName(charToken) == nullptr ? 0 : charToken) { } QString KDbToken::name() const { if (!isValid()) { return QLatin1String(""); } if (v > maxCharTokenValue) { return QLatin1String(g_tokenName(v)); } if (isprint(v)) { return QString(QLatin1Char(char(v))); } else { return QLatin1String(QByteArray::number(v)); } } QString KDbToken::toString(const KDbDriver *driver) const { if (toChar() > 0) { return name(); } // other arithmetic operations: << >> // NOTE: only include cases that have toString() != name() or are dependent on driver switch (v) { case ::BITWISE_SHIFT_RIGHT: return QLatin1String(">>"); case ::BITWISE_SHIFT_LEFT: return QLatin1String("<<"); // other relational operations: <= >= <> (or !=) LIKE IN case ::NOT_EQUAL: return QLatin1String("<>"); case ::NOT_EQUAL2: return QLatin1String("!="); case ::LESS_OR_EQUAL: return QLatin1String("<="); case ::GREATER_OR_EQUAL: return QLatin1String(">="); case ::LIKE: return driver ? driver->behavior()->LIKE_OPERATOR : QLatin1String("LIKE"); case ::NOT_LIKE: return driver ? (QString::fromLatin1("NOT ") + driver->behavior()->LIKE_OPERATOR) : QString::fromLatin1("NOT LIKE"); case ::SQL_IN: return QLatin1String("IN"); // other logical operations: OR (or ||) AND (or &&) XOR case ::SIMILAR_TO: return QLatin1String("SIMILAR TO"); case ::NOT_SIMILAR_TO: return QLatin1String("NOT SIMILAR TO"); // other string operations: || (as CONCATENATION) case ::CONCATENATION: return QLatin1String("||"); // SpecialBinary "pseudo operators": /* not handled here */ default:; } const QString s = name(); if (!s.isEmpty()) { return s; } return QString::fromLatin1(" ").arg(v); } //static QString KDbToken::toString(KDbToken token, const KDbDriver *driver) { return token.toString(driver); } KDB_EXPORT QDebug operator<<(QDebug dbg, KDbToken token) { dbg.nospace() << qPrintable(token.name()); return dbg.space(); } //! @internal class KDbToken::List : public QList { public: List() { for (int i = 0; i < KDbToken::maxTokenValue; ++i) { - if (g_tokenName(i) != 0) { + if (g_tokenName(i)) { append(KDbToken(i)); } } } }; Q_GLOBAL_STATIC(KDbToken::List, g_allTokens) //static QList KDbToken::allTokens() { return *g_allTokens; } EOF extractTokens | while read token value; do echo "const KDbToken KDbToken::$token(::$token);" done >> generated/KDbToken.cpp customTokens | while read token value; do echo "const KDbToken KDbToken::$token($value);" done >> generated/KDbToken.cpp diff --git a/src/parser/generated/KDbToken.cpp b/src/parser/generated/KDbToken.cpp index 3fc17939..6cb8d532 100644 --- a/src/parser/generated/KDbToken.cpp +++ b/src/parser/generated/KDbToken.cpp @@ -1,187 +1,187 @@ /**************************************************************************** * Created by generate_parser_code.sh * WARNING! All changes made in this file will be lost! ****************************************************************************/ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbToken.h" #include "KDbDriver.h" #include "KDbDriverBehavior.h" #include "sqlparser.h" #include "parser/KDbParser_p.h" #include KDbToken::KDbToken(char charToken) - : v(g_tokenName(charToken) == 0 ? 0 : charToken) + : v(g_tokenName(charToken) == nullptr ? 0 : charToken) { } QString KDbToken::name() const { if (!isValid()) { return QLatin1String(""); } if (v > maxCharTokenValue) { return QLatin1String(g_tokenName(v)); } if (isprint(v)) { return QString(QLatin1Char(char(v))); } else { return QLatin1String(QByteArray::number(v)); } } QString KDbToken::toString(const KDbDriver *driver) const { if (toChar() > 0) { return name(); } // other arithmetic operations: << >> // NOTE: only include cases that have toString() != name() or are dependent on driver switch (v) { case ::BITWISE_SHIFT_RIGHT: return QLatin1String(">>"); case ::BITWISE_SHIFT_LEFT: return QLatin1String("<<"); // other relational operations: <= >= <> (or !=) LIKE IN case ::NOT_EQUAL: return QLatin1String("<>"); case ::NOT_EQUAL2: return QLatin1String("!="); case ::LESS_OR_EQUAL: return QLatin1String("<="); case ::GREATER_OR_EQUAL: return QLatin1String(">="); case ::LIKE: return driver ? driver->behavior()->LIKE_OPERATOR : QLatin1String("LIKE"); case ::NOT_LIKE: return driver ? (QString::fromLatin1("NOT ") + driver->behavior()->LIKE_OPERATOR) : QString::fromLatin1("NOT LIKE"); case ::SQL_IN: return QLatin1String("IN"); // other logical operations: OR (or ||) AND (or &&) XOR case ::SIMILAR_TO: return QLatin1String("SIMILAR TO"); case ::NOT_SIMILAR_TO: return QLatin1String("NOT SIMILAR TO"); // other string operations: || (as CONCATENATION) case ::CONCATENATION: return QLatin1String("||"); // SpecialBinary "pseudo operators": /* not handled here */ default:; } const QString s = name(); if (!s.isEmpty()) { return s; } return QString::fromLatin1(" ").arg(v); } //static QString KDbToken::toString(KDbToken token, const KDbDriver *driver) { return token.toString(driver); } KDB_EXPORT QDebug operator<<(QDebug dbg, KDbToken token) { dbg.nospace() << qPrintable(token.name()); return dbg.space(); } //! @internal class KDbToken::List : public QList { public: List() { for (int i = 0; i < KDbToken::maxTokenValue; ++i) { - if (g_tokenName(i) != 0) { + if (g_tokenName(i)) { append(KDbToken(i)); } } } }; Q_GLOBAL_STATIC(KDbToken::List, g_allTokens) //static QList KDbToken::allTokens() { return *g_allTokens; } const KDbToken KDbToken::SQL_TYPE(::SQL_TYPE); const KDbToken KDbToken::AS(::AS); const KDbToken KDbToken::AS_EMPTY(::AS_EMPTY); const KDbToken KDbToken::ASC(::ASC); const KDbToken KDbToken::AUTO_INCREMENT(::AUTO_INCREMENT); const KDbToken KDbToken::BIT(::BIT); const KDbToken KDbToken::BITWISE_SHIFT_LEFT(::BITWISE_SHIFT_LEFT); const KDbToken KDbToken::BITWISE_SHIFT_RIGHT(::BITWISE_SHIFT_RIGHT); const KDbToken KDbToken::BY(::BY); const KDbToken KDbToken::CHARACTER_STRING_LITERAL(::CHARACTER_STRING_LITERAL); const KDbToken KDbToken::CONCATENATION(::CONCATENATION); const KDbToken KDbToken::CREATE(::CREATE); const KDbToken KDbToken::DESC(::DESC); const KDbToken KDbToken::DISTINCT(::DISTINCT); const KDbToken KDbToken::DOUBLE_QUOTED_STRING(::DOUBLE_QUOTED_STRING); const KDbToken KDbToken::FROM(::FROM); const KDbToken KDbToken::JOIN(::JOIN); const KDbToken KDbToken::KEY(::KEY); const KDbToken KDbToken::LEFT(::LEFT); const KDbToken KDbToken::LESS_OR_EQUAL(::LESS_OR_EQUAL); const KDbToken KDbToken::GREATER_OR_EQUAL(::GREATER_OR_EQUAL); const KDbToken KDbToken::SQL_NULL(::SQL_NULL); const KDbToken KDbToken::SQL_IS(::SQL_IS); const KDbToken KDbToken::SQL_IS_NULL(::SQL_IS_NULL); const KDbToken KDbToken::SQL_IS_NOT_NULL(::SQL_IS_NOT_NULL); const KDbToken KDbToken::ORDER(::ORDER); const KDbToken KDbToken::PRIMARY(::PRIMARY); const KDbToken KDbToken::SELECT(::SELECT); const KDbToken KDbToken::INTEGER_CONST(::INTEGER_CONST); const KDbToken KDbToken::REAL_CONST(::REAL_CONST); const KDbToken KDbToken::RIGHT(::RIGHT); const KDbToken KDbToken::SQL_ON(::SQL_ON); const KDbToken KDbToken::DATE_CONST(::DATE_CONST); const KDbToken KDbToken::DATETIME_CONST(::DATETIME_CONST); const KDbToken KDbToken::TIME_CONST(::TIME_CONST); const KDbToken KDbToken::TABLE(::TABLE); const KDbToken KDbToken::IDENTIFIER(::IDENTIFIER); const KDbToken KDbToken::IDENTIFIER_DOT_ASTERISK(::IDENTIFIER_DOT_ASTERISK); const KDbToken KDbToken::QUERY_PARAMETER(::QUERY_PARAMETER); const KDbToken KDbToken::VARCHAR(::VARCHAR); const KDbToken KDbToken::WHERE(::WHERE); const KDbToken KDbToken::SQL(::SQL); const KDbToken KDbToken::SQL_TRUE(::SQL_TRUE); const KDbToken KDbToken::SQL_FALSE(::SQL_FALSE); const KDbToken KDbToken::UNION(::UNION); const KDbToken KDbToken::SCAN_ERROR(::SCAN_ERROR); const KDbToken KDbToken::AND(::AND); const KDbToken KDbToken::BETWEEN(::BETWEEN); const KDbToken KDbToken::NOT_BETWEEN(::NOT_BETWEEN); const KDbToken KDbToken::EXCEPT(::EXCEPT); const KDbToken KDbToken::SQL_IN(::SQL_IN); const KDbToken KDbToken::INTERSECT(::INTERSECT); const KDbToken KDbToken::LIKE(::LIKE); const KDbToken KDbToken::ILIKE(::ILIKE); const KDbToken KDbToken::NOT_LIKE(::NOT_LIKE); const KDbToken KDbToken::NOT(::NOT); const KDbToken KDbToken::NOT_EQUAL(::NOT_EQUAL); const KDbToken KDbToken::NOT_EQUAL2(::NOT_EQUAL2); const KDbToken KDbToken::OR(::OR); const KDbToken KDbToken::SIMILAR_TO(::SIMILAR_TO); const KDbToken KDbToken::NOT_SIMILAR_TO(::NOT_SIMILAR_TO); const KDbToken KDbToken::XOR(::XOR); const KDbToken KDbToken::UMINUS(::UMINUS); const KDbToken KDbToken::BETWEEN_AND(0x1001); const KDbToken KDbToken::NOT_BETWEEN_AND(0x1002); diff --git a/src/parser/generated/KDbToken.h b/src/parser/generated/KDbToken.h index 60d5f847..5776090c 100644 --- a/src/parser/generated/KDbToken.h +++ b/src/parser/generated/KDbToken.h @@ -1,179 +1,179 @@ /**************************************************************************** * Created by generate_parser_code.sh * WARNING! All changes made in this file will be lost! ****************************************************************************/ /* This file is part of the KDE project Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOKEN_H #define KDB_TOKEN_H #include "kdb_export.h" #include class KDbDriver; /*! @brief A type-safe KDBSQL token It can be used in KDb expressions @see KDbExpression */ class KDB_EXPORT KDbToken { public: //! @todo add KDbToken(const QByteArray &name) //! Creates an invalid token inline KDbToken() : v(0) {} KDbToken(const KDbToken &other) : v(other.v) {} //! Creates a single-character token //! Only characters that belong to the grammar are accepted: //! ';' ',' '.' '>' '<' '=' '+' '-' '&' '|' '/' '*' '%' '~' '(' ')' //! Invalid KDbToken is created for character that is not accepted. KDbToken(char charToken); //! @return true if this token is valid inline bool isValid() const { return v != 0; } //! @return name of this token //! Useful for debugging. //! For example "NOT_EQUAL" string is returned for the NOT_EQUAL token. //! A single character is returned for printable single-character tokens. //! A number is returned for non-printable single-character. //! "" is returned for an invalid string. QString name() const; //! @return string interpretation of this token (as visibe to the user) //! For example "<>" is returned for the NOT_EQUAL token. //! Empty string is returned for an invalid string //! The result may depend on the optional @a driver parameter. - //! If @a driver is 0, representation for portable KDbSQL dialect is returned. - QString toString(const KDbDriver *driver = 0) const; + //! If @a driver is @c nullptr, representation for portable KDbSQL dialect is returned. + QString toString(const KDbDriver *driver = nullptr) const; //! Like toString(const KDbDriver *driver) - static QString toString(KDbToken token, const KDbDriver *driver = 0); + static QString toString(KDbToken token, const KDbDriver *driver = nullptr); //! Maximum character token value (253) static const int maxCharTokenValue; //! Maximum character token value static const int maxTokenValue; //! @return character equivalent of this token //! Only character-based tokens are supported this way (toInt() <= maxCharTokenValue). //! For unsupported tokens 0 is returned. inline char toChar() const { return v <= maxCharTokenValue ? v : 0; } //! @return numeric value of this token inline int value() const { return v; } //! @return true if this token is equal to @a other token inline bool operator==(KDbToken other) const { return v == other.v; } //! @return true if this token is not equal to @a other token inline bool operator!=(KDbToken other) const { return v != other.v; } //! @return true if this token is equal to @a other token inline bool operator==(char charToken) const { return v == charToken; } //! @return true if this token is not equal to @a other token inline bool operator!=(char charToken) const { return v != charToken; } static QList allTokens(); // -- constants go here -- static const KDbToken SQL_TYPE; static const KDbToken AS; static const KDbToken AS_EMPTY; static const KDbToken ASC; static const KDbToken AUTO_INCREMENT; static const KDbToken BIT; static const KDbToken BITWISE_SHIFT_LEFT; static const KDbToken BITWISE_SHIFT_RIGHT; static const KDbToken BY; static const KDbToken CHARACTER_STRING_LITERAL; static const KDbToken CONCATENATION; static const KDbToken CREATE; static const KDbToken DESC; static const KDbToken DISTINCT; static const KDbToken DOUBLE_QUOTED_STRING; static const KDbToken FROM; static const KDbToken JOIN; static const KDbToken KEY; static const KDbToken LEFT; static const KDbToken LESS_OR_EQUAL; static const KDbToken GREATER_OR_EQUAL; static const KDbToken SQL_NULL; static const KDbToken SQL_IS; static const KDbToken SQL_IS_NULL; static const KDbToken SQL_IS_NOT_NULL; static const KDbToken ORDER; static const KDbToken PRIMARY; static const KDbToken SELECT; static const KDbToken INTEGER_CONST; static const KDbToken REAL_CONST; static const KDbToken RIGHT; static const KDbToken SQL_ON; static const KDbToken DATE_CONST; static const KDbToken DATETIME_CONST; static const KDbToken TIME_CONST; static const KDbToken TABLE; static const KDbToken IDENTIFIER; static const KDbToken IDENTIFIER_DOT_ASTERISK; static const KDbToken QUERY_PARAMETER; static const KDbToken VARCHAR; static const KDbToken WHERE; static const KDbToken SQL; static const KDbToken SQL_TRUE; static const KDbToken SQL_FALSE; static const KDbToken UNION; static const KDbToken SCAN_ERROR; static const KDbToken AND; static const KDbToken BETWEEN; static const KDbToken NOT_BETWEEN; static const KDbToken EXCEPT; static const KDbToken SQL_IN; static const KDbToken INTERSECT; static const KDbToken LIKE; static const KDbToken ILIKE; static const KDbToken NOT_LIKE; static const KDbToken NOT; static const KDbToken NOT_EQUAL; static const KDbToken NOT_EQUAL2; static const KDbToken OR; static const KDbToken SIMILAR_TO; static const KDbToken NOT_SIMILAR_TO; static const KDbToken XOR; static const KDbToken UMINUS; //! Custom tokens are not used in parser but used as an extension in expression classes. static const KDbToken BETWEEN_AND; static const KDbToken NOT_BETWEEN_AND; // -- end of constants -- class List; private: inline KDbToken(int value) : v(value) {} int v; }; //! Sends information about token @a token to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, KDbToken token); #endif diff --git a/src/parser/generated/sqlscanner.cpp b/src/parser/generated/sqlscanner.cpp index bf5f7eb3..31b9e7e1 100644 --- a/src/parser/generated/sqlscanner.cpp +++ b/src/parser/generated/sqlscanner.cpp @@ -1,2353 +1,2353 @@ #line 2 "generated/sqlscanner.cpp" #line 4 "generated/sqlscanner.cpp" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 5 #define YY_FLEX_SUBMINOR_VERSION 37 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ #ifdef __cplusplus /* The "const" storage-class-modifier is valid. */ #define YY_USE_CONST #else /* ! __cplusplus */ /* C99 requires __STDC__ to be defined as 1. */ #if defined (__STDC__) #define YY_USE_CONST #endif /* defined (__STDC__) */ #endif /* ! __cplusplus */ #ifdef YY_USE_CONST #define yyconst const #else #define yyconst #endif /* Returned upon end-of-file. */ #define YY_NULL 0 /* Promotes a possibly negative, possibly signed char to an unsigned * integer for use as an array index. If the signed char is negative, * we want to instead treat it as an 8-bit unsigned char, hence the * double cast. */ #define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN (yy_start) = 1 + 2 * /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START (((yy_start) - 1) / 2) #define YYSTATE YY_START /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) /* Special action meaning "start processing a new file". */ #define YY_NEW_FILE yyrestart(yyin ) #define YY_END_OF_BUFFER_CHAR 0 /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #define YY_BUF_SIZE 16384 #endif /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif extern yy_size_t yyleng; extern FILE *yyin, *yyout; #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 #define YY_LESS_LINENO(n) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ *yy_cp = (yy_hold_char); \ YY_RESTORE_YY_MORE_OFFSET \ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) #define unput(c) yyunput( c, (yytext_ptr) ) #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ - yy_size_t yy_buf_size; + int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ - yy_size_t yy_n_chars; + int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; #define YY_BUFFER_NEW 0 #define YY_BUFFER_NORMAL 1 /* When an EOF's been seen but there's still some text to process * then we mark the buffer as YY_EOF_PENDING, to indicate that we * shouldn't try reading from the input source any more. We might * still have a bunch of tokens to match, though, because of * possible backing-up. * * When we actually see the EOF, we change the status to "new" * (via yyrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ /* Stack of input buffers. */ static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". * * Returns the top of the stack, or NULL. */ #define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ : NULL) /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] /* yy_hold_char holds the character lost when yytext is formed. */ static char yy_hold_char; -static yy_size_t yy_n_chars; /* number of characters read into yy_ch_buf */ +static int yy_n_chars; /* number of characters read into yy_ch_buf */ yy_size_t yyleng; /* Points to current character in buffer. */ static char *yy_c_buf_p = (char *) 0; static int yy_init = 0; /* whether we need to initialize */ static int yy_start = 0; /* start state number */ /* Flag which is used to allow yywrap()'s to do buffer switches * instead of setting up a fresh yyin. A bit of a hack ... */ static int yy_did_buffer_switch_on_eof; void yyrestart (FILE *input_file ); void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ); void yy_delete_buffer (YY_BUFFER_STATE b ); void yy_flush_buffer (YY_BUFFER_STATE b ); void yypush_buffer_state (YY_BUFFER_STATE new_buffer ); void yypop_buffer_state (void ); static void yyensure_buffer_stack (void ); static void yy_load_buffer_state (void ); static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ); #define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ) YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ); YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ); YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,yy_size_t len ); void *yyalloc (yy_size_t ); void *yyrealloc (void *,yy_size_t ); void yyfree (void * ); #define yy_new_buffer yy_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ yyensure_buffer_stack (); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer(yyin,YY_BUF_SIZE ); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ yyensure_buffer_stack (); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer(yyin,YY_BUF_SIZE ); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) /* Begin user sect3 */ #define yywrap() 1 #define YY_SKIP_YYWRAP typedef unsigned char YY_CHAR; FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; typedef int yy_state_type; extern int yylineno; int yylineno = 1; extern char *yytext; #define yytext_ptr yytext static yy_state_type yy_get_previous_state (void ); static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); static int yy_get_next_buffer (void ); static void yy_fatal_error (yyconst char msg[] ); /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ (yytext_ptr) = yy_bp; \ yyleng = (size_t) (yy_cp - yy_bp); \ (yy_hold_char) = *yy_cp; \ *yy_cp = '\0'; \ (yy_c_buf_p) = yy_cp; #define YY_NUM_RULES 48 #define YY_END_OF_BUFFER 49 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info { flex_int32_t yy_verify; flex_int32_t yy_nxt; }; static yyconst flex_int16_t yy_accept[177] = { 0, 0, 0, 49, 48, 46, 47, 48, 47, 47, 48, 47, 7, 47, 47, 47, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 47, 47, 46, 2, 0, 43, 0, 9, 0, 43, 0, 8, 8, 7, 44, 32, 4, 1, 3, 5, 33, 44, 10, 44, 40, 44, 44, 44, 44, 6, 25, 44, 44, 44, 44, 44, 29, 30, 44, 44, 44, 44, 44, 44, 44, 0, 31, 43, 43, 8, 9, 41, 44, 44, 44, 44, 44, 44, 0, 44, 44, 44, 24, 44, 44, 44, 44, 44, 44, 44, 44, 34, 45, 44, 44, 42, 44, 12, 44, 0, 14, 15, 16, 0, 26, 44, 44, 44, 44, 44, 27, 44, 44, 44, 28, 44, 0, 0, 0, 0, 0, 39, 35, 44, 44, 37, 38, 44, 11, 44, 0, 0, 0, 0, 0, 36, 44, 18, 13, 0, 23, 0, 0, 0, 44, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 22, 19, 0, 0, 0, 20, 0 } ; static yyconst flex_int32_t yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 1, 5, 6, 7, 5, 5, 5, 5, 5, 5, 8, 5, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 10, 11, 12, 5, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 28, 29, 30, 31, 32, 28, 33, 34, 35, 28, 36, 37, 38, 5, 28, 5, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 28, 28, 54, 55, 56, 57, 28, 58, 59, 60, 28, 1, 61, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } ; static yyconst flex_int32_t yy_meta[62] = { 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1 } ; static yyconst flex_int16_t yy_base[181] = { 0, 0, 0, 431, 441, 428, 418, 58, 441, 422, 56, 418, 56, 56, 415, 58, 64, 63, 67, 65, 417, 68, 75, 76, 70, 101, 110, 78, 94, 118, 92, 116, 0, 363, 421, 441, 73, 418, 116, 441, 67, 414, 107, 411, 410, 80, 410, 441, 441, 441, 441, 441, 441, 124, 127, 140, 409, 138, 137, 126, 148, 143, 77, 144, 141, 155, 152, 153, 408, 171, 165, 162, 172, 174, 185, 181, 182, 377, 441, 190, 188, 405, 404, 403, 197, 194, 207, 198, 201, 221, 214, 192, 212, 224, 229, 211, 237, 226, 239, 241, 249, 242, 250, 402, 441, 243, 261, 401, 257, 400, 262, 244, 397, 396, 393, 281, 391, 255, 267, 279, 269, 295, 389, 298, 305, 308, 388, 310, 258, 278, 291, 303, 305, 386, 384, 302, 322, 382, 378, 321, 376, 331, 381, 313, 301, 317, 317, 283, 335, 270, 231, 343, 441, 313, 327, 328, 288, 322, 338, 441, 332, 357, 341, 350, 359, 347, 352, 351, 333, 441, 441, 441, 219, 376, 353, 441, 441, 432, 435, 90, 438 } ; static yyconst flex_int16_t yy_def[181] = { 0, 176, 1, 176, 176, 176, 176, 177, 176, 176, 178, 176, 179, 176, 176, 176, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 180, 176, 176, 176, 177, 176, 177, 176, 178, 176, 178, 176, 176, 179, 179, 176, 176, 176, 176, 176, 176, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 180, 176, 177, 178, 176, 179, 179, 179, 179, 179, 179, 179, 179, 176, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 176, 179, 179, 179, 179, 179, 179, 176, 179, 179, 179, 176, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 179, 176, 176, 176, 176, 176, 179, 179, 179, 179, 179, 179, 179, 179, 179, 176, 176, 176, 176, 176, 179, 179, 179, 179, 176, 176, 176, 176, 176, 179, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 0, 176, 176, 176, 176 } ; static yyconst flex_int16_t yy_nxt[503] = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 20, 20, 22, 23, 20, 24, 20, 25, 26, 20, 27, 28, 29, 20, 30, 31, 20, 32, 4, 8, 16, 17, 18, 19, 20, 21, 20, 20, 22, 23, 20, 24, 20, 25, 26, 27, 28, 29, 20, 30, 31, 20, 33, 37, 41, 44, 45, 47, 48, 49, 51, 52, 176, 176, 176, 41, 176, 176, 37, 176, 90, 55, 59, 58, 176, 176, 176, 176, 64, 44, 45, 53, 65, 46, 42, 54, 38, 57, 60, 56, 70, 176, 61, 176, 63, 42, 62, 55, 59, 58, 176, 38, 71, 75, 64, 80, 72, 53, 65, 176, 54, 79, 57, 60, 56, 176, 70, 176, 61, 66, 63, 62, 73, 176, 67, 176, 176, 68, 71, 75, 69, 82, 72, 83, 76, 42, 176, 176, 74, 176, 176, 87, 176, 176, 38, 66, 85, 176, 73, 67, 92, 176, 176, 68, 176, 69, 91, 82, 86, 83, 76, 176, 84, 74, 176, 89, 88, 87, 95, 93, 176, 176, 85, 176, 94, 97, 92, 98, 96, 100, 176, 176, 91, 86, 176, 79, 80, 84, 99, 102, 89, 176, 88, 176, 95, 93, 176, 176, 106, 94, 176, 97, 103, 98, 96, 100, 176, 90, 101, 112, 176, 176, 173, 107, 99, 102, 42, 109, 38, 108, 176, 105, 115, 176, 106, 176, 116, 103, 176, 110, 176, 111, 114, 101, 113, 112, 176, 118, 176, 107, 176, 176, 176, 109, 108, 117, 105, 119, 176, 176, 122, 124, 116, 120, 176, 110, 176, 111, 114, 113, 176, 176, 128, 118, 121, 126, 176, 129, 176, 176, 123, 117, 127, 119, 115, 133, 122, 124, 176, 120, 142, 161, 176, 125, 136, 135, 130, 176, 128, 134, 121, 126, 129, 143, 176, 123, 131, 176, 127, 144, 133, 176, 132, 137, 176, 142, 138, 176, 125, 176, 136, 135, 130, 139, 134, 145, 140, 146, 141, 143, 176, 176, 131, 153, 147, 144, 148, 132, 152, 137, 176, 154, 138, 155, 176, 159, 151, 158, 149, 139, 160, 145, 140, 146, 141, 162, 163, 164, 153, 147, 161, 150, 148, 172, 152, 156, 166, 154, 167, 155, 157, 159, 158, 168, 149, 169, 160, 170, 171, 173, 162, 175, 163, 164, 151, 176, 150, 176, 172, 165, 156, 176, 166, 176, 167, 176, 157, 176, 176, 168, 176, 169, 176, 170, 171, 176, 176, 175, 174, 176, 176, 176, 176, 176, 165, 81, 104, 176, 176, 176, 81, 43, 40, 36, 34, 78, 176, 50, 43, 39, 35, 34, 176, 174, 36, 36, 36, 40, 40, 40, 77, 77, 3, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176 } ; static yyconst flex_int16_t yy_chk[503] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 10, 12, 12, 13, 13, 13, 15, 15, 17, 16, 19, 40, 18, 21, 36, 24, 62, 17, 21, 19, 22, 23, 62, 27, 24, 45, 45, 16, 24, 179, 10, 16, 7, 18, 21, 17, 27, 30, 22, 28, 23, 40, 22, 17, 21, 19, 25, 36, 28, 30, 24, 42, 28, 16, 24, 26, 16, 38, 18, 21, 17, 31, 27, 29, 22, 25, 23, 22, 29, 53, 25, 59, 54, 26, 28, 30, 26, 53, 28, 54, 31, 42, 58, 57, 29, 55, 64, 59, 61, 63, 38, 25, 57, 60, 29, 25, 64, 66, 67, 26, 65, 26, 63, 53, 58, 54, 31, 71, 55, 29, 70, 61, 60, 59, 67, 65, 69, 72, 57, 73, 66, 70, 64, 71, 69, 73, 75, 76, 63, 58, 74, 79, 80, 55, 72, 75, 61, 91, 60, 85, 67, 65, 84, 87, 85, 66, 88, 70, 76, 71, 69, 73, 86, 90, 74, 91, 95, 92, 172, 86, 72, 75, 80, 88, 79, 87, 89, 84, 94, 93, 85, 97, 95, 76, 94, 89, 150, 90, 93, 74, 92, 91, 96, 97, 98, 86, 99, 101, 105, 88, 87, 96, 84, 98, 100, 102, 101, 105, 95, 99, 117, 89, 108, 90, 93, 92, 106, 110, 111, 97, 100, 108, 118, 111, 120, 149, 102, 96, 110, 98, 115, 117, 101, 105, 119, 99, 128, 156, 147, 106, 120, 119, 115, 156, 111, 118, 100, 108, 111, 129, 121, 102, 115, 123, 110, 130, 117, 135, 115, 121, 124, 128, 123, 125, 106, 127, 120, 119, 115, 124, 118, 131, 125, 132, 127, 129, 139, 136, 115, 144, 135, 130, 136, 115, 143, 121, 141, 145, 123, 146, 148, 154, 151, 153, 139, 124, 155, 131, 125, 132, 127, 157, 158, 160, 144, 135, 161, 141, 136, 168, 143, 148, 162, 145, 163, 146, 151, 154, 153, 164, 139, 165, 155, 166, 167, 173, 157, 174, 158, 160, 142, 140, 141, 138, 168, 161, 148, 137, 162, 134, 163, 133, 151, 126, 122, 164, 116, 165, 114, 166, 167, 113, 112, 174, 173, 109, 107, 103, 83, 82, 161, 81, 77, 68, 56, 46, 44, 43, 41, 37, 34, 33, 20, 14, 11, 9, 6, 5, 3, 173, 177, 177, 177, 178, 178, 178, 180, 180, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176 } ; static yy_state_type yy_last_accepting_state; static char *yy_last_accepting_cpos; extern int yy_flex_debug; int yy_flex_debug = 0; /* The intent behind this definition is that it'll catch * any uses of REJECT which flex missed. */ #define REJECT reject_used_but_not_detected #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET char *yytext; #line 1 "KDbSqlScanner.l" /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004-2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #line 22 "KDbSqlScanner.l" #include "sqlparser.h" #include "KDb.h" #include "KDbExpression.h" #include "KDbParser.h" #include "KDbSqlTypes.h" #include "kdb_debug.h" extern int globalCurrentPos; extern QByteArray globalToken; #define YY_NO_UNPUT #define ECOUNT globalCurrentPos += yyleng; globalToken = yytext extern void setError(const QString& errDesc); extern void setError(const QString& errName, const QString& errDesc); /* *** Please reflect changes to this file in ../driver_p.cpp *** */ /*identifier [a-zA-Z_][a-zA-Z_0-9]* */ /* quoted_identifier (\"[a-zA-Z_0-9]+\") */ #line 663 "generated/sqlscanner.cpp" #define INITIAL 0 #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif static int yy_init_globals (void ); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int yylex_destroy (void ); int yyget_debug (void ); void yyset_debug (int debug_flag ); YY_EXTRA_TYPE yyget_extra (void ); void yyset_extra (YY_EXTRA_TYPE user_defined ); FILE *yyget_in (void ); void yyset_in (FILE * in_str ); FILE *yyget_out (void ); void yyset_out (FILE * out_str ); yy_size_t yyget_leng (void ); char *yyget_text (void ); int yyget_lineno (void ); void yyset_lineno (int line_number ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int yywrap (void ); #else extern int yywrap (void ); #endif #endif static void yyunput (int c,char *buf_ptr ); #ifndef yytext_ptr static void yy_flex_strncpy (char *,yyconst char *,int ); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (yyconst char * ); #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (void ); #else static int input (void ); #endif #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #define YY_READ_BUF_SIZE 8192 #endif /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ #define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ #ifndef YY_INPUT #define YY_INPUT(buf,result,max_size) \ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ size_t n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ if ( c == '\n' ) \ buf[n++] = (char) c; \ if ( c == EOF && ferror( yyin ) ) \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ result = n; \ } \ else \ { \ errno=0; \ - while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + while ( (result = (int) fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ break; \ } \ errno=0; \ clearerr(yyin); \ } \ }\ \ #endif /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. */ #ifndef yyterminate #define yyterminate() return YY_NULL #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) #endif /* end tables serialization structures and prototypes */ /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int yylex (void); #define YY_DECL int yylex (void) #endif /* !YY_DECL */ /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ #ifndef YY_USER_ACTION #define YY_USER_ACTION #endif /* Code executed at the end of each rule. */ #ifndef YY_BREAK #define YY_BREAK break; #endif #define YY_RULE_SETUP \ YY_USER_ACTION /** The main scanner function which does all the work. */ YY_DECL { register yy_state_type yy_current_state; register char *yy_cp, *yy_bp; register int yy_act; #line 60 "KDbSqlScanner.l" #line 849 "generated/sqlscanner.cpp" if ( !(yy_init) ) { (yy_init) = 1; #ifdef YY_USER_INIT YY_USER_INIT; #endif if ( ! (yy_start) ) (yy_start) = 1; /* first start state */ if ( ! yyin ) yyin = stdin; if ( ! yyout ) yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { yyensure_buffer_stack (); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer(yyin,YY_BUF_SIZE ); } yy_load_buffer_state( ); } while ( 1 ) /* loops until end-of-file is reached */ { yy_cp = (yy_c_buf_p); /* Support of yytext. */ *yy_cp = (yy_hold_char); /* yy_bp points to the position in yy_ch_buf of the start of * the current run. */ yy_bp = yy_cp; yy_current_state = (yy_start); yy_match: do { register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 177 ) yy_c = yy_meta[(unsigned int) yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c]; ++yy_cp; } while ( yy_current_state != 176 ); yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); yy_find_action: yy_act = yy_accept[yy_current_state]; YY_DO_BEFORE_ACTION; do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ /* undo the effects of YY_DO_BEFORE_ACTION */ *yy_cp = (yy_hold_char); yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); goto yy_find_action; case 1: YY_RULE_SETUP #line 63 "KDbSqlScanner.l" { ECOUNT; return NOT_EQUAL; } YY_BREAK case 2: YY_RULE_SETUP #line 68 "KDbSqlScanner.l" { ECOUNT; return NOT_EQUAL2; } YY_BREAK case 3: YY_RULE_SETUP #line 73 "KDbSqlScanner.l" { ECOUNT; return '='; } YY_BREAK case 4: YY_RULE_SETUP #line 78 "KDbSqlScanner.l" { ECOUNT; return LESS_OR_EQUAL; } YY_BREAK case 5: YY_RULE_SETUP #line 83 "KDbSqlScanner.l" { ECOUNT; return GREATER_OR_EQUAL; } YY_BREAK case 6: YY_RULE_SETUP #line 88 "KDbSqlScanner.l" { ECOUNT; return SQL_IN; } YY_BREAK case 7: YY_RULE_SETUP #line 93 "KDbSqlScanner.l" { //! @todo what about hex or octal values? //we're using QString:toLongLong() here because atoll() is not so portable: ECOUNT; bool ok; yylval.integerValue = QByteArray(yytext).toLongLong(&ok); if (!ok) { setError(KDbParser::tr("Invalid integer number"), KDbParser::tr("This integer number may be too large.")); return SCAN_ERROR; } return INTEGER_CONST; } YY_BREAK case 8: YY_RULE_SETUP #line 106 "KDbSqlScanner.l" { ECOUNT; yylval.binaryValue = new QByteArray(yytext, yyleng); return REAL_CONST; } YY_BREAK case 9: YY_RULE_SETUP #line 112 "KDbSqlScanner.l" { ECOUNT; return AND; } YY_BREAK case 10: YY_RULE_SETUP #line 117 "KDbSqlScanner.l" { ECOUNT; return AS; } YY_BREAK case 11: YY_RULE_SETUP #line 122 "KDbSqlScanner.l" { ECOUNT; return CREATE; } YY_BREAK case 12: YY_RULE_SETUP #line 127 "KDbSqlScanner.l" { ECOUNT; return FROM; } YY_BREAK case 13: YY_RULE_SETUP #line 132 "KDbSqlScanner.l" { ECOUNT; return SQL_TYPE; } YY_BREAK case 14: YY_RULE_SETUP #line 137 "KDbSqlScanner.l" { ECOUNT; return JOIN; } YY_BREAK case 15: YY_RULE_SETUP #line 142 "KDbSqlScanner.l" { ECOUNT; return LEFT; } YY_BREAK case 16: YY_RULE_SETUP #line 147 "KDbSqlScanner.l" { ECOUNT; return LIKE; } YY_BREAK case 17: /* rule 17 can match eol */ YY_RULE_SETUP #line 152 "KDbSqlScanner.l" { ECOUNT; return NOT_LIKE; } YY_BREAK case 18: YY_RULE_SETUP #line 157 "KDbSqlScanner.l" { ECOUNT; return BETWEEN; } YY_BREAK case 19: /* rule 19 can match eol */ YY_RULE_SETUP #line 162 "KDbSqlScanner.l" { ECOUNT; return NOT_BETWEEN; } YY_BREAK case 20: /* rule 20 can match eol */ YY_RULE_SETUP #line 167 "KDbSqlScanner.l" { ECOUNT; return NOT_SIMILAR_TO; } YY_BREAK case 21: /* rule 21 can match eol */ YY_RULE_SETUP #line 172 "KDbSqlScanner.l" { ECOUNT; return SIMILAR_TO; } YY_BREAK case 22: /* rule 22 can match eol */ YY_RULE_SETUP #line 177 "KDbSqlScanner.l" { ECOUNT; return SQL_IS_NOT_NULL; } YY_BREAK case 23: /* rule 23 can match eol */ YY_RULE_SETUP #line 182 "KDbSqlScanner.l" { ECOUNT; return SQL_IS_NULL; } YY_BREAK case 24: YY_RULE_SETUP #line 187 "KDbSqlScanner.l" { ECOUNT; return NOT; } YY_BREAK case 25: YY_RULE_SETUP #line 192 "KDbSqlScanner.l" { ECOUNT; return SQL_IS; } YY_BREAK case 26: YY_RULE_SETUP #line 197 "KDbSqlScanner.l" { ECOUNT; return SQL_NULL; } YY_BREAK case 27: YY_RULE_SETUP #line 202 "KDbSqlScanner.l" { ECOUNT; return SQL_TRUE; } YY_BREAK case 28: YY_RULE_SETUP #line 207 "KDbSqlScanner.l" { ECOUNT; return SQL_FALSE; } YY_BREAK case 29: YY_RULE_SETUP #line 212 "KDbSqlScanner.l" { ECOUNT; return SQL_ON; } YY_BREAK case 30: YY_RULE_SETUP #line 217 "KDbSqlScanner.l" { ECOUNT; return OR; } YY_BREAK case 31: YY_RULE_SETUP #line 222 "KDbSqlScanner.l" { /* also means OR for numbers (mysql) */ ECOUNT; return CONCATENATION; } YY_BREAK case 32: YY_RULE_SETUP #line 227 "KDbSqlScanner.l" { ECOUNT; return BITWISE_SHIFT_LEFT; } YY_BREAK case 33: YY_RULE_SETUP #line 232 "KDbSqlScanner.l" { ECOUNT; return BITWISE_SHIFT_RIGHT; } YY_BREAK case 34: YY_RULE_SETUP #line 237 "KDbSqlScanner.l" { ECOUNT; return XOR; } YY_BREAK case 35: YY_RULE_SETUP #line 242 "KDbSqlScanner.l" { ECOUNT; return RIGHT; } YY_BREAK case 36: YY_RULE_SETUP #line 247 "KDbSqlScanner.l" { ECOUNT; return SELECT; } YY_BREAK case 37: YY_RULE_SETUP #line 252 "KDbSqlScanner.l" { ECOUNT; return TABLE; } YY_BREAK case 38: YY_RULE_SETUP #line 257 "KDbSqlScanner.l" { ECOUNT; return WHERE; } YY_BREAK case 39: YY_RULE_SETUP #line 262 "KDbSqlScanner.l" { ECOUNT; return ORDER; } YY_BREAK case 40: YY_RULE_SETUP #line 267 "KDbSqlScanner.l" { ECOUNT; return BY; } YY_BREAK case 41: YY_RULE_SETUP #line 272 "KDbSqlScanner.l" { ECOUNT; return ASC; } YY_BREAK case 42: YY_RULE_SETUP #line 277 "KDbSqlScanner.l" { ECOUNT; return DESC; } YY_BREAK case 43: /* rule 43 can match eol */ YY_RULE_SETUP #line 282 "KDbSqlScanner.l" { ECOUNT; //kdbDebug() << "{string} yytext: '" << yytext << "' (" << yyleng << ")"; int errorPosition; const QString unescaped( KDb::unescapeString(QString::fromUtf8(yytext+1, yyleng-2), yytext[0], &errorPosition)); if (errorPosition >= 0) { // sanity check setError(KDbParser::tr("Invalid string"), KDbParser::tr("Invalid character in string")); return SCAN_ERROR; } yylval.stringValue = new QString(unescaped); return CHARACTER_STRING_LITERAL; /* "ZZZ" sentinel for script */ } YY_BREAK case 44: YY_RULE_SETUP #line 299 "KDbSqlScanner.l" { kdbDebug() << "{identifier} yytext: '" << yytext << "' (" << yyleng << ")"; ECOUNT; if (yytext[0]>='0' && yytext[0]<='9') { setError(KDbParser::tr("Invalid identifier"), KDbParser::tr("Identifiers should start with a letter or '_' character")); return SCAN_ERROR; } yylval.stringValue = new QString(QString::fromUtf8(yytext, yyleng)); return IDENTIFIER; } YY_BREAK case 45: /* rule 45 can match eol */ YY_RULE_SETUP #line 311 "KDbSqlScanner.l" { kdbDebug() << "{query_parameter} yytext: '" << yytext << "' (" << yyleng << ")"; ECOUNT; yylval.stringValue = new QString(QString::fromUtf8(yytext+1, yyleng-2)); return QUERY_PARAMETER; } YY_BREAK case 46: /* rule 46 can match eol */ YY_RULE_SETUP #line 318 "KDbSqlScanner.l" { ECOUNT; } YY_BREAK case 47: YY_RULE_SETUP #line 322 "KDbSqlScanner.l" { kdbDebug() << "char: '" << yytext[0] << "'"; ECOUNT; return yytext[0]; } YY_BREAK case 48: YY_RULE_SETUP #line 328 "KDbSqlScanner.l" ECHO; YY_BREAK #line 1348 "generated/sqlscanner.cpp" case YY_STATE_EOF(INITIAL): yyterminate(); case YY_END_OF_BUFFER: { /* Amount of text matched not including the EOB char. */ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; /* Undo the effects of YY_DO_BEFORE_ACTION. */ *yy_cp = (yy_hold_char); YY_RESTORE_YY_MORE_OFFSET if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) { /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called * yylex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a * back-up) that will match for the new input source. */ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; } /* Note that here we test for yy_c_buf_p "<=" to the position * of the first EOB in the buffer, since yy_c_buf_p will * already have been incremented past the NUL character * (since all states make transitions on EOB to the * end-of-buffer state). Contrast this with the test * in input(). */ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) { /* This was really a NUL. */ yy_state_type yy_next_state; (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( ); /* Okay, we're now positioned to make the NUL * transition. We couldn't have * yy_get_previous_state() go ahead and do it * for us because it doesn't know how to deal * with the possibility of jamming (and we don't * want to build jamming into it because then it * will run more slowly). */ yy_next_state = yy_try_NUL_trans( yy_current_state ); yy_bp = (yytext_ptr) + YY_MORE_ADJ; if ( yy_next_state ) { /* Consume the NUL. */ yy_cp = ++(yy_c_buf_p); yy_current_state = yy_next_state; goto yy_match; } else { yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); goto yy_find_action; } } else switch ( yy_get_next_buffer( ) ) { case EOB_ACT_END_OF_FILE: { (yy_did_buffer_switch_on_eof) = 0; if ( yywrap( ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up * yytext, we can now set up * yy_c_buf_p so that if some total * hoser (like flex itself) wants to * call the scanner after we return the * YY_NULL, it'll still work - another * YY_NULL will get returned. */ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; yy_act = YY_STATE_EOF(YY_START); goto do_action; } else { if ( ! (yy_did_buffer_switch_on_eof) ) YY_NEW_FILE; } break; } case EOB_ACT_CONTINUE_SCAN: (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( ); yy_cp = (yy_c_buf_p); yy_bp = (yytext_ptr) + YY_MORE_ADJ; goto yy_match; case EOB_ACT_LAST_MATCH: (yy_c_buf_p) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; yy_current_state = yy_get_previous_state( ); yy_cp = (yy_c_buf_p); yy_bp = (yytext_ptr) + YY_MORE_ADJ; goto yy_find_action; } break; } default: YY_FATAL_ERROR( "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ } /* end of yylex */ /* yy_get_next_buffer - try to read in a new buffer * * Returns a code representing an action: * EOB_ACT_LAST_MATCH - * EOB_ACT_CONTINUE_SCAN - continue scanning from current position * EOB_ACT_END_OF_FILE - end of file */ static int yy_get_next_buffer (void) { register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; register char *source = (yytext_ptr); register int number_to_move, i; int ret_val; if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) YY_FATAL_ERROR( "fatal flex scanner internal error--end of buffer missed" ); if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) { /* Don't try to fill the buffer, so this is an EOF. */ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) { /* We matched a single character, the EOB, so * treat this as a final EOF. */ return EOB_ACT_END_OF_FILE; } else { /* We matched some text prior to the EOB, first * process it. */ return EOB_ACT_LAST_MATCH; } } /* Try to read more data. */ /* First move last chars to start of buffer. */ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; else { - yy_size_t num_to_read = + int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) ((yy_c_buf_p) - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { yy_size_t new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; else b->yy_buf_size *= 2; b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); } else /* Can't grow it, we don't own it. */ b->yy_ch_buf = 0; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "fatal error - scanner input buffer overflow" ); (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; - num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + num_to_read = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; } if ( num_to_read > YY_READ_BUF_SIZE ) num_to_read = YY_READ_BUF_SIZE; /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), (yy_n_chars), num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } if ( (yy_n_chars) == 0 ) { if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; yyrestart(yyin ); } else { ret_val = EOB_ACT_LAST_MATCH; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_EOF_PENDING; } } else ret_val = EOB_ACT_CONTINUE_SCAN; - if ((yy_size_t) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + if ((int) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ - yy_size_t new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); } (yy_n_chars) += number_to_move; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; return ret_val; } /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (void) { register yy_state_type yy_current_state; register char *yy_cp; yy_current_state = (yy_start); for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) { register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 177 ) yy_c = yy_meta[(unsigned int) yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c]; } return yy_current_state; } /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis * next_state = yy_try_NUL_trans( current_state ); */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) { register int yy_is_jam; register char *yy_cp = (yy_c_buf_p); register YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; (yy_last_accepting_cpos) = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 177 ) yy_c = yy_meta[(unsigned int) yy_c]; } - yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c]; yy_is_jam = (yy_current_state == 176); return yy_is_jam ? 0 : yy_current_state; } static void yyunput (int c, register char * yy_bp ) { register char *yy_cp; yy_cp = (yy_c_buf_p); /* undo effects of setting up yytext */ *yy_cp = (yy_hold_char); if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) { /* need to shift things up to make room */ /* +2 for EOB chars. */ - register yy_size_t number_to_move = (yy_n_chars) + 2; + register int number_to_move = (yy_n_chars) + 2; register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; register char *source = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) *--dest = *--source; yy_cp += (int) (dest - source); yy_bp += (int) (dest - source); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = - (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + (yy_n_chars) = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size; if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) YY_FATAL_ERROR( "flex scanner push-back overflow" ); } *--yy_cp = (char) c; (yytext_ptr) = yy_bp; (yy_hold_char) = *yy_cp; (yy_c_buf_p) = yy_cp; } #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (void) #else static int input (void) #endif { int c; *(yy_c_buf_p) = (yy_hold_char); if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) { /* yy_c_buf_p now points to the character we want to return. * If this occurs *before* the EOB characters, then it's a * valid NUL; if not, then we've hit the end of the buffer. */ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) /* This was really a NUL. */ *(yy_c_buf_p) = '\0'; else { /* need more input */ yy_size_t offset = (yy_c_buf_p) - (yytext_ptr); ++(yy_c_buf_p); switch ( yy_get_next_buffer( ) ) { case EOB_ACT_LAST_MATCH: /* This happens because yy_g_n_b() * sees that we've accumulated a * token and flags that we need to * try matching the token before * proceeding. But for input(), * there's no matching to consider. * So convert the EOB_ACT_LAST_MATCH * to EOB_ACT_END_OF_FILE. */ /* Reset buffer status. */ yyrestart(yyin ); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { if ( yywrap( ) ) return EOF; if ( ! (yy_did_buffer_switch_on_eof) ) YY_NEW_FILE; #ifdef __cplusplus return yyinput(); #else return input(); #endif } case EOB_ACT_CONTINUE_SCAN: (yy_c_buf_p) = (yytext_ptr) + offset; break; } } } c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ *(yy_c_buf_p) = '\0'; /* preserve yytext */ (yy_hold_char) = *++(yy_c_buf_p); return c; } #endif /* ifndef YY_NO_INPUT */ /** Immediately switch to a different input stream. * @param input_file A readable stream. * * @note This function does not reset the start condition to @c INITIAL . */ void yyrestart (FILE * input_file ) { if ( ! YY_CURRENT_BUFFER ){ yyensure_buffer_stack (); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer(yyin,YY_BUF_SIZE ); } yy_init_buffer(YY_CURRENT_BUFFER,input_file ); yy_load_buffer_state( ); } /** Switch to a different input buffer. * @param new_buffer The new input buffer. * */ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) { /* TODO. We should be able to replace this entire function body * with * yypop_buffer_state(); * yypush_buffer_state(new_buffer); */ yyensure_buffer_stack (); if ( YY_CURRENT_BUFFER == new_buffer ) return; if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *(yy_c_buf_p) = (yy_hold_char); YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } YY_CURRENT_BUFFER_LVALUE = new_buffer; yy_load_buffer_state( ); /* We don't actually know whether we did this switch during * EOF (yywrap()) processing, but the only time this flag * is looked at is after yywrap() is called, so it's safe * to go ahead and always set it. */ (yy_did_buffer_switch_on_eof) = 1; } static void yy_load_buffer_state (void) { (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; (yy_hold_char) = *(yy_c_buf_p); } /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. * * @return the allocated buffer state. */ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) { YY_BUFFER_STATE b; b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ); if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_is_our_buffer = 1; yy_init_buffer(b,file ); return b; } /** Destroy the buffer. * @param b a buffer created with yy_create_buffer() * */ void yy_delete_buffer (YY_BUFFER_STATE b ) { if ( ! b ) return; if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) yyfree((void *) b->yy_ch_buf ); yyfree((void *) b ); } /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a yyrestart() or at EOF. */ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) { int oerrno = errno; yy_flush_buffer(b ); b->yy_input_file = file; b->yy_fill_buffer = 1; /* If b is the current buffer, then yy_init_buffer was _probably_ * called from yyrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ b->yy_bs_lineno = 1; b->yy_bs_column = 0; } b->yy_is_interactive = 0; errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * */ void yy_flush_buffer (YY_BUFFER_STATE b ) { if ( ! b ) return; b->yy_n_chars = 0; /* We always need two end-of-buffer characters. The first causes * a transition to the end-of-buffer state. The second causes * a jam in that state. */ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; b->yy_buf_pos = &b->yy_ch_buf[0]; b->yy_at_bol = 1; b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) yy_load_buffer_state( ); } /** Pushes the new state onto the stack. The new state becomes * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. * */ void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) { if (new_buffer == NULL) return; yyensure_buffer_stack(); /* This block is copied from yy_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *(yy_c_buf_p) = (yy_hold_char); YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } /* Only push if top exists. Otherwise, replace top. */ if (YY_CURRENT_BUFFER) (yy_buffer_stack_top)++; YY_CURRENT_BUFFER_LVALUE = new_buffer; /* copied from yy_switch_to_buffer. */ yy_load_buffer_state( ); (yy_did_buffer_switch_on_eof) = 1; } /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * */ void yypop_buffer_state (void) { if (!YY_CURRENT_BUFFER) return; yy_delete_buffer(YY_CURRENT_BUFFER ); YY_CURRENT_BUFFER_LVALUE = NULL; if ((yy_buffer_stack_top) > 0) --(yy_buffer_stack_top); if (YY_CURRENT_BUFFER) { yy_load_buffer_state( ); (yy_did_buffer_switch_on_eof) = 1; } } /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ static void yyensure_buffer_stack (void) { yy_size_t num_to_alloc; if (!(yy_buffer_stack)) { /* First allocation is just for 2 elements, since we don't know if this * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ num_to_alloc = 1; (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc (num_to_alloc * sizeof(struct yy_buffer_state*) ); if ( ! (yy_buffer_stack) ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); (yy_buffer_stack_max) = num_to_alloc; (yy_buffer_stack_top) = 0; return; } if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ /* Increase the buffer to prepare for a possible push. */ int grow_size = 8 /* arbitrary grow size */; num_to_alloc = (yy_buffer_stack_max) + grow_size; (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc ((yy_buffer_stack), num_to_alloc * sizeof(struct yy_buffer_state*) ); if ( ! (yy_buffer_stack) ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); /* zero only the new slots.*/ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); (yy_buffer_stack_max) = num_to_alloc; } } /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) { YY_BUFFER_STATE b; if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ return 0; b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; b->yy_input_file = 0; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; yy_switch_to_buffer(b ); return b; } /** Setup the input buffer state to scan a string. The next call to yylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * yy_scan_bytes() instead. */ YY_BUFFER_STATE yy_scan_string (yyconst char * yystr ) { - return yy_scan_bytes(yystr,strlen(yystr) ); + return yy_scan_bytes(yystr,(int) strlen(yystr) ); } /** Setup the input buffer state to scan the given bytes. The next call to yylex() will * scan from a @e copy of @a bytes. * @param yybytes the byte buffer to scan * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_bytes (yyconst char * yybytes, yy_size_t _yybytes_len ) { YY_BUFFER_STATE b; char *buf; yy_size_t n; int i; /* Get memory for full buffer, including space for trailing EOB's. */ - n = _yybytes_len + 2; + n = (yy_size_t) _yybytes_len + 2; buf = (char *) yyalloc(n ); if ( ! buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; b = yy_scan_buffer(buf,n ); if ( ! b ) YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. */ b->yy_is_our_buffer = 1; return b; } #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif static void yy_fatal_error (yyconst char* msg ) { (void) fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } /* Redefine yyless() so it works in section 3 code. */ #undef yyless #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = (yy_hold_char); \ (yy_c_buf_p) = yytext + yyless_macro_arg; \ (yy_hold_char) = *(yy_c_buf_p); \ *(yy_c_buf_p) = '\0'; \ yyleng = yyless_macro_arg; \ } \ while ( 0 ) /* Accessor methods (get/set functions) to struct members. */ /** Get the current line number. * */ int yyget_lineno (void) { return yylineno; } /** Get the input stream. * */ FILE *yyget_in (void) { return yyin; } /** Get the output stream. * */ FILE *yyget_out (void) { return yyout; } /** Get the length of the current token. * */ yy_size_t yyget_leng (void) { return yyleng; } /** Get the current token. * */ char *yyget_text (void) { return yytext; } /** Set the current line number. * @param line_number * */ void yyset_lineno (int line_number ) { yylineno = line_number; } /** Set the input stream. This does not discard the current * input buffer. * @param in_str A readable stream. * * @see yy_switch_to_buffer */ void yyset_in (FILE * in_str ) { yyin = in_str ; } void yyset_out (FILE * out_str ) { yyout = out_str ; } int yyget_debug (void) { return yy_flex_debug; } void yyset_debug (int bdebug ) { yy_flex_debug = bdebug ; } static int yy_init_globals (void) { /* Initialization is the same as for the non-reentrant scanner. * This function is called from yylex_destroy(), so don't allocate here. */ (yy_buffer_stack) = 0; (yy_buffer_stack_top) = 0; (yy_buffer_stack_max) = 0; (yy_c_buf_p) = (char *) 0; (yy_init) = 0; (yy_start) = 0; /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else yyin = (FILE *) 0; yyout = (FILE *) 0; #endif /* For future reference: Set errno on error, since we are called by * yylex_init() */ return 0; } /* yylex_destroy is for both reentrant and non-reentrant scanners. */ int yylex_destroy (void) { /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ yy_delete_buffer(YY_CURRENT_BUFFER ); YY_CURRENT_BUFFER_LVALUE = NULL; yypop_buffer_state(); } /* Destroy the stack itself. */ yyfree((yy_buffer_stack) ); (yy_buffer_stack) = NULL; /* Reset the globals. This is important in a non-reentrant scanner so the next time * yylex() is called, initialization will occur. */ yy_init_globals( ); return 0; } /* * Internal utility routines. */ #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) { register int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (yyconst char * s ) { register int n; for ( n = 0; s[n]; ++n ) ; return n; } #endif void *yyalloc (yy_size_t size ) { return (void *) malloc( size ); } void *yyrealloc (void * ptr, yy_size_t size ) { /* The cast to (char *) in the following accommodates both * implementations that use char* generic pointers, and those * that use void* generic pointers. It works with the latter * because both ANSI C and C++ allow castless assignment from * any pointer type to void*, and deal with argument conversions * as though doing an assignment. */ return (void *) realloc( (char *) ptr, size ); } void yyfree (void * ptr ) { free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ } #define YYTABLES_NAME "yytables" #line 328 "KDbSqlScanner.l" void tokenize(const char *data) { yy_switch_to_buffer(yy_scan_string(data)); globalToken.clear(); globalCurrentPos = 0; } diff --git a/src/tools/KDbFieldValidator.cpp b/src/tools/KDbFieldValidator.cpp index 3548bd56..b4fdf032 100644 --- a/src/tools/KDbFieldValidator.cpp +++ b/src/tools/KDbFieldValidator.cpp @@ -1,87 +1,87 @@ /* This file is part of the KDE project Copyright (C) 2006 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbFieldValidator.h" #include "KDbField.h" #include "KDbLongLongValidator.h" #include #include #include using namespace KDbUtils; KDbFieldValidator::KDbFieldValidator(const KDbField &field, QWidget * parent) : KDbMultiValidator(parent) { //! @todo merge this code with KexiTableEdit code! //! @todo set maximum length validator //! @todo handle input mask (via QLineEdit::setInputMask() const KDbField::Type t = field.type(); // cache: evaluating type of expressions can be expensive if (KDbField::isIntegerType(t)) { - QValidator *validator = 0; + QValidator *validator = nullptr; const bool u = field.isUnsigned(); int bottom = 0, top = 0; if (t == KDbField::Byte) { bottom = u ? 0 : -0x80; top = u ? 0xff : 0x7f; } else if (t == KDbField::ShortInteger) { bottom = u ? 0 : -0x8000; top = u ? 0xffff : 0x7fff; } else if (t == KDbField::Integer) { bottom = u ? 0 : -0x7fffffff - 1; top = u ? 0xffffffff : 0x7fffffff; } else if (t == KDbField::BigInteger) { //! @todo handle unsigned (using ULongLongValidator) - validator = new KDbLongLongValidator(0); + validator = new KDbLongLongValidator(nullptr); } if (!validator) - validator = new QIntValidator(bottom, top, 0); //the default + validator = new QIntValidator(bottom, top, nullptr); //the default addSubvalidator(validator); } else if (KDbField::isFPNumericType(t)) { QValidator *validator; if (t == KDbField::Float) { if (field.isUnsigned()) //ok? - validator = new QDoubleValidator(0, 3.4e+38, field.scale(), 0); + validator = new QDoubleValidator(0, 3.4e+38, field.scale(), nullptr); else - validator = new QDoubleValidator((QObject*)0); + validator = new QDoubleValidator((QObject*)nullptr); } else {//double if (field.isUnsigned()) //ok? - validator = new QDoubleValidator(0, 1.7e+308, field.scale(), 0); + validator = new QDoubleValidator(0, 1.7e+308, field.scale(), nullptr); else - validator = new QDoubleValidator((QObject*)0); + validator = new QDoubleValidator((QObject*)nullptr); } addSubvalidator(validator); } else if (t == KDbField::Date) { //! @todo add validator // QValidator *validator = new KDateValidator(this); // setValidator( validator ); } else if (t == KDbField::Time) { //! @todo add validator } else if (t == KDbField::DateTime) { } else if (t == KDbField::Boolean) { //! @todo add BooleanValidator - addSubvalidator(new QIntValidator(0, 1, 0)); + addSubvalidator(new QIntValidator(0, 1, nullptr)); } } KDbFieldValidator::~KDbFieldValidator() { } diff --git a/src/tools/KDbFieldValidator.h b/src/tools/KDbFieldValidator.h index 97e468aa..89aa7c6e 100644 --- a/src/tools/KDbFieldValidator.h +++ b/src/tools/KDbFieldValidator.h @@ -1,46 +1,46 @@ /* This file is part of the KDE project Copyright (C) 2006 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOOLS_FIELDVALIDATOR_H #define KDB_TOOLS_FIELDVALIDATOR_H #include "KDbValidator.h" class KDbField; //! @short A validator for KDb data types /*! This can be used by QLineEdit or subclass to provide validated text entry. Curently is supports all integer types, floating point types and booleans. Internal validators like KIntValidator or KDbLongLongValidator are used. 'unsigned' and 'scale' parameters are taken into account when setting up internal validators. @todo date/time support for types @todo add validation of the maximum length and other field's properties */ class KDB_EXPORT KDbFieldValidator : public KDbMultiValidator { Q_OBJECT public: //! Setups the validator for @a field. Does not keep a pointer to @a field. - explicit KDbFieldValidator(const KDbField &field, QWidget * parent = 0); - ~KDbFieldValidator(); + explicit KDbFieldValidator(const KDbField &field, QWidget * parent = nullptr); + ~KDbFieldValidator() override; private: Q_DISABLE_COPY(KDbFieldValidator) }; #endif diff --git a/src/tools/KDbIdentifierValidator.h b/src/tools/KDbIdentifierValidator.h index ca99830f..a0567b3a 100644 --- a/src/tools/KDbIdentifierValidator.h +++ b/src/tools/KDbIdentifierValidator.h @@ -1,54 +1,54 @@ /* This file is part of the KDE project Copyright (C) 2003-2005 Jarosław Staniek Copyright (C) 2005 Martin Ellis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOOLS_IDENTIFIER_H #define KDB_TOOLS_IDENTIFIER_H #include "KDbValidator.h" //! Validates input for identifier. class KDB_EXPORT KDbIdentifierValidator : public KDbValidator { Q_OBJECT public: - explicit KDbIdentifierValidator(QObject * parent = 0); + explicit KDbIdentifierValidator(QObject * parent = nullptr); - virtual ~KDbIdentifierValidator(); + ~KDbIdentifierValidator() override; - virtual State validate(QString & input, int & pos) const; + State validate(QString & input, int & pos) const override; //! @return true if upper-case letters in the input are replaced to lower-case. //! @c false by default. bool isLowerCaseForced() const; //! If @a set is true, upper-case letters in the input are replaced to lower-case. void setLowerCaseForced(bool set); protected: - virtual KDbValidator::Result internalCheck(const QString &valueName, const QVariant& value, - QString *message, QString *details); + KDbValidator::Result internalCheck(const QString &valueName, const QVariant &value, + QString *message, QString *details) override; private: class Private; Private* const d; Q_DISABLE_COPY(KDbIdentifierValidator) }; #endif diff --git a/src/tools/KDbLongLongValidator.cpp b/src/tools/KDbLongLongValidator.cpp index 4e92b7b4..f479854e 100644 --- a/src/tools/KDbLongLongValidator.cpp +++ b/src/tools/KDbLongLongValidator.cpp @@ -1,149 +1,149 @@ /* This file is part of the KDE project Copyright (C) 2006-2016 Jarosław Staniek Based on KIntValidator code by Glen Parker This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDbLongLongValidator.h" #include class Q_DECL_HIDDEN KDbLongLongValidator::Private { public: Private() : min(0) , max(0) { } qint64 base; qint64 min; qint64 max; }; KDbLongLongValidator::KDbLongLongValidator(QWidget * parent, int base) : QValidator(parent) , d(new Private) { setBase(base); } KDbLongLongValidator::KDbLongLongValidator(qint64 bottom, qint64 top, QWidget * parent, int base) : QValidator(parent) , d(new Private) { setBase(base); setRange(bottom, top); } KDbLongLongValidator::~KDbLongLongValidator() { delete d; } QValidator::State KDbLongLongValidator::validate(QString &str, int &) const { bool ok; qint64 val = 0; QString newStr; newStr = str.trimmed(); if (d->base > 10) newStr = newStr.toUpper(); if (newStr == QString::fromLatin1("-")) {// a special case if ((d->min || d->max) && d->min >= 0) ok = false; else return QValidator::Acceptable; } else if (!newStr.isEmpty()) val = newStr.toLongLong(&ok, d->base); else { val = 0; ok = true; } if (! ok) return QValidator::Invalid; if ((! d->min && ! d->max) || (val >= d->min && val <= d->max)) return QValidator::Acceptable; if (d->max && d->min >= 0 && val < 0) return QValidator::Invalid; return QValidator::Acceptable; } void KDbLongLongValidator::fixup(QString &str) const { int dummy; qint64 val; QValidator::State state; state = validate(str, dummy); if (state == QValidator::Invalid || state == QValidator::Acceptable) return; if (! d->min && ! d->max) return; - val = str.toLongLong(0, d->base); + val = str.toLongLong(nullptr, d->base); if (val < d->min) val = d->min; if (val > d->max) val = d->max; str.setNum(val, d->base); } void KDbLongLongValidator::setRange(qint64 bottom, qint64 top) { d->min = bottom; d->max = top; if (d->max < d->min) d->max = d->min; } void KDbLongLongValidator::setBase(int base) { d->base = base; if (d->base < 2) d->base = 2; if (d->base > 36) d->base = 36; } qint64 KDbLongLongValidator::bottom() const { return d->min; } qint64 KDbLongLongValidator::top() const { return d->max; } int KDbLongLongValidator::base() const { return d->base; } diff --git a/src/tools/KDbLongLongValidator.h b/src/tools/KDbLongLongValidator.h index a2e5c7c2..9732201e 100644 --- a/src/tools/KDbLongLongValidator.h +++ b/src/tools/KDbLongLongValidator.h @@ -1,70 +1,70 @@ /* This file is part of the KDE project Copyright (C) 2006-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOOLS_LLONGVALIDATOR_H #define KDB_TOOLS_LLONGVALIDATOR_H #include "kdb_export.h" #include //! @short A validator for longlong data type. /*! This can be used by QLineEdit or subclass to provide validated text entry. Can be provided with a base value (default is 10), to allow the proper entry of hexadecimal, octal, or any other base numeric data. Based on KIntValidator code by Glen Parker */ class KDB_EXPORT KDbLongLongValidator : public QValidator { Q_OBJECT public: explicit KDbLongLongValidator(QWidget * parent, int base = 10); KDbLongLongValidator(qint64 bottom, qint64 top, QWidget * parent, int base = 10); - virtual ~KDbLongLongValidator(); + ~KDbLongLongValidator() override; //! Validates the text, and returns the result. Does not modify the parameters. - virtual State validate(QString &, int &) const; + State validate(QString &, int &) const override; //! Fixes the text if possible, providing a valid string. The parameter may be modified. - virtual void fixup(QString &) const; + void fixup(QString &) const override; //! Sets the minimum and maximum values allowed. virtual void setRange(qint64 bottom, qint64 top); //! Sets the numeric base value. virtual void setBase(int base); //! @return the current minimum value allowed virtual qint64 bottom() const; //! @return the current maximum value allowed virtual qint64 top() const; //! @return the current numeric base virtual int base() const; private: class Private; Private * const d; Q_DISABLE_COPY(KDbLongLongValidator) }; #endif diff --git a/src/tools/KDbObjectNameValidator.h b/src/tools/KDbObjectNameValidator.h index d99e6a8b..769ef132 100644 --- a/src/tools/KDbObjectNameValidator.h +++ b/src/tools/KDbObjectNameValidator.h @@ -1,49 +1,50 @@ /* This file is part of the KDE project Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDBOBJECTNAMEVALIDATOR_H #define KDBOBJECTNAMEVALIDATOR_H #include #include "KDbValidator.h" class KDbDriver; /*! Validates input: accepts if the name is not reserved for internal kexi objects. */ class KDB_EXPORT KDbObjectNameValidator : public KDbValidator { Q_OBJECT public: /*! @a drv is a KDb driver on which isSystemObjectName() will be called inside check(). If @a drv is 0, KDbDriver::isKDbSystemObjectName() static function is called instead. */ explicit KDbObjectNameValidator(const KDbDriver *drv, QObject * parent = nullptr); - virtual ~KDbObjectNameValidator(); + ~KDbObjectNameValidator() override; protected: - virtual KDbValidator::Result internalCheck(const QString &valueName, const QVariant& value, - QString *message, QString *details); + KDbValidator::Result internalCheck(const QString &valueName, const QVariant &value, + QString *message, QString *details) override; + private: class Private; Private * const d; Q_DISABLE_COPY(KDbObjectNameValidator) }; #endif diff --git a/src/tools/KDbUtils.cpp b/src/tools/KDbUtils.cpp index 0ac6ca40..375c3bb8 100644 --- a/src/tools/KDbUtils.cpp +++ b/src/tools/KDbUtils.cpp @@ -1,648 +1,648 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek Portions of kstandarddirs.cpp: Copyright (C) 1999 Sirtaj Singh Kang Copyright (C) 1999,2007 Stephan Kulow Copyright (C) 1999 Waldo Bastian Copyright (C) 2009 David Faure Portions of kshell.cpp: Copyright (c) 2003,2007 Oswald Buddenhagen This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KDb.h" #include "KDbConnection.h" #include "KDbDriverManager.h" #include "KDbUtils.h" #include "KDbUtils_p.h" #include "kdb_debug.h" #include "config-kdb.h" #include #include #include #include #include static const int SQUEEZED_TEXT_LIMIT = 1024; static const int SQUEEZED_TEXT_SUFFIX = 24; #ifdef Q_OS_WIN #include #ifdef _WIN32_WCE #include #endif #ifdef Q_OS_WIN64 //! @todo did not find a reliable way to fix with kdewin mingw header #define interface struct #endif #endif using namespace KDbUtils; class Q_DECL_HIDDEN Property::Private { public: Private() : isNull(true) {} Private(const QVariant &aValue, const QString &aCaption) : value(aValue), caption(aCaption), isNull(false) { } bool operator==(const Private &other) const { return std::tie(value, caption, isNull) == std::tie(other.value, other.caption, other.isNull); } QVariant value; //!< Property value QString caption; //!< User visible property caption bool isNull; }; Property::Property() : d(new Private) { } Property::Property(const QVariant &value, const QString &caption) : d(new Private(value, caption)) { } Property::Property(const Property &other) : d(new Private(*other.d)) { } Property::~Property() { delete d; } bool Property::operator==(const Property &other) const { return *d == *other.d; } bool Property::isNull() const { return d->isNull; } QVariant Property::value() const { return d->value; } void Property::setValue(const QVariant &value) { d->value = value; d->isNull = false; } QString Property::caption() const { return d->caption; } void Property::setCaption(const QString &caption) { d->caption = caption; d->isNull = false; } //--------- void KDbUtils::serializeMap(const QMap& map, QByteArray *array) { Q_ASSERT(array); QDataStream ds(array, QIODevice::WriteOnly); ds.setVersion(QDataStream::Qt_3_1); ds << map; } void KDbUtils::serializeMap(const QMap& map, QString *string) { Q_ASSERT(string); QByteArray array; QDataStream ds(&array, QIODevice::WriteOnly); ds.setVersion(QDataStream::Qt_3_1); ds << map; kdbDebug() << array[3] << array[4] << array[5]; const int size = array.size(); string->clear(); string->reserve(size); for (int i = 0; i < size; i++) { (*string)[i] = QChar(ushort(array[i]) + 1); } } QMap KDbUtils::deserializeMap(const QByteArray& array) { QMap map; QByteArray ba(array); QDataStream ds(&ba, QIODevice::ReadOnly); ds.setVersion(QDataStream::Qt_3_1); ds >> map; return map; } QMap KDbUtils::deserializeMap(const QString& string) { QByteArray array; const int size = string.length(); array.resize(size); for (int i = 0; i < size; i++) { array[i] = char(string[i].unicode() - 1); } QMap map; QDataStream ds(&array, QIODevice::ReadOnly); ds.setVersion(QDataStream::Qt_3_1); ds >> map; return map; } QString KDbUtils::stringToFileName(const QString& string) { QString _string(string); _string.replace(QRegularExpression(QLatin1String("[\\\\/:\\*?\"<>|]")), QLatin1String(" ")); return _string.simplified(); } void KDbUtils::simpleCrypt(QString *string) { Q_ASSERT(string); for (int i = 0; i < string->length(); i++) { ushort& unicode = (*string)[i].unicode(); unicode += (47 + i); } } bool KDbUtils::simpleDecrypt(QString *string) { Q_ASSERT(string); QString result(*string); for (int i = 0; i < result.length(); i++) { ushort& unicode = result[i].unicode(); if (unicode <= (47 + i)) { return false; } unicode -= (47 + i); } *string = result; return true; } QString KDbUtils::pointerToStringInternal(void* pointer, int size) { QString string; unsigned char* cstr_pointer = (unsigned char*) & pointer; for (int i = 0; i < size; i++) { QString s; s.sprintf("%2.2x", cstr_pointer[i]); string.append(s); } return string; } void* KDbUtils::stringToPointerInternal(const QString& string, int size) { if ((string.length() / 2) < size) return nullptr; QByteArray array; array.resize(size); bool ok; for (int i = 0; i < size; i++) { array[i] = (unsigned char)(string.midRef(i * 2, 2).toUInt(&ok, 16)); if (!ok) return nullptr; } return static_cast(array.data()); } //--------- //! @internal class Q_DECL_HIDDEN StaticSetOfStrings::Private { public: - Private() : array(0), set(0) {} + Private() : array(nullptr), set(nullptr) {} ~Private() { delete set; } const char* const * array; QSet *set; }; StaticSetOfStrings::StaticSetOfStrings() : d(new Private) { } StaticSetOfStrings::StaticSetOfStrings(const char* const array[]) : d(new Private) { setStrings(array); } StaticSetOfStrings::~StaticSetOfStrings() { delete d; } void StaticSetOfStrings::setStrings(const char* const array[]) { delete d->set; - d->set = 0; + d->set = nullptr; d->array = array; } bool StaticSetOfStrings::isEmpty() const { - return d->array == 0; + return d->array == nullptr; } bool StaticSetOfStrings::contains(const QByteArray& string) const { if (!d->set) { d->set = new QSet(); for (const char * const * p = d->array;*p;p++) d->set->insert(QByteArray::fromRawData(*p, qstrlen(*p))); } return d->set->contains(string); } //--------- #ifdef Q_OS_MACOS //! Internal, from kdelibs' kstandarddirs.cpp static QString getBundle(const QString& path, bool ignore) { QFileInfo info; QString bundle = path; bundle += QLatin1String(".app/Contents/MacOS/") + bundle.section(QLatin1Char('/'), -1); info.setFile( bundle ); FILE *file; if (file = fopen(info.absoluteFilePath().toUtf8().constData(), "r")) { fclose(file); struct stat _stat; if ((stat(info.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( ignore || (_stat.st_mode & S_IXUSR) ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { return bundle; } } } return QString(); } #endif //! Internal, from kdelibs' kstandarddirs.cpp static QString checkExecutable(const QString& path, bool ignoreExecBit) { #ifdef Q_OS_MACOS QString bundle = getBundle(path, ignoreExecBit); if (!bundle.isEmpty()) { return bundle; } #endif QFileInfo info(path); QFileInfo orig = info; #ifdef Q_OS_MACOS FILE *file; if (file = fopen(orig.absoluteFilePath().toUtf8().constData(), "r")) { fclose(file); struct stat _stat; if ((stat(orig.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) { return QString(); } if ( ignoreExecBit || (_stat.st_mode & S_IXUSR) ) { if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) { orig.makeAbsolute(); return orig.filePath(); } } } return QString(); #else if (info.exists() && info.isSymLink()) info = QFileInfo(info.canonicalFilePath()); if (info.exists() && ( ignoreExecBit || info.isExecutable() ) && info.isFile()) { // return absolute path, but without symlinks resolved in order to prevent // problems with executables that work differently depending on name they are // run as (for example gunzip) orig.makeAbsolute(); return orig.filePath(); } return QString(); #endif } //! Internal, from kdelibs' kstandarddirs.cpp #if defined _WIN32 || defined _WIN64 # define KPATH_SEPARATOR ';' # define ESCAPE '^' #else # define KPATH_SEPARATOR ':' # define ESCAPE '\\' #endif //! Internal, from kdelibs' kstandarddirs.cpp static inline QString equalizePath(QString &str) { #ifdef Q_OS_WIN // filter pathes through QFileInfo to have always // the same case for drive letters QFileInfo f(str); if (f.isAbsolute()) return f.absoluteFilePath(); else #endif return str; } //! Internal, from kdelibs' kstandarddirs.cpp static void tokenize(QStringList& tokens, const QString& str, const QString& delim) { const int len = str.length(); QString token; for(int index = 0; index < len; index++) { if (delim.contains(str[index])) { tokens.append(equalizePath(token)); token.clear(); } else { token += str[index]; } } if (!token.isEmpty()) { tokens.append(equalizePath(token)); } } //! Internal, based on kdelibs' kshell.cpp static QString tildeExpand(const QString &fname) { if (!fname.isEmpty() && fname[0] == QLatin1Char('~')) { int pos = fname.indexOf( QLatin1Char('/') ); QString ret = QDir::homePath(); // simplified if (pos > 0) { ret += fname.midRef(pos); } return ret; } else if (fname.length() > 1 && fname[0] == QLatin1Char(ESCAPE) && fname[1] == QLatin1Char('~')) { return fname.mid(1); } return fname; } //! Internal, from kdelibs' kstandarddirs.cpp static QStringList systemPaths(const QString& pstr) { QStringList tokens; QString p = pstr; if (p.isEmpty()) { p = QString::fromLocal8Bit( qgetenv( "PATH" ) ); } QString delimiters(QLatin1Char(KPATH_SEPARATOR)); delimiters += QLatin1Char('\b'); tokenize(tokens, p, delimiters); QStringList exePaths; // split path using : or \b as delimiters for(int i = 0; i < tokens.count(); i++) { exePaths << tildeExpand(tokens[ i ]); } return exePaths; } //! Internal, from kdelibs' kstandarddirs.cpp #ifdef Q_OS_WIN static QStringList executableExtensions() { QStringList ret = QString::fromLocal8Bit(qgetenv("PATHEXT")).split(QLatin1Char(';')); if (!ret.contains(QLatin1String(".exe"), Qt::CaseInsensitive)) { // If %PATHEXT% does not contain .exe, it is either empty, malformed, or distorted in ways that we cannot support, anyway. ret.clear(); ret << QLatin1String(".exe") << QLatin1String(".com") << QLatin1String(".bat") << QLatin1String(".cmd"); } return ret; } #endif //! Based on kdelibs' kstandarddirs.cpp QString KDbUtils::findExe(const QString& appname, const QString& path, FindExeOptions options) { #ifdef Q_OS_WIN QStringList executable_extensions = executableExtensions(); if (!executable_extensions.contains( appname.section(QLatin1Char('.'), -1, -1, QString::SectionIncludeLeadingSep), Qt::CaseInsensitive)) { QString found_exe; foreach (const QString& extension, executable_extensions) { found_exe = findExe(appname + extension, path, options); if (!found_exe.isEmpty()) { return found_exe; } } return QString(); } #endif // absolute or relative path? if (appname.contains(QDir::separator())) { return checkExecutable(appname, options & IgnoreExecBit); } QString p; QString result; const QStringList exePaths = systemPaths(path); for (QStringList::ConstIterator it = exePaths.begin(); it != exePaths.end(); ++it) { p = (*it) + QLatin1Char('/'); p += appname; // Check for executable in this tokenized path result = checkExecutable(p, options & IgnoreExecBit); if (!result.isEmpty()) { return result; } } // Not found in PATH, look into a bin dir p = QFile::decodeName(BIN_INSTALL_DIR "/"); p += appname; result = checkExecutable(p, options & IgnoreExecBit); if (!result.isEmpty()) { return result; } // If we reach here, the executable wasn't found. // So return empty string. return QString(); } // --- class Q_DECL_HIDDEN PropertySet::Private { public: Private() {} Private(const Private &other) { copy(other); } void copy(const Private &other) { for (AutodeletedHash::ConstIterator it(other.data.constBegin()); it != other.data.constEnd(); ++it) { data.insert(it.key(), new Property(*it.value())); } } bool operator==(const Private &other) const { if (data.count() != other.data.count()) { return false; } for (AutodeletedHash::ConstIterator it(other.data.constBegin()); it != other.data.constEnd(); ++it) { AutodeletedHash::ConstIterator findHere(data.constFind(it.key())); if (*findHere.value() != *it.value()) { return false; } } return true; } AutodeletedHash data; }; PropertySet::PropertySet() : d(new Private) { } PropertySet::PropertySet(const PropertySet &other) : d(new Private(*other.d)) { } PropertySet::~PropertySet() { delete d; } PropertySet& PropertySet::operator=(const PropertySet &other) { if (this != &other) { d->data.clear(); d->copy(*other.d); } return *this; } bool PropertySet::operator==(const PropertySet &other) const { return *d == *other.d; } void PropertySet::insert(const QByteArray &name, const QVariant &value, const QString &caption) { QString realCaption = caption; Property *existing = d->data.value(name); if (existing) { existing->setValue(value); if (!caption.isEmpty()) { // if not, reuse existing->setCaption(caption); } } else { if (KDb::isIdentifier(name)) { d->data.insert(name, new Property(value, realCaption)); } else { kdbWarning() << name << "cannot be used as property name"; } } } void PropertySet::setCaption(const QByteArray &name, const QString &caption) { Property *existing = d->data.value(name); if (existing) { existing->setCaption(caption); } } void PropertySet::setValue(const QByteArray &name, const QVariant &value) { Property *existing = d->data.value(name); if (existing) { existing->setValue(value); } } void PropertySet::remove(const QByteArray &name) { d->data.remove(name); } Property PropertySet::property(const QByteArray &name) const { Property *result = d->data.value(name); return result ? *result : Property(); } QList PropertySet::names() const { return d->data.keys(); } QVariant KDbUtils::squeezedValue(const QVariant &value) { switch(value.type()) { case QVariant::String: if (value.toString().length() > SQUEEZED_TEXT_LIMIT) { return QVariant(value.toString().left(SQUEEZED_TEXT_LIMIT - SQUEEZED_TEXT_SUFFIX) + QString::fromLatin1("...") + value.toString().right(SQUEEZED_TEXT_SUFFIX) + QString::fromLatin1("[%1 characters]").arg(value.toString().length())); } break; case QVariant::ByteArray: if (value.toByteArray().length() > SQUEEZED_TEXT_LIMIT) { return QVariant(value.toByteArray().left(SQUEEZED_TEXT_LIMIT - SQUEEZED_TEXT_SUFFIX) + "..." + value.toByteArray().right(SQUEEZED_TEXT_SUFFIX) + '[' + QByteArray::number(value.toByteArray().length()) + " bytes]"); } break; default: break; } //! @todo add BitArray, Url, Hash, Map, Pixmap, Image? return value; } diff --git a/src/tools/KDbUtils.h b/src/tools/KDbUtils.h index 6edef8db..8310f2c3 100644 --- a/src/tools/KDbUtils.h +++ b/src/tools/KDbUtils.h @@ -1,470 +1,470 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek Portions of kstandarddirs.cpp: Copyright (C) 1999 Sirtaj Singh Kang Copyright (C) 1999,2007 Stephan Kulow Copyright (C) 1999 Waldo Bastian Copyright (C) 2009 David Faure This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOOLS_UTILS_H #define KDB_TOOLS_UTILS_H #include "kdb_export.h" #include "config-kdb.h" #include #include #include #include namespace KDbUtils { //! @return true if @a o has parent @a par. inline bool hasParent(QObject* par, QObject* o) { if (!o || !par) return false; while (o && o != par) o = o->parent(); return o == par; } -//! @return parent object of @a o that is of type @a type or NULL if no such parent +//! @return parent object of @a o that is of type @a type or @c nullptr if no such parent template -inline type findParent(QObject* o, const char* className = 0) +inline type findParent(QObject* o, const char* className = nullptr) { if (!o) - return 0; + return nullptr; while ((o = o->parent())) { if (::qobject_cast< type >(o) && (!className || o->inherits(className))) return ::qobject_cast< type >(o); } - return 0; + return nullptr; } //! QDateTime - a hack needed because QVariant(QTime) has broken isNull() inline QDateTime stringToHackedQTime(const QString& s) { if (s.isEmpty()) return QDateTime(); // kdbDebug() << QDateTime( QDate(0,1,2), QTime::fromString( s, Qt::ISODate ) ).toString(Qt::ISODate); return QDateTime(QDate(0, 1, 2), QTime::fromString(s, Qt::ISODate)); } /*! Serializes @a map to the array pointed by @a array. KDbUtils::deserializeMap() can be used to deserialize this array back to map. */ KDB_EXPORT void serializeMap(const QMap& map, QByteArray *array); KDB_EXPORT void serializeMap(const QMap& map, QString *string); /*! @return a map deserialized from a byte array @a array. @a array need to contain data previously serialized using KexiUtils::serializeMap(). */ KDB_EXPORT QMap deserializeMap(const QByteArray& array); /*! @return a map deserialized from @a string. @a string need to contain data previously serialized using KexiUtils::serializeMap(). */ KDB_EXPORT QMap deserializeMap(const QString& string); /*! @return a valid filename converted from @a string by: - replacing \\, /, :, *, ?, ", <, >, |, \n \\t characters with a space - simplifing whitespace by removing redundant space characters using QString::simplified() Do not pass full paths here, but only filename strings. */ KDB_EXPORT QString stringToFileName(const QString& string); /*! Performs a simple @a string encryption using rot47-like algorithm. Each character's unicode value is increased by 47 + i (where i is index of the character). The resulting string still contains readable characters but some of them can be non-ASCII. @note Do not use this for data that can be accessed by attackers! */ KDB_EXPORT void simpleCrypt(QString *string); /*! Performs a simple @a string decryption using rot47-like algorithm, using opposite operations to KexiUtils::simpleCrypt(). @return true on success and false on failure. Failue means that one or more characters have unicode numbers smaller than value of 47 + i. On failure @a string is not altered. */ KDB_EXPORT bool simpleDecrypt(QString *string); //! @internal KDB_EXPORT QString pointerToStringInternal(void* pointer, int size); //! @internal KDB_EXPORT void* stringToPointerInternal(const QString& string, int size); //! @return a pointer @a pointer safely serialized to string template QString pointerToString(type *pointer) { return pointerToStringInternal(pointer, sizeof(type*)); } //! @return a pointer of type @a type safely deserialized from @a string template type* stringToPointer(const QString& string) { return static_cast(stringToPointerInternal(string, sizeof(type*))); } //! @return value converted to text, squeezed to reasonable length, useful for debugging //! If the value is not a byte array or string, or if it's not longer than 1024 characters, //! @a value is returned. //! @since 3.1 KDB_EXPORT QVariant squeezedValue(const QVariant &value); //! @short Autodeleting hash template class AutodeletedHash : public QHash { public: //! Creates autodeleting hash as a copy of @a other. //! Auto-deletion is not enabled as it would cause double deletion for items. //! If you enable auto-deletion on here, make sure you disable it in the @a other hash. AutodeletedHash(const AutodeletedHash& other) : QHash(other), m_autoDelete(false) {} //! Creates empty autodeleting hash. //! Auto-deletion is enabled by default. AutodeletedHash(bool autoDelete = true) : QHash(), m_autoDelete(autoDelete) {} ~AutodeletedHash() { if (m_autoDelete) { qDeleteAll(*this); } } void setAutoDelete(bool set) { m_autoDelete = set; } bool autoDelete() const { return m_autoDelete; } void clear() { if (m_autoDelete) { qDeleteAll(*this); } QHash::clear(); } typename QHash::iterator erase(typename QHash::iterator pos) { typename QHash::iterator it = QHash::erase(pos); if (m_autoDelete) { delete it.value(); it.value() = 0; return it; } } typename QHash::iterator insert(const Key &key, const T &value) { if (m_autoDelete) { T &oldValue = QHash::operator[](key); if (oldValue && oldValue != value) { // only delete if differs delete oldValue; } } return QHash::insert(key, value); } // note: no need to override insertMulti(), unite(), take(), they does not replace items private: bool m_autoDelete; }; //! @short Autodeleting list template class AutodeletedList : public QList { public: //! Creates autodeleting list as a copy of @a other. //! Auto-deletion is not enabled as it would cause double deletion for items. //! If you enable auto-deletion on here, make sure you disable it in the @a other list. AutodeletedList(const AutodeletedList& other) : QList(other), m_autoDelete(false) {} //! Creates empty autodeleting list. //! Auto-deletion is enabled by default. AutodeletedList(bool autoDelete = true) : QList(), m_autoDelete(autoDelete) {} ~AutodeletedList() { if (m_autoDelete) qDeleteAll(*this); } void setAutoDelete(bool set) { m_autoDelete = set; } bool autoDelete() const { return m_autoDelete; } void removeAt(int i) { T item = QList::takeAt(i); if (m_autoDelete) delete item; } void removeFirst() { T item = QList::takeFirst(); if (m_autoDelete) delete item; } void removeLast() { T item = QList::takeLast(); if (m_autoDelete) delete item; } void replace(int i, const T& value) { T item = QList::takeAt(i); insert(i, value); if (m_autoDelete) delete item; } void insert(int i, const T& value) { QList::insert(i, value); } typename QList::iterator erase(typename QList::iterator pos) { T item = *pos; typename QList::iterator res = QList::erase(pos); if (m_autoDelete) delete item; return res; } typename QList::iterator erase( typename QList::iterator afirst, typename QList::iterator alast) { if (!m_autoDelete) return QList::erase(afirst, alast); while (afirst != alast) { T item = *afirst; afirst = QList::erase(afirst); delete item; } return alast; } void pop_back() { removeLast(); } void pop_front() { removeFirst(); } int removeAll(const T& value) { if (!m_autoDelete) return QList::removeAll(value); typename QList::iterator it(QList::begin()); int removedCount = 0; while (it != QList::end()) { if (*it == value) { T item = *it; it = QList::erase(it); delete item; removedCount++; } else ++it; } return removedCount; } void clear() { if (!m_autoDelete) return QList::clear(); while (!QList::isEmpty()) { T item = QList::takeFirst(); delete item; } } private: bool m_autoDelete; }; //! @short Case insensitive hash container supporting QString or QByteArray keys. //! Keys are turned to lowercase before inserting. template class CaseInsensitiveHash : public QHash { public: CaseInsensitiveHash() : QHash() {} typename QHash::iterator find(const Key& key) const { return QHash::find(key.toLower()); } typename QHash::const_iterator constFind(const Key& key) const { return QHash::constFind(key.toLower()); } bool contains(const Key& key) const { return QHash::contains(key.toLower()); } int count(const Key& key) const { return QHash::count(key.toLower()); } typename QHash::iterator insert(const Key& key, const T& value) { return QHash::insert(key.toLower(), value); } typename QHash::iterator insertMulti(const Key& key, const T& value) { return QHash::insertMulti(key.toLower(), value); } const Key key(const T& value, const Key& defaultKey) const { return QHash::key(value, defaultKey.toLower()); } int remove(const Key& key) { return QHash::remove(key.toLower()); } const T take(const Key& key) { return QHash::take(key.toLower()); } const T value(const Key& key) const { return QHash::value(key.toLower()); } const T value(const Key& key, const T& defaultValue) const { return QHash::value(key.toLower(), defaultValue); } QList values(const Key& key) const { return QHash::values(key.toLower()); } T& operator[](const Key& key) { return QHash::operator[](key.toLower()); } const T operator[](const Key& key) const { return QHash::operator[](key.toLower()); } }; //! A set created from static (0-terminated) array of raw null-terminated strings. class KDB_EXPORT StaticSetOfStrings { public: StaticSetOfStrings(); explicit StaticSetOfStrings(const char* const array[]); ~StaticSetOfStrings(); void setStrings(const char* const array[]); bool isEmpty() const; //! @return true if @a string can be found within set, comparison is case sensitive bool contains(const QByteArray& string) const; private: class Private; Private * const d; Q_DISABLE_COPY(StaticSetOfStrings) }; /*! @return debugging string for object @a object of type @a T */ template QString debugString(const T& object) { QString result; QDebug dbg(&result); dbg << object; return result; } //! Used by findExe(). enum FindExeOption { NoFindExeOptions = 0, IgnoreExecBit = 1 }; Q_DECLARE_FLAGS(FindExeOptions, FindExeOption) /** * Finds the executable in the system path. * * A valid executable must be a file and have its executable bit set. * * @param appname The name of the executable file for which to search. * if this contains a path separator, it will be resolved * according to the current working directory * (shell-like behavior). * @param path The path which will be searched. If this is * null (default), the @c $PATH environment variable will * be searched. * @param options if the flags passed include IgnoreExecBit the path returned * may not have the executable bit set. * * @return The path of the executable. If it was not found, * it will return QString(). */ QString findExe(const QString& appname, const QString& path = QString(), FindExeOptions options = NoFindExeOptions); //! A single property //! @note This property is general-purpose and not related to Qt Properties. //! @see KDbUtils::PropertySet class KDB_EXPORT Property { public: //! Constructs a null property Property(); Property(const QVariant &aValue, const QString &aCaption); Property(const Property &other); ~Property(); bool operator==(const Property &other) const; bool operator!=(const Property &other) const { return !operator==(other); } bool isNull() const; QVariant value() const; void setValue(const QVariant &value); QString caption() const; void setCaption(const QString &caption); private: class Private; Private * const d; }; //! A set of properties. //! @note These properties are general-purpose and not related to Qt Properties. //! @see KDbUtils::Property class KDB_EXPORT PropertySet { public: PropertySet(); PropertySet(const PropertySet &other); ~PropertySet(); //! Assigns @a other to this property set and returns a reference to this property set. PropertySet& operator=(const PropertySet &other); //! @return true if this property set has exactly the same properties as @a other //! @since 3.1 bool operator==(const PropertySet &other) const; //! @return true if this property differs in at least one property from @a other //! @since 3.1 bool operator!=(const PropertySet &other) const { return !operator==(other); } //! Inserts property with a given @a name, @a value and @a caption. //! If @a caption is empty, caption from existing property is reused. //! @a name must be a valid identifier (see KDb::isIdentifier()). void insert(const QByteArray &name, const QVariant &value, const QString &caption = QString()); //! Sets caption for property @a name to @a caption. //! If such property does not exist, does nothing. //! @since 3.1 void setCaption(const QByteArray &name, const QString &caption); //! Sets value for property @a name to @a value. //! If such property does not exist, does nothing. //! @since 3.1 void setValue(const QByteArray &name, const QVariant &value); //! Removes property with a given @a name. void remove(const QByteArray &name); //! @return property with a given @a name. //! If not found, a null Property is returned (Property::isNull). Property property(const QByteArray &name) const; //! @return a list of property names. QList names() const; private: class Private; Private * const d; }; } // KDbUtils #endif //KDB_TOOLS_UTILS_H diff --git a/src/tools/KDbValidator.h b/src/tools/KDbValidator.h index de4c999f..c919687f 100644 --- a/src/tools/KDbValidator.h +++ b/src/tools/KDbValidator.h @@ -1,158 +1,157 @@ /* This file is part of the KDE project Copyright (C) 2004, 2006 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDB_TOOLS_VALIDATOR_H #define KDB_TOOLS_VALIDATOR_H #include "kdb_export.h" #include #include #include //! @short A validator extending QValidator with offline-checking for value's validity /*! The offline-checking for value's validity is provided by @ref KDbValidator::check() method. The validator groups two purposes into one container: - string validator for line editors (online checking, "on typing"); - offline-checking for QVariant values, reimplementing validate(). It also offers error and warning messages for check() method. You may need to reimplement: - QValidator::State validate(QString& input, int& pos) const - Result check(const QString &valueName, const QVariant &v, QString *message, QString *details) */ class KDB_EXPORT KDbValidator : public QValidator { Q_OBJECT public: enum Result { Error = 0, Ok = 1, Warning = 2 }; - explicit KDbValidator(QObject *parent = 0); + explicit KDbValidator(QObject *parent = nullptr); - virtual ~KDbValidator(); + ~KDbValidator() override; /*! Sets accepting empty values on (true) or off (false). By default the validator does not accepts empty values. */ void setAcceptsEmptyValue(bool set); /*! @return true if the validator accepts empty values @see setAcceptsEmptyValue() */ bool acceptsEmptyValue() const; /*! Checks if value @a value is ok and returns one of @a Result value: - @a Error is returned on error; - @a Ok on success; - @a Warning if there is something to warn about. In any case except @a Ok, i18n'ed message will be set in @a message and (optionally) datails are set in @a details, e.g. for use in a message box. @a valueName can be used to contruct @a message as well, for example: "[valueName] is not a valid login name". Depending on acceptsEmptyValue(), immediately accepts empty values or not. */ Result check(const QString &valueName, const QVariant& v, QString *message, QString *details); /*! This implementation always returns value QValidator::Acceptable. */ - virtual QValidator::State validate(QString & input, int & pos) const; + QValidator::State validate(QString &input, int &pos) const override; //! A generic error/warning message "... value has to be entered." static const QString messageColumnNotEmpty(); //! Adds a child validator @a v void addChildValidator(KDbValidator* v); protected: /* Used by check(), for reimplementation, by default returns @a Error.*/ virtual Result internalCheck(const QString &valueName, const QVariant& value, QString *message, QString *details); friend class KDbMultiValidator; private: class Private; Private* const d; Q_DISABLE_COPY(KDbValidator) }; //! @short A validator groupping multiple QValidators /*! KDbMultiValidator behaves like normal KDbValidator, but it allows to add define more than one different validator. Given validation is successful if every subvalidator accepted given value. - acceptsEmptyValue() is used globally here (no matter what is defined in subvalidators). - result of calling check() depends on value of check() returned by subvalidators: - Error is returned if at least one subvalidator returned Error; - Warning is returned if at least one subvalidator returned Warning and no validator returned error; - Ok is returned only if exactly all subvalidators returned Ok. - If there is no subvalidators defined, Error is always returned. - If a given subvalidator is not of class KDbValidator but ust QValidator, it's assumed it's check() method returned Ok. - result of calling validate() (a method implemented for QValidator) depends on value of validate() returned by subvalidators: - Invalid is returned if at least one subvalidator returned Invalid - Intermediate is returned if at least one subvalidator returned Intermediate - Acceptable is returned if exactly all subvalidators returned Acceptable. - If there is no subvalidators defined, Invalid is always returned. If there are no subvalidators, the multi validator always accepts the input. */ class KDB_EXPORT KDbMultiValidator : public KDbValidator { Q_OBJECT public: /*! Constructs multivalidator with no subvalidators defined. You can add more validators with addSubvalidator(). */ - explicit KDbMultiValidator(QObject *parent = 0); + explicit KDbMultiValidator(QObject *parent = nullptr); /*! Constructs multivalidator with one validator @a validator. It will be owned if has no parent defined. You can add more validators with addSubvalidator(). */ - explicit KDbMultiValidator(QValidator *validator, QObject *parent = 0); + explicit KDbMultiValidator(QValidator *validator, QObject *parent = nullptr); - ~KDbMultiValidator(); + ~KDbMultiValidator() override; /*! Adds validator @a validator as another subvalidator. Subvalidator will be owned by this multivalidator if @a owned is true - and its parent is NULL. */ + and its parent is @c nullptr. */ void addSubvalidator(QValidator* validator, bool owned = true); /*! Reimplemented to call validate() on subvalidators. */ - virtual QValidator::State validate(QString & input, int & pos) const; + QValidator::State validate(QString &input, int &pos) const override; /*! Calls QValidator::fixup() on every subvalidator. This may be senseless to use this methog in certain cases (can return weir results), so think twice before.. */ - virtual void fixup(QString & input) const; + void fixup(QString &input) const override; protected: - virtual KDbValidator::Result internalCheck( - const QString &valueName, const QVariant& value, - QString *message, QString *details); + KDbValidator::Result internalCheck(const QString &valueName, const QVariant &value, + QString *message, QString *details) override; private: class Private; Private* const d; Q_DISABLE_COPY(KDbMultiValidator) }; #endif diff --git a/src/views/KDbTableViewColumn.cpp b/src/views/KDbTableViewColumn.cpp index 96dc0d8a..6d3407ac 100644 --- a/src/views/KDbTableViewColumn.cpp +++ b/src/views/KDbTableViewColumn.cpp @@ -1,403 +1,403 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #include "KDbTableViewColumn.h" #include "KDbConnection.h" #include "KDbConnectionOptions.h" #include "KDbCursor.h" #include "KDb.h" #include "KDbQuerySchema.h" #include "KDbRecordEditBuffer.h" #include "KDbTableViewData.h" #include "KDbValidator.h" #include class Q_DECL_HIDDEN KDbTableViewColumn::Private { public: Private() - : data(0) - , validator(0) - , relatedData(0) - , field(0) - , columnInfo(0) - , visibleLookupColumnInfo(0) + : data(nullptr) + , validator(nullptr) + , relatedData(nullptr) + , field(nullptr) + , columnInfo(nullptr) + , visibleLookupColumnInfo(nullptr) , width(0) , readOnly(false) , visible(true) , relatedDataEditable(false) , headerTextVisible(true) { } //! Data that this column is assigned to. Set by KDbTableViewColumn::setData() KDbTableViewData* data; QString captionAliasOrName; QIcon icon; KDbValidator* validator; KDbTableViewData* relatedData; int relatedDataPKeyID; KDbField* field; //! @see columnInfo() KDbQueryColumnInfo* columnInfo; //! @see visibleLookupColumnInfo() KDbQueryColumnInfo* visibleLookupColumnInfo; int width; bool isDBAware; //!< true if data is stored in DB, not only in memeory bool readOnly; bool fieldOwned; bool visible; bool relatedDataEditable; bool headerTextVisible; }; //------------------------ KDbTableViewColumn::KDbTableViewColumn(KDbField *f, bool owner) : d(new Private) { d->field = f; d->isDBAware = false; d->fieldOwned = owner; d->captionAliasOrName = d->field->captionOrName(); } KDbTableViewColumn::KDbTableViewColumn(const QString &name, KDbField::Type ctype, KDbField::Constraints cconst, KDbField::Options options, int maxLength, int precision, QVariant defaultValue, const QString &caption, const QString &description) : d(new Private) { d->field = new KDbField( name, ctype, cconst, options, maxLength, precision, defaultValue, caption, description); d->isDBAware = false; d->fieldOwned = true; d->captionAliasOrName = d->field->captionOrName(); } KDbTableViewColumn::KDbTableViewColumn(const QString &name, KDbField::Type ctype, const QString &caption, const QString &description) : d(new Private) { d->field = new KDbField( name, ctype, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, QVariant(), caption, description); d->isDBAware = false; d->fieldOwned = true; d->captionAliasOrName = d->field->captionOrName(); } // db-aware KDbTableViewColumn::KDbTableViewColumn( const KDbQuerySchema &query, KDbQueryColumnInfo *aColumnInfo, KDbQueryColumnInfo *aVisibleLookupColumnInfo) : d(new Private) { Q_ASSERT(aColumnInfo); d->field = aColumnInfo->field(); d->columnInfo = aColumnInfo; d->visibleLookupColumnInfo = aVisibleLookupColumnInfo; d->isDBAware = true; d->fieldOwned = false; //setup column's caption: if (!d->columnInfo->field()->caption().isEmpty()) { d->captionAliasOrName = d->columnInfo->field()->caption(); } else { //reuse alias if available: d->captionAliasOrName = d->columnInfo->alias(); //last hance: use field name if (d->captionAliasOrName.isEmpty()) d->captionAliasOrName = d->columnInfo->field()->name(); //! @todo compute other auto-name? } //setup column's readonly flag: true, if // - it's not from parent table's field, or // - if the query itself is coming from read-only connection, or // - if the query itself is stored (i.e. has connection) and lookup column is defined const bool columnFromMasterTable = query.masterTable() == d->columnInfo->field()->table(); d->readOnly = !columnFromMasterTable || (query.connection() && query.connection()->options()->isReadOnly()); //! @todo remove this when queries become editable ^^^^^^^^^^^^^^ // kdbDebug() << "KDbTableViewColumn: query.masterTable()==" // << (query.masterTable() ? query.masterTable()->name() : "notable") << ", columnInfo->field->table()==" // << (columnInfo->field->table() ? columnInfo->field->table()->name() : "notable"); } KDbTableViewColumn::KDbTableViewColumn(bool) : d(new Private) { d->isDBAware = false; } KDbTableViewColumn::~KDbTableViewColumn() { if (d->fieldOwned) delete d->field; - setValidator(0); + setValidator(nullptr); delete d->relatedData; delete d; } void KDbTableViewColumn::setValidator(KDbValidator *v) { if (d->validator) {//remove old one if (!d->validator->parent()) //destroy if has no parent delete d->validator; } d->validator = v; } void KDbTableViewColumn::setData(KDbTableViewData *data) { d->data = data; } void KDbTableViewColumn::setRelatedData(KDbTableViewData *data) { if (d->isDBAware) return; if (d->relatedData) delete d->relatedData; - d->relatedData = 0; + d->relatedData = nullptr; if (!data) return; //find a primary key const QList *columns = data->columns(); int id = -1; foreach(KDbTableViewColumn* col, *columns) { id++; if (col->field()->isPrimaryKey()) { //found, remember d->relatedDataPKeyID = id; d->relatedData = data; return; } } } bool KDbTableViewColumn::isReadOnly() const { return d->readOnly || (d->data && d->data->isReadOnly()); } void KDbTableViewColumn::setReadOnly(bool ro) { d->readOnly = ro; } bool KDbTableViewColumn::isVisible() const { return d->columnInfo ? d->columnInfo->isVisible() : d->visible; } void KDbTableViewColumn::setVisible(bool v) { bool changed = d->visible != v; if (d->columnInfo && d->columnInfo->isVisible() != v) { d->columnInfo->setVisible(v); changed = true; } d->visible = v; if (changed && d->data) { d->data->columnVisibilityChanged(*this); } } void KDbTableViewColumn::setIcon(const QIcon& icon) { d->icon = icon; } QIcon KDbTableViewColumn::icon() const { return d->icon; } void KDbTableViewColumn::setHeaderTextVisible(bool visible) { d->headerTextVisible = visible; } bool KDbTableViewColumn::isHeaderTextVisible() const { return d->headerTextVisible; } QString KDbTableViewColumn::captionAliasOrName() const { return d->captionAliasOrName; } KDbValidator* KDbTableViewColumn::validator() const { return d->validator; } KDbTableViewData *KDbTableViewColumn::relatedData() const { return d->relatedData; } KDbField* KDbTableViewColumn::field() const { return d->field; } void KDbTableViewColumn::setRelatedDataEditable(bool set) { d->relatedDataEditable = set; } bool KDbTableViewColumn::isRelatedDataEditable() const { return d->relatedDataEditable; } KDbQueryColumnInfo* KDbTableViewColumn::columnInfo() const { return d->columnInfo; } KDbQueryColumnInfo* KDbTableViewColumn::visibleLookupColumnInfo() const { return d->visibleLookupColumnInfo; } bool KDbTableViewColumn::isDBAware() const { return d->isDBAware; } bool KDbTableViewColumn::acceptsFirstChar(const QChar &ch) const { // the field we're looking at can be related to "visible lookup column" // if lookup column is present KDbField *visibleField = d->visibleLookupColumnInfo ? d->visibleLookupColumnInfo->field() : d->field; const KDbField::Type type = visibleField->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isNumericType(type)) { if (ch == QLatin1Char('.') || ch == QLatin1Char(',')) return KDbField::isFPNumericType(type); if (ch == QLatin1Char('-')) return !visibleField->isUnsigned(); if (ch == QLatin1Char('+') || (ch >= QLatin1Char('0') && ch <= QLatin1Char('9'))) return true; return false; } switch (type) { case KDbField::Boolean: return false; case KDbField::Date: case KDbField::DateTime: case KDbField::Time: return ch >= QLatin1Char('0') && ch <= QLatin1Char('9'); default:; } return true; } void KDbTableViewColumn::setWidth(int w) { d->width = w; } int KDbTableViewColumn::width() const { return d->width; } QDebug operator<<(QDebug dbg, const KDbTableViewColumn &column) { dbg.nospace() << "TableViewColumn("; dbg.space() << "columnInfo:"; if (column.columnInfo()) { dbg.space() << *column.columnInfo(); } else { dbg.space() << ""; } dbg.space() << "captionAliasOrName:" << column.captionAliasOrName(); dbg.space() << "visibleLookupColumnInfo:"; if (column.visibleLookupColumnInfo()) { dbg.space() << *column.visibleLookupColumnInfo(); } else { dbg.space() << ""; } dbg.space() << "data: KDbTableViewData("; const KDbTableViewData *data = column.d->data; if (data) { dbg.space() << "count:" << data->count() << ")"; } else { dbg.space() << ")"; } dbg.space() << "relatedData: KDbTableViewData("; const KDbTableViewData *relatedData = column.d->relatedData; if (data) { dbg.space() << "count:" << relatedData->count() << ")"; } else { dbg.space() << ")"; } const KDbField *field = column.d->field; if (data) { dbg.space() << "field:" << *field; } else { dbg.space() << ""; } dbg.space() << "fieldOwned:" << column.d->fieldOwned; dbg.space() << "validator:"; if (column.validator()) { dbg.space() << ""; } else { dbg.space() << ""; } dbg.space() << "icon:" << column.icon().name(); dbg.space() << "fieldOwned:" << column.d->fieldOwned; dbg.space() << "width:" << column.width(); dbg.space() << "isDBAware:" << column.isDBAware(); dbg.space() << "readOnly:" << column.isReadOnly(); dbg.space() << "visible:" << column.isVisible(); dbg.space() << "relatedDataEditable:" << column.isRelatedDataEditable(); dbg.space() << "headerTextVisible:" << column.isHeaderTextVisible(); return dbg.space(); } diff --git a/src/views/KDbTableViewColumn.h b/src/views/KDbTableViewColumn.h index f7b14259..db7ff4ca 100644 --- a/src/views/KDbTableViewColumn.h +++ b/src/views/KDbTableViewColumn.h @@ -1,187 +1,187 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #ifndef KDB_TABLEVIEWCOLUMN_H #define KDB_TABLEVIEWCOLUMN_H #include "kdb_export.h" #include "KDbField.h" class KDbQueryColumnInfo; class KDbQuerySchema; class KDbTableViewColumn; class KDbTableViewData; class KDbValidator; //! Definition of a single column for table view. /*! @todo improve API */ class KDB_EXPORT KDbTableViewColumn { public: /*! Not db-aware ctor. if @a owner is true, the field @a will be owned by this column, so you shouldn't care about destroying this field. */ explicit KDbTableViewColumn(KDbField *f, bool owner = false); /*! Not db-aware, convenience ctor, like above. The field is created using specified parameters that are equal to these accepted by KDbField ctor. The column will be the owner of this automatically generated field. */ KDbTableViewColumn(const QString &name, KDbField::Type ctype, KDbField::Constraints cconst = KDbField::NoConstraints, KDbField::Options options = KDbField::NoOptions, int maxLength = 0, int precision = 0, QVariant defaultValue = QVariant(), const QString &caption = QString(), const QString &description = QString()); /*! Not db-aware, convenience ctor, simplified version of the above. */ KDbTableViewColumn(const QString &name, KDbField::Type ctype, const QString &caption, const QString &description = QString()); //! Db-aware version. KDbTableViewColumn(const KDbQuerySchema &query, KDbQueryColumnInfo *aColumnInfo, - KDbQueryColumnInfo *aVisibleLookupColumnInfo = 0); + KDbQueryColumnInfo *aVisibleLookupColumnInfo = nullptr); virtual ~KDbTableViewColumn(); virtual bool acceptsFirstChar(const QChar &ch) const; /*! @return true if the column is read-only For db-aware column this can depend on whether the column is in parent table of this query. @see setReadOnly() */ bool isReadOnly() const; //! forces readOnly flag to be set to @a ro //! @todo synchronize this with table view: void setReadOnly(bool ro); //! Column visibility. By default column is visible. bool isVisible() const; //! Changes column visibility. //! KDbTableViewData is informed about this change. //! @todo react on changes of KDbQueryColumnInfo::visible too void setVisible(bool v); /*! Sets icon for displaying in the caption area (header). */ void setIcon(const QIcon &icon); /*! @return bame of icon displayed in the caption area (header). */ QIcon icon() const; /*! If @a visible is true, caption has to be displayed in the column's header, (or field's name if caption is empty. True by default. */ void setHeaderTextVisible(bool visible); /*! @return true if caption has to be displayed in the column's header, (or field's name if caption is empty. */ bool isHeaderTextVisible() const; /*! @return whatever is available: - field's caption - or field's alias (from query) - or finally - field's name */ QString captionAliasOrName() const; /*! Assigns validator @a v for this column. If the validator has no parent object, it will be owned by the column, so you don't need to care about destroying it. */ void setValidator(KDbValidator *v); //! @return validator assigned for this column of 0 if there is no validator assigned. KDbValidator* validator() const; /*! For not-db-aware data only: Sets related data @a data for this column, what defines simple one-field, one-to-many relationship between this column and the primary key in @a data. The relationship will be used to generate a popup editor instead of just regular editor. This assignment has no result if @a data has no primary key defined. @a data is owned, so is will be destroyed when needed. It is also destroyed - when another data (or NULL) is set for the same column. */ + when another data (or @c nullptr) is set for the same column. */ void setRelatedData(KDbTableViewData *data); /*! For not-db-aware data only: Related data @a data for this column, what defines simple one-field. - NULL by default. @see setRelatedData() */ + @c nullptr by default. @see setRelatedData() */ KDbTableViewData *relatedData() const; /*! @return field for this column. For db-aware information is taken from columnInfo(). */ KDbField* field() const; /*! Only usable if related data is set (ie. this is for combo boxes). Sets 'editable' flag for this column, what means a new value can be entered by hand. This is similar to QComboBox::setEditable(). */ void setRelatedDataEditable(bool set); /*! Only usable if related data is set (ie. this is for combo boxes). @return 'editable' flag for this column. False by default. @see setRelatedDataEditable(bool). */ bool isRelatedDataEditable() const; /*! A rich field information for db-aware data. For not-db-aware data it is always 0 (use field() instead). */ KDbQueryColumnInfo* columnInfo() const; /*! A rich field information for db-aware data. Specifies information for a column that should be visible instead of columnInfo. For example case see @ref KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpanded(KDbQuerySchema::FieldsExpandedOptions options = Default) For not-db-aware data it is always 0. */ KDbQueryColumnInfo* visibleLookupColumnInfo() const; //! @return true if data is stored in DB, not only in memeory. bool isDBAware() const; /*! Sets visible width for this column to @a w (usually in pixels or points). 0 means there is no hint for the width. */ void setWidth(int w); /*! @return width of this field (usually in pixels or points). 0 (the default) means there is no hint for the width. */ int width() const; protected: //! special ctor that does not allocate d member; explicit KDbTableViewColumn(bool); //! used by KDbTableViewData::addColumn() void setData(KDbTableViewData *data); private: class Private; Private * const d; friend class KDbTableViewData; friend KDB_EXPORT QDebug operator<<(QDebug, const KDbTableViewColumn&); Q_DISABLE_COPY(KDbTableViewColumn) }; //! Sends information about column @a column to debug output @a dbg. //! @since 3.1 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTableViewColumn &column); #endif diff --git a/src/views/KDbTableViewData.cpp b/src/views/KDbTableViewData.cpp index d62a5a6b..4d291024 100644 --- a/src/views/KDbTableViewData.cpp +++ b/src/views/KDbTableViewData.cpp @@ -1,972 +1,972 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek Copyright (C) 2014 Michał Poteralski This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #include "KDbTableViewData.h" #include "KDbConnection.h" #include "KDbConnectionOptions.h" #include "KDbCursor.h" #include "KDbError.h" #include "KDb.h" #include "KDbOrderByColumn.h" #include "KDbQuerySchema.h" #include "KDbRecordEditBuffer.h" #include "KDbTableViewColumn.h" #include "kdb_debug.h" #include #include // #define TABLEVIEW_NO_PROCESS_EVENTS static unsigned short charTable[] = { #include "chartable.txt" }; //------------------------------- //! @internal Unicode-aware collator for comparing strings class CollatorInstance { public: CollatorInstance() { UErrorCode status = U_ZERO_ERROR; m_collator = icu::Collator::createInstance(status); if (U_FAILURE(status)) { kdbWarning() << "Could not create instance of collator:" << status; - m_collator = 0; + m_collator = nullptr; } else { // enable normalization by default m_collator->setAttribute(UCOL_NORMALIZATION_MODE, UCOL_ON, status); if (U_FAILURE(status)) { kdbWarning() << "Could not set collator attribute:" << status; } } } icu::Collator* getCollator() { return m_collator; } ~CollatorInstance() { delete m_collator; } private: icu::Collator *m_collator; }; Q_GLOBAL_STATIC(CollatorInstance, KDb_collator) //! @internal A functor used in qSort() in order to sort by a given column class LessThanFunctor { private: KDbOrderByColumn::SortOrder m_order; QVariant m_leftTmp, m_rightTmp; int m_sortColumn; bool (*m_lessThanFunction)(const QVariant&, const QVariant&); #define CAST_AND_COMPARE(casting) \ return left.casting() < right.casting() static bool cmpInt(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toInt); } static bool cmpUInt(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toUInt); } static bool cmpLongLong(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toLongLong); } static bool cmpULongLong(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toULongLong); } static bool cmpDouble(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toDouble); } static bool cmpDate(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toDate); } static bool cmpDateTime(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toDateTime); } static bool cmpTime(const QVariant& left, const QVariant& right) { CAST_AND_COMPARE(toDate); } static bool cmpString(const QVariant& left, const QVariant& right) { const QString &as = left.toString(); const QString &bs = right.toString(); - const QChar *a = as.isEmpty() ? 0 : as.unicode(); - const QChar *b = bs.isEmpty() ? 0 : bs.unicode(); + const QChar *a = as.isEmpty() ? nullptr : as.unicode(); + const QChar *b = bs.isEmpty() ? nullptr : bs.unicode(); - if (a == 0) { - return b != 0; + if (a == nullptr) { + return b != nullptr; } - if (a == b || b == 0) { + if (a == b || b == nullptr) { return false; } int len = qMin(as.length(), bs.length()); forever { unsigned short au = a->unicode(); unsigned short bu = b->unicode(); au = (au <= 0x17e ? charTable[au] : 0xffff); bu = (bu <= 0x17e ? charTable[bu] : 0xffff); if (len <= 0) return false; len--; if (au != bu) return au < bu; a++; b++; } return false; } static bool cmpStringWithCollator(const QVariant& left, const QVariant& right) { const QString &as = left.toString(); const QString &bs = right.toString(); return icu::Collator::LESS == KDb_collator->getCollator()->compare( (const UChar *)as.constData(), as.size(), (const UChar *)bs.constData(), bs.size()); } //! Compare function for BLOB data (QByteArray). Uses size as the weight. static bool cmpBLOB(const QVariant& left, const QVariant& right) { return left.toByteArray().size() < right.toByteArray().size(); } public: LessThanFunctor() : m_order(KDbOrderByColumn::SortOrder::Ascending) , m_sortColumn(-1) - , m_lessThanFunction(0) + , m_lessThanFunction(nullptr) { } void setColumnType(const KDbField& field) { const KDbField::Type t = field.type(); if (field.isTextType()) m_lessThanFunction = &cmpString; if (KDbField::isFPNumericType(t)) m_lessThanFunction = &cmpDouble; else if (t == KDbField::Integer && field.isUnsigned()) m_lessThanFunction = &cmpUInt; else if (t == KDbField::Boolean || KDbField::isNumericType(t)) m_lessThanFunction = &cmpInt; //other integers else if (t == KDbField::BigInteger) { if (field.isUnsigned()) m_lessThanFunction = &cmpULongLong; else m_lessThanFunction = &cmpLongLong; } else if (t == KDbField::Date) m_lessThanFunction = &cmpDate; else if (t == KDbField::Time) m_lessThanFunction = &cmpTime; else if (t == KDbField::DateTime) m_lessThanFunction = &cmpDateTime; else if (t == KDbField::BLOB) //! @todo allow users to define BLOB sorting function? m_lessThanFunction = &cmpBLOB; else { // anything else // check if CollatorInstance is not destroyed and has valid collator if (!KDb_collator.isDestroyed() && KDb_collator->getCollator()) { m_lessThanFunction = &cmpStringWithCollator; } else { m_lessThanFunction = &cmpString; } } } void setSortOrder(KDbOrderByColumn::SortOrder order) { m_order = order; } void setSortColumn(int column) { m_sortColumn = column; } #define _IIF(a,b) ((a) ? (b) : !(b)) //! Main comparison operator that takes column number, type and order into account bool operator()(KDbRecordData* record1, KDbRecordData* record2) { // compare NULLs : NULL is smaller than everything if ((m_leftTmp = record1->at(m_sortColumn)).isNull()) return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending, !record2->at(m_sortColumn).isNull()); if ((m_rightTmp = record2->at(m_sortColumn)).isNull()) return m_order == KDbOrderByColumn::SortOrder::Descending; return _IIF(m_order == KDbOrderByColumn::SortOrder::Ascending, m_lessThanFunction(m_leftTmp, m_rightTmp)); } }; #undef _IIF #undef CAST_AND_COMPARE //! @internal class Q_DECL_HIDDEN KDbTableViewData::Private { public: Private() : sortColumn(0) , realSortColumn(0) , sortOrder(KDbOrderByColumn::SortOrder::Ascending) , type(1) - , pRecordEditBuffer(0) + , pRecordEditBuffer(nullptr) , readOnly(false) , insertingEnabled(true) , containsRecordIdInfo(false) , autoIncrementedColumn(-2) { } ~Private() { delete pRecordEditBuffer; } //! Number of physical columns int realColumnCount; /*! Columns information */ QList columns; /*! Visible columns information */ QList visibleColumns; //! (Logical) sorted column number, set by setSorting(). //! Can differ from realSortColumn if there's lookup column used. int sortColumn; //! Real sorted column number, set by setSorting(), used by cmp*() methods int realSortColumn; //! Specifies sorting order KDbOrderByColumn::SortOrder sortOrder; LessThanFunctor lessThanFunctor; short type; KDbRecordEditBuffer *pRecordEditBuffer; KDbCursor *cursor; KDbResultInfo result; QList visibleColumnIDs; QList globalColumnIDs; bool readOnly; bool insertingEnabled; //! @see KDbTableViewData::containsRecordIdInfo() bool containsRecordIdInfo; mutable int autoIncrementedColumn; }; //------------------------------- KDbTableViewData::KDbTableViewData() : QObject() , KDbTableViewDataBase() , d(new Private) { init(); } // db-aware ctor KDbTableViewData::KDbTableViewData(KDbCursor *c) : QObject() , KDbTableViewDataBase() , d(new Private) { init(); d->cursor = c; d->containsRecordIdInfo = d->cursor->containsRecordIdInfo(); if (d->cursor && d->cursor->query()) { const KDbQuerySchema::FieldsExpandedOptions fieldsExpandedOptions = d->containsRecordIdInfo ? KDbQuerySchema::WithInternalFieldsAndRecordId : KDbQuerySchema::WithInternalFields; d->realColumnCount = d->cursor->query()->fieldsExpanded(fieldsExpandedOptions).count(); } else { d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0); } // Allocate KDbTableViewColumn objects for each visible query column const KDbQueryColumnInfo::Vector fields = d->cursor->query()->fieldsExpanded(); const int fieldCount = fields.count(); for (int i = 0;i < fieldCount;i++) { KDbQueryColumnInfo *ci = fields[i]; if (ci->isVisible()) { - KDbQueryColumnInfo *visibleLookupColumnInfo = 0; + KDbQueryColumnInfo *visibleLookupColumnInfo = nullptr; if (ci->indexForVisibleLookupValue() != -1) { //Lookup field is defined visibleLookupColumnInfo = d->cursor->query()->expandedOrInternalField(ci->indexForVisibleLookupValue()); } KDbTableViewColumn* col = new KDbTableViewColumn(*d->cursor->query(), ci, visibleLookupColumnInfo); addColumn(col); } } } KDbTableViewData::KDbTableViewData( const QList &keys, const QList &values, KDbField::Type keyType, KDbField::Type valueType) : QObject() , KDbTableViewDataBase() , d(new Private) { init(keys, values, keyType, valueType); } KDbTableViewData::KDbTableViewData( KDbField::Type keyType, KDbField::Type valueType) : QObject() , KDbTableViewDataBase() , d(new Private) { const QList empty; init(empty, empty, keyType, valueType); } KDbTableViewData::~KDbTableViewData() { emit destroying(); clearInternal(false /* !processEvents */); qDeleteAll(d->columns); delete d; } void KDbTableViewData::init( const QList &keys, const QList &values, KDbField::Type keyType, KDbField::Type valueType) { init(); KDbField *keyField = new KDbField(QLatin1String("key"), keyType); keyField->setPrimaryKey(true); KDbTableViewColumn *keyColumn = new KDbTableViewColumn(keyField, true); keyColumn->setVisible(false); addColumn(keyColumn); KDbField *valueField = new KDbField(QLatin1String("value"), valueType); KDbTableViewColumn *valueColumn = new KDbTableViewColumn(valueField, true); addColumn(valueColumn); int cnt = qMin(keys.count(), values.count()); QList::ConstIterator it_keys = keys.constBegin(); QList::ConstIterator it_values = values.constBegin(); for (;cnt > 0;++it_keys, ++it_values, cnt--) { KDbRecordData *record = new KDbRecordData(2); (*record)[0] = (*it_keys); (*record)[1] = (*it_values); append(record); } } void KDbTableViewData::init() { d->realColumnCount = 0; - d->cursor = 0; + d->cursor = nullptr; } void KDbTableViewData::deleteLater() { - d->cursor = 0; + d->cursor = nullptr; QObject::deleteLater(); } void KDbTableViewData::addColumn(KDbTableViewColumn* col) { d->columns.append(col); col->setData(this); if (col->isVisible()) { d->visibleColumns.append(col); d->visibleColumnIDs.append(d->visibleColumns.count() - 1); d->globalColumnIDs.append(d->columns.count() - 1); } else { d->visibleColumnIDs.append(-1); } d->autoIncrementedColumn = -2; //clear cache; if (!d->cursor || !d->cursor->query()) { d->realColumnCount = d->columns.count() + (d->containsRecordIdInfo ? 1 : 0); } } void KDbTableViewData::columnVisibilityChanged(const KDbTableViewColumn &column) { if (column.isVisible()) { // column made visible int indexInGlobal = d->columns.indexOf(const_cast(&column)); // find previous column that is visible int prevIndexInGlobal = indexInGlobal - 1; while (prevIndexInGlobal >= 0 && d->visibleColumnIDs[prevIndexInGlobal] == -1) { prevIndexInGlobal--; } int indexInVisible = prevIndexInGlobal + 1; // update d->visibleColumns.insert(indexInVisible, const_cast(&column)); d->visibleColumnIDs[indexInGlobal] = indexInVisible; d->globalColumnIDs.insert(indexInVisible, indexInGlobal); for (int i = indexInGlobal + 1; i < d->columns.count(); i++) { // increment ids of the rest if (d->visibleColumnIDs[i] >= 0) { d->visibleColumnIDs[i]++; } } } else { // column made invisible int indexInVisible = d->visibleColumns.indexOf(const_cast(&column)); d->visibleColumns.removeAt(indexInVisible); int indexInGlobal = globalIndexOfVisibleColumn(indexInVisible); d->visibleColumnIDs[indexInGlobal] = -1; d->globalColumnIDs.removeAt(indexInVisible); } } int KDbTableViewData::globalIndexOfVisibleColumn(int visibleIndex) const { return d->globalColumnIDs.value(visibleIndex, -1); } int KDbTableViewData::visibleColumnIndex(int globalIndex) const { return d->visibleColumnIDs.value(globalIndex, -1); } int KDbTableViewData::columnCount() const { return d->columns.count(); } int KDbTableViewData::visibleColumnCount() const { return d->visibleColumns.count(); } QList* KDbTableViewData::columns() { return &d->columns; } QList* KDbTableViewData::visibleColumns() { return &d->visibleColumns; } KDbTableViewColumn* KDbTableViewData::column(int index) { return d->columns.value(index); } KDbTableViewColumn* KDbTableViewData::visibleColumn(int index) { return d->visibleColumns.value(index); } bool KDbTableViewData::isDBAware() const { - return d->cursor != 0; + return d->cursor != nullptr; } KDbCursor* KDbTableViewData::cursor() const { return d->cursor; } bool KDbTableViewData::isInsertingEnabled() const { return d->insertingEnabled; } KDbRecordEditBuffer* KDbTableViewData::recordEditBuffer() const { return d->pRecordEditBuffer; } const KDbResultInfo& KDbTableViewData::result() const { return d->result; } bool KDbTableViewData::containsRecordIdInfo() const { return d->containsRecordIdInfo; } KDbRecordData* KDbTableViewData::createItem() const { return new KDbRecordData(d->realColumnCount); } QString KDbTableViewData::dbTableName() const { if (d->cursor && d->cursor->query() && d->cursor->query()->masterTable()) return d->cursor->query()->masterTable()->name(); return QString(); } void KDbTableViewData::setSorting(int column, KDbOrderByColumn::SortOrder order) { d->sortOrder = order; if (column < 0 || column >= d->columns.count()) { d->sortColumn = -1; d->realSortColumn = -1; return; } // find proper column information for sorting (lookup column points to alternate column with visible data) const KDbTableViewColumn *tvcol = d->columns.at(column); KDbQueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo(); const KDbField *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field() : tvcol->field(); d->sortColumn = column; d->realSortColumn = tvcol->columnInfo()->indexForVisibleLookupValue() != -1 ? tvcol->columnInfo()->indexForVisibleLookupValue() : d->sortColumn; // setup compare functor d->lessThanFunctor.setColumnType(*field); d->lessThanFunctor.setSortOrder(d->sortOrder); d->lessThanFunctor.setSortColumn(column); } int KDbTableViewData::sortColumn() const { return d->sortColumn; } KDbOrderByColumn::SortOrder KDbTableViewData::sortOrder() const { return d->sortOrder; } void KDbTableViewData::sort() { if (d->sortColumn < 0 || d->sortColumn >= d->columns.count()) { return; } qSort(begin(), end(), d->lessThanFunctor); } void KDbTableViewData::setReadOnly(bool set) { if (d->readOnly == set) return; d->readOnly = set; if (d->readOnly) setInsertingEnabled(false); } void KDbTableViewData::setInsertingEnabled(bool set) { if (d->insertingEnabled == set) return; d->insertingEnabled = set; if (d->insertingEnabled) setReadOnly(false); } void KDbTableViewData::clearRecordEditBuffer() { //init record edit buffer if (!d->pRecordEditBuffer) d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware()); else d->pRecordEditBuffer->clear(); } bool KDbTableViewData::updateRecordEditBufferRef(KDbRecordData *record, int colnum, KDbTableViewColumn* col, QVariant* newval, bool allowSignals, QVariant *visibleValueForLookupField) { Q_ASSERT(newval); d->result.clear(); if (allowSignals) emit aboutToChangeCell(record, colnum, newval, &d->result); if (!d->result.success) return false; //kdbDebug() << "column #" << colnum << " = " << newval.toString(); if (!col) { kdbWarning() << "column #" << colnum << "not found! col==0"; return false; } if (!d->pRecordEditBuffer) d->pRecordEditBuffer = new KDbRecordEditBuffer(isDBAware()); if (d->pRecordEditBuffer->isDBAware()) { if (!(col->columnInfo())) { kdbWarning() << "column #" << colnum << " not found!"; return false; } d->pRecordEditBuffer->insert(col->columnInfo(), *newval); if (col->visibleLookupColumnInfo() && visibleValueForLookupField) { //this is value for lookup table: update visible value as well d->pRecordEditBuffer->insert(col->visibleLookupColumnInfo(), *visibleValueForLookupField); } return true; } if (!(col->field())) { kdbWarning() << "column #" << colnum << "not found!"; return false; } //not db-aware: const QString colname = col->field()->name(); if (colname.isEmpty()) { kdbWarning() << "column #" << colnum << "not found!"; return false; } d->pRecordEditBuffer->insert(colname, *newval); return true; } bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum, KDbTableViewColumn* col, const QVariant &newval, bool allowSignals) { QVariant newv(newval); return updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals); } bool KDbTableViewData::updateRecordEditBuffer(KDbRecordData *record, int colnum, const QVariant &newval, bool allowSignals) { KDbTableViewColumn* col = d->columns.value(colnum); QVariant newv(newval); return col ? updateRecordEditBufferRef(record, colnum, col, &newv, allowSignals) : false; } //! Get a new value (if present in the buffer), or the old one (taken here for optimization) static inline void saveRecordGetValue(const QVariant **pval, KDbCursor *cursor, KDbRecordEditBuffer *pRecordEditBuffer, QList::ConstIterator* it_f, KDbRecordData *record, KDbField *f, QVariant* val, int col) { if (!*pval) { *pval = cursor ? pRecordEditBuffer->at( (**it_f)->columnInfo(), record->at(col).isNull() /* useDefaultValueIfPossible */ ) : pRecordEditBuffer->at( *f ); *val = *pval ? **pval : record->at(col); /* get old value */ //kdbDebug() << col << *(**it_f)->columnInfo() << "val:" << *val; } } //! @todo if there're multiple views for this data, we need multiple buffers! bool KDbTableViewData::saveRecord(KDbRecordData *record, bool insert, bool repaint) { Q_UNUSED(record); if (!d->pRecordEditBuffer) return true; //nothing to do //check constraints: //-check if every NOT NULL and NOT EMPTY field is filled QList::ConstIterator it_f(d->columns.constBegin()); int col = 0; - const QVariant *pval = 0; + const QVariant *pval = nullptr; QVariant val; for (;it_f != d->columns.constEnd() && col < record->count();++it_f, ++col) { KDbField *f = (*it_f)->field(); if (f->isNotNull()) { saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, col); //check it if (val.isNull() && !f->isAutoIncrement()) { //NOT NULL violated d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName()) + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData(); d->result.description = tr("The column's constraint is declared as NOT NULL."); d->result.column = col; return false; } } if (f->isNotEmpty()) { saveRecordGetValue(&pval, d->cursor, d->pRecordEditBuffer, &it_f, record, f, &val, col); if (!f->isAutoIncrement() && (val.isNull() || KDb::isEmptyValue(f->type(), val))) { //NOT EMPTY violated d->result.message = tr("\"%1\" column requires a value to be entered.").arg(f->captionOrName()) + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData(); d->result.description = tr("The column's constraint is declared as NOT EMPTY."); d->result.column = col; return false; } } } if (d->cursor) {//db-aware if (insert) { if (!d->cursor->insertRecord(record, d->pRecordEditBuffer, d->containsRecordIdInfo /*also retrieve ROWID*/)) { d->result.message = tr("Record inserting failed.") + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData(); KDb::getHTMLErrorMesage(*d->cursor, &d->result); /* if (desc) *desc = */ /*! @todo use KexiMainWindow::showErrorMessage(const QString &title, KDbObject *obj) after it will be moved somewhere to KDb (this will require moving other showErrorMessage() methods from KexiMainWindow to libkexiutils....) then: just call: *desc = KDb::errorMessage(d->cursor); */ return false; } } else { // record updating if (!d->cursor->updateRecord(static_cast(record), d->pRecordEditBuffer, d->containsRecordIdInfo /*use ROWID*/)) { d->result.message = tr("Record changing failed.") + QLatin1String("\n\n") + KDbTableViewData::messageYouCanImproveData(); //! @todo set d->result.column if possible KDb::getHTMLErrorMesage(*d->cursor, &d->result.description); return false; } } } else {//not db-aware version KDbRecordEditBuffer::SimpleMap b = d->pRecordEditBuffer->simpleBuffer(); for (KDbRecordEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it != b.constEnd();++it) { int i = -1; foreach(KDbTableViewColumn *col, d->columns) { i++; if (col->field()->name() == it.key()) { kdbDebug() << col->field()->name() << ": " << record->at(i).toString() << " -> " << it.value().toString(); (*record)[i] = it.value(); } } } } d->pRecordEditBuffer->clear(); if (repaint) emit recordRepaintRequested(record); return true; } bool KDbTableViewData::saveRecordChanges(KDbRecordData *record, bool repaint) { Q_UNUSED(record); d->result.clear(); emit aboutToUpdateRecord(record, d->pRecordEditBuffer, &d->result); if (!d->result.success) return false; if (saveRecord(record, false /*update*/, repaint)) { emit recordUpdated(record); return true; } return false; } bool KDbTableViewData::saveNewRecord(KDbRecordData *record, bool repaint) { Q_UNUSED(record); d->result.clear(); emit aboutToInsertRecord(record, &d->result, repaint); if (!d->result.success) return false; if (saveRecord(record, true /*insert*/, repaint)) { emit recordInserted(record, repaint); return true; } return false; } bool KDbTableViewData::deleteRecord(KDbRecordData *record, bool repaint) { Q_UNUSED(record); d->result.clear(); emit aboutToDeleteRecord(record, &d->result, repaint); if (!d->result.success) return false; if (d->cursor) {//db-aware d->result.success = false; if (!d->cursor->deleteRecord(static_cast(record), d->containsRecordIdInfo /*use ROWID*/)) { d->result.message = tr("Record deleting failed."); //! @todo use KDberrorMessage() for description as in KDbTableViewData::saveRecord() */ KDb::getHTMLErrorMesage(*d->cursor, &d->result); d->result.success = false; return false; } } int index = indexOf(record); if (index == -1) { //aah - this shouldn't be! kdbWarning() << "!removeRef() - IMPL. ERROR?"; d->result.success = false; return false; } removeAt(index); emit recordDeleted(); return true; } void KDbTableViewData::deleteRecords(const QList &recordsToDelete, bool repaint) { Q_UNUSED(repaint); if (recordsToDelete.isEmpty()) return; int last_r = 0; KDbTableViewDataIterator it(begin()); for (QList::ConstIterator r_it = recordsToDelete.constBegin(); r_it != recordsToDelete.constEnd(); ++r_it) { for (; last_r < (*r_it); last_r++) ++it; it = erase(it); /* this will delete *it */ last_r++; } //DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS! //--> emit reloadRequested(); //! \todo more effective? emit recordsDeleted(recordsToDelete); } void KDbTableViewData::insertRecord(KDbRecordData *record, int index, bool repaint) { Q_UNUSED(record); insert(index = qMin(index, count()), record); emit recordInserted(record, index, repaint); } void KDbTableViewData::clearInternal(bool processEvents) { clearRecordEditBuffer(); //! @todo this is time consuming: find better data model const int c = count(); #ifndef TABLEVIEW_NO_PROCESS_EVENTS const bool _processEvents = processEvents && !qApp->closingDown(); #endif for (int i = 0; i < c; i++) { removeLast(); #ifndef TABLEVIEW_NO_PROCESS_EVENTS if (_processEvents && i % 1000 == 0) qApp->processEvents(QEventLoop::AllEvents, 1); #endif } } bool KDbTableViewData::deleteAllRecords(bool repaint) { clearInternal(); bool res = true; if (d->cursor) { //db-aware res = d->cursor->deleteAllRecords(); } if (repaint) emit reloadRequested(); return res; } int KDbTableViewData::autoIncrementedColumn() const { if (d->autoIncrementedColumn == -2) { //find such a column d->autoIncrementedColumn = -1; foreach(KDbTableViewColumn *col, d->columns) { d->autoIncrementedColumn++; if (col->field()->isAutoIncrement()) break; } } return d->autoIncrementedColumn; } bool KDbTableViewData::preloadAllRecords() { if (!d->cursor) return false; if (!d->cursor->moveFirst() && d->cursor->result().isError()) return false; #ifndef TABLEVIEW_NO_PROCESS_EVENTS const bool processEvents = !qApp->closingDown(); #endif for (int i = 0;!d->cursor->eof();i++) { KDbRecordData *record = d->cursor->storeCurrentRecord(); if (!record) { clear(); return false; } // record->debug(); append(record); if (!d->cursor->moveNext() && d->cursor->result().isError()) { clear(); return false; } #ifndef TABLEVIEW_NO_PROCESS_EVENTS if (processEvents && (i % 1000) == 0) qApp->processEvents(QEventLoop::AllEvents, 1); #endif } return true; } bool KDbTableViewData::isReadOnly() const { return d->readOnly || (d->cursor && d->cursor->connection()->options()->isReadOnly()); } // static QString KDbTableViewData::messageYouCanImproveData() { return tr("Please correct data in this record or use the \"Cancel record changes\" function."); } QDebug operator<<(QDebug dbg, const KDbTableViewData &data) { dbg.nospace() << "TableViewData("; dbg.space() << "sortColumn:" << data.sortColumn() << "sortOrder:" << (data.sortOrder() == KDbOrderByColumn::SortOrder::Ascending ? "asc" : "desc") << "isDBAware:" << data.isDBAware() << "dbTableName:" << data.dbTableName() << "cursor:" << (data.cursor() ? "yes" : "no") << "columnCount:" << data.columnCount() << "count:" << data.count() << "autoIncrementedColumn:" << data.autoIncrementedColumn() << "visibleColumnCount:" << data.visibleColumnCount() << "isReadOnly:" << data.isReadOnly() << "isInsertingEnabled:" << data.isInsertingEnabled() << "containsRecordIdInfo:" << data.containsRecordIdInfo() << "result:" << data.result(); dbg.nospace() << ")"; return dbg.space(); } diff --git a/src/views/KDbTableViewData.h b/src/views/KDbTableViewData.h index c60e5961..f1d3128c 100644 --- a/src/views/KDbTableViewData.h +++ b/src/views/KDbTableViewData.h @@ -1,361 +1,361 @@ /* This file is part of the KDE project Copyright (C) 2002 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003-2016 Jarosław Staniek Copyright (C) 2014 Michał Poteralski This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. Original Author: Till Busch Original Project: buX (www.bux.at) */ #ifndef KDB_TABLEVIEWDATA_H #define KDB_TABLEVIEWDATA_H #include "KDbField.h" #include "KDbUtils.h" #include "KDbRecordData.h" #include "KDbOrderByColumn.h" class KDbCursor; class KDbRecordEditBuffer; class KDbResultInfo; class KDbTableViewColumn; typedef KDbUtils::AutodeletedList KDbTableViewDataBase; typedef KDbTableViewDataBase::ConstIterator KDbTableViewDataConstIterator; typedef KDbTableViewDataBase::Iterator KDbTableViewDataIterator; //! A list of records to allow configurable sorting and more. /*! @todo improve API */ class KDB_EXPORT KDbTableViewData : public QObject, protected KDbTableViewDataBase { Q_OBJECT public: //! Non-db-aware version KDbTableViewData(); //! Db-aware version. The cursor is not owned by the data. explicit KDbTableViewData(KDbCursor *c); /*! Defines two-column table usually used with comboboxes. First column is invisible and contains key values. Second column and contains user-visible value. @param keys a list of keys @param values a list of text values (must be of the same length as keys list) @param keyType a type for keys @param valueType a type for values @todo make this more generic: allow to add more columns! */ KDbTableViewData( const QList &keys, const QList &values, KDbField::Type keyType = KDbField::Text, KDbField::Type valueType = KDbField::Text); /*! Like above constructor, but keys and values are not provided. You can do this later by calling append(KDbRecordData*) method. (KDbRecordData object must have exactly two columns) */ KDbTableViewData(KDbField::Type keyType, KDbField::Type valueType); - virtual ~KDbTableViewData(); + ~KDbTableViewData() override; /*! Preloads all records provided by cursor (only for db-aware version). */ bool preloadAllRecords(); /*! Sets sorting for @a column. If @a column is -1, sorting is disabled. */ void setSorting(int column, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending); /*! @return the column number by which the data is sorted, or -1 if sorting is disabled. Initial sorted column number for data after instantiating object is -1. */ int sortColumn() const; /*! @return sorting order. This is independent of whether the data is actually sorted. sortColumn() should be checked first to see if sorting for any column is enabled (by default it is not). */ KDbOrderByColumn::SortOrder sortOrder() const; //! Sorts this data using previously set order. void sort(); /*! Adds column @a col. Warning: @a col will be owned by this object, and deleted on its destruction. */ void addColumn(KDbTableViewColumn* col); //! @return Index of visible column @a visibleIndex on global list. int globalIndexOfVisibleColumn(int visibleIndex) const; //! @return Index on list of visible columns for column @a globalIndex //! or -1 if column at @a globalIndex is not visible. int visibleColumnIndex(int globalIndex) const; /*! @return true if this db-aware data set. */ /*! @todo virtual? */ bool isDBAware() const; /*! For db-aware data set only: table name is returned; equivalent to cursor()->query()->parentTable()->name(). */ QString dbTableName() const; KDbCursor* cursor() const; int columnCount() const; //! @return number of visible columns int visibleColumnCount() const; //! @return column at index @a index (visible or not) KDbTableViewColumn* column(int c); //! @return visible column at index @a index KDbTableViewColumn* visibleColumn(int index); //! @return list of all columns QList* columns(); //! @return list of visible columns QList* visibleColumns(); /*! @return true if data is not editable. Can be set using setReadOnly() but it's still true if database cursor returned by cursor() is not 0 and has read-only connection. */ virtual bool isReadOnly() const; /*! Sets readOnly flag for this data. If @a set is true, insertingEnabled flag will be cleared automatically. @see isInsertingEnabled() */ virtual void setReadOnly(bool set); /*! @return true if data inserting is enabled (the default). */ virtual bool isInsertingEnabled() const; /*! Sets insertingEnabled flag. If true, empty record is available If @a set is true, read-only flag will be cleared automatically. @see setReadOnly() */ virtual void setInsertingEnabled(bool set); /*! Clears and initializes internal record edit buffer for incoming editing. Creates buffer using recordEditBuffer(false) (false means not db-aware type) if our data is not db-aware, or db-aware buffer if data is db-aware (isDBAware()==true). @see KDbRecordEditBuffer */ void clearRecordEditBuffer(); /*! Updates internal record edit buffer: currently edited column @a col (number @a colnum) has now assigned new value of @a newval. Uses column's caption to address the column in buffer if the buffer is of simple type, or db-aware buffer if (isDBAware()==true). (then fields are addressed with KDbField, instead of caption strings). If @a allowSignals is true (the default), aboutToChangeCell() signal is emitted. @a visibleValueForLookupField allows to pass visible value (usually a text) for a lookup field (only reasonable if col->visibleLookupColumnInfo != 0). Note that @a newval may be changed in aboutToChangeCell() signal handler. @see KDbRecordEditBuffer */ bool updateRecordEditBufferRef(KDbRecordData *record, int colnum, KDbTableViewColumn* col, QVariant* newval, bool allowSignals = true, - QVariant *visibleValueForLookupField = 0); + QVariant *visibleValueForLookupField = nullptr); /*! Added for convenience. Like above but @a newval is passed by value. */ bool updateRecordEditBuffer(KDbRecordData *record, int colnum, KDbTableViewColumn* col, const QVariant &newval, bool allowSignals = true); /*! Added for convenience. Like above but it's assumed that @a record record's columns are ordered like in table view, not like in form view. Don't use this with form views. */ bool updateRecordEditBuffer(KDbRecordData *record, int colnum, const QVariant &newval, bool allowSignals = true); //! @return record edit buffer for currently edited record. Can be 0 or empty. KDbRecordEditBuffer* recordEditBuffer() const; /*! @return last operation's result information (always not null). */ const KDbResultInfo& result() const; bool saveRecordChanges(KDbRecordData *record, bool repaint = false); bool saveNewRecord(KDbRecordData *record, bool repaint = false); bool deleteRecord(KDbRecordData *record, bool repaint = false); /*! Deletes records (by number) passed with @a recordsToDelete. Currently, this method is only for non data-aware tables. */ void deleteRecords(const QList &recordsToDelete, bool repaint = false); /*! Deletes all records. Works either for db-aware and non db-aware tables. Column's definition is not changed. For db-aware version, all records are removed from a database. Record-edit buffer is cleared. If @a repaint is true, reloadRequested() signal is emitted after deleting (if at least one record was deleted), so presenters can repaint their contents. @return true on success. */ virtual bool deleteAllRecords(bool repaint = false); /*! @internal method, used mostly by specialized classes like KexiTableView. Clears internal record structures. Record-edit buffer is cleared. Does not touch data @ database backend. Use deleteAllRecords() to safely delete all records. */ virtual void clearInternal(bool processEvents = true); /*! Inserts new @a record at index @a index. @a record will be owned by this data object. Note: Reasonable only for not not-db-aware version. */ void insertRecord(KDbRecordData *record, int index, bool repaint = false); //! @todo add this as well? void insertRecord(KDbRecordData *record, KDbRecordData *aboveRecord) //! @return index of autoincremented column. The result is cached. //! @todo what about multiple autoinc columns? //! @todo what about changing column order? int autoIncrementedColumn() const; //! Emits reloadRequested() signal to reload presenters. void reload() { emit reloadRequested(); } inline KDbRecordData* at(int index) { return KDbTableViewDataBase::at(index); } inline virtual int count() const { return KDbTableViewDataBase::count(); } inline bool isEmpty() const { return KDbTableViewDataBase::isEmpty(); } inline KDbRecordData* first() { return KDbTableViewDataBase::first(); } inline KDbRecordData* last() { return KDbTableViewDataBase::last(); } inline int indexOf(const KDbRecordData* record, int from = 0) const { return KDbTableViewDataBase::indexOf(const_cast(record), from); } inline void removeFirst() { KDbTableViewDataBase::removeFirst(); } inline void removeLast() { KDbTableViewDataBase::removeLast(); } inline void append(KDbRecordData* record) { KDbTableViewDataBase::append(record); } inline void prepend(KDbRecordData* record) { KDbTableViewDataBase::prepend(record); } inline KDbTableViewDataConstIterator constBegin() const { return KDbTableViewDataBase::constBegin(); } inline KDbTableViewDataConstIterator constEnd() const { return KDbTableViewDataBase::constEnd(); } inline KDbTableViewDataIterator begin() { return KDbTableViewDataBase::begin(); } inline KDbTableViewDataIterator end() { return KDbTableViewDataBase::end(); } /*! @return true if ROWID information is stored within every record. Only reasonable for db-aware version. ROWID information is available if KDbDriverBehavior::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false for a KDb database driver and a table has no primary key defined. Phisically, ROWID information is stored after last KDbRecordData's element, so every KDbRecordData's length is expanded by one. */ bool containsRecordIdInfo() const; //! Creates a single record data with proper number of columns. KDbRecordData* createItem() const; //! @return reusable i18n'd message //! "Please correct data in this record or use the \"Cancel record changes\" function." static QString messageYouCanImproveData(); public Q_SLOTS: //! @internal Clean up. void deleteLater(); Q_SIGNALS: void destroying(); /*! Emitted before change of the single, currently edited cell. Connect this signal to your slot and set @a result->success to false to disallow this change. You can also change @a newValue to other value, or change other columns in @a record. */ void aboutToChangeCell(KDbRecordData *record, int colnum, QVariant* newValue, KDbResultInfo* result); /*! Emitted before inserting of a new, current record. Connect this signal to your slot and set @a result->success to false to disallow this inserting. You can also change columns in @a record. */ void aboutToInsertRecord(KDbRecordData *record, KDbResultInfo* result, bool repaint); /*! Emitted before changing of an edited, current record. Connect this signal to your slot and set @a result->success to false to disallow this change. You can also change columns in @a record. */ void aboutToUpdateRecord(KDbRecordData *record, KDbRecordEditBuffer* buffer, KDbResultInfo* result); void recordUpdated(KDbRecordData*); //!< Current record has been updated void recordInserted(KDbRecordData*, bool repaint); //!< A record has been inserted //! A record has been inserted at @a index position (not db-aware data only) void recordInserted(KDbRecordData*, int index, bool repaint); /*! Emitted before deleting of a current record. Connect this signal to your slot and set @a result->success to false to disallow this deleting. */ void aboutToDeleteRecord(KDbRecordData *record, KDbResultInfo* result, bool repaint); //! Current record has been deleted void recordDeleted(); //! Records have been deleted void recordsDeleted(const QList &recordsToDelete); //! Displayed data needs to be reloaded in all presenters. void reloadRequested(); void recordRepaintRequested(KDbRecordData*); protected: //! Used by KDbTableViewColumn::setVisible() void columnVisibilityChanged(const KDbTableViewColumn &column); private: void init(); void init(const QList &keys, const QList &values, KDbField::Type keyType, KDbField::Type valueType); //! @internal for saveRecordChanges() and saveNewRecord() bool saveRecord(KDbRecordData *record, bool insert, bool repaint); friend class KDbTableViewColumn; Q_DISABLE_COPY(KDbTableViewData) class Private; Private * const d; }; //! Sends information about data @a data to debug output @a dbg. //! @since 3.1 KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbTableViewData &data); #endif diff --git a/tests/features/main.cpp b/tests/features/main.cpp index 8cfd7495..c49938e0 100644 --- a/tests/features/main.cpp +++ b/tests/features/main.cpp @@ -1,352 +1,352 @@ /* This file is part of the KDE project Copyright (C) 2003-2010 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; QByteArray prgname; QString db_name; QString drv_id; QString test_name; KDbCursor::Options cursor_options; bool db_name_required = true; KDbConnectionData conn_data; //! @todo IMPORTANT: replace QPointer conn; -KDbConnection* conn = 0; +KDbConnection* conn = nullptr; //! @todo IMPORTANT: replace QPointer driver; KDbDriver* driver; -QApplication *app = 0; +QApplication *app = nullptr; #include "dbcreation_test.h" #include "cursors_test.h" #include "schema_test.h" #include "tables_test.h" #ifndef NO_GUI # include "tableview_test.h" #endif #include "parser_test.h" #include "dr_prop_test.h" static int finish(int code) { qDebug() << "main:" << test_name << "test:" << (code==0?"PASSED":"ERROR"); if (code != 0 && conn) { qDebug() << "main:" << conn->result(); conn->disconnect(); delete conn; } return code; } //! @return true if option @a option or @a shortOption is found //! Removes the option. static bool takeOption(QStringList &args, const QString &option, const QString &shortOption = QString()) { return args.removeOne(QLatin1String("-") + (shortOption.isEmpty() ? option : shortOption)) || args.removeOne(QLatin1String("--") + option); } //! @return next element after option @a option or @a shortOption, what should mean parameter //! Removes option and its argument. static QString takeOptionWithArg(QStringList &args, const QString &option, const QString &shortOption = QString()) { int index = args.indexOf(QLatin1String("-") + (shortOption.isEmpty() ? option : shortOption)); if (index == -1) index = args.indexOf(QLatin1String("--") + option); if (index == -1) return QString(); args.removeAt(index); return args.takeAt(index); // option's argument } #define APPNAME "kdbfeaturestest" static void showHelp() { QTextStream s(stdout); s << APPNAME ", version " KDB_VERSION_STRING "\n" "\nA set of tests for the KDb library API." "\nEvery test is mostly driver-independent." "\n (c) 2003-2016, Kexi Team" "\n (c) 2003-2006, OpenOffice Software LLC." "\n" "\nUsage: " APPNAME " --test [options]" "\n driver_id [db_name] [sql_statement]" "\n" "\nOptions:" "\n --help Displays this help and exits" "\n --buffered-cursors Optional switch: turns cursors used in any" "\n tests to be buffered" "\n -h, --host Host name to use when connecting" "\n to server backends" "\n -p, --password Password to use when connecting to server" "\n backends" "\n -P, --port Port number to use when connecting to server" "\n backends" "\n --query-params Query parameters separated by '|' character" "\n that will be passed to query statement" "\n to replace [...] placeholders" "\n -t, --test Specifies test to execute; required" "\n Available tests:" "\n - cursors: test for cursors behavior" "\n - schema: test for db schema retrieving" "\n - dbcreation: test for new db creation" "\n - tables: test for tables creation and data" "\n inserting" //"\n - tableview: test for KexiDataTableView data-aware //"\n widget "\n - parser: test for parsing sql statements," "\n returns debug string for a given" "\n sql statement or error message" "\n - dr_prop: shows properties of selected" "\n driver" "\n -u, --user User name to use when connecting to servers" "\n backends" "\n" "\nNotes:" "\n1. 'dr_prop' requires argument" "\n2. 'parser' test requires , and " "\n arguments" "\n3. All other tests require and arguments" "\n4. 'tables' test automatically runs 'dbcreation' test" "\n is removed if already exists" "\n5. must be a valid database created using KDb," "\n e.g. using the \"tables\" test" "\n" "\nArguments:" "\n driver_id Driver ID, e.g. org.kde.kdb.sqlite;" "\n if a word without \".\" is used," "\n \"org.kde.kdb.\" will be prepended" "\n db_name Database name" "\n sql_statement Optional SQL statement (for parser test)" "\n" "\nExamples:" "\n " APPNAME " -t dr_prop sqlite" "\n Shows properties of the SQLite driver" "\n " APPNAME " -p PASSWORD -u USER -t tables mysql mysqltest" "\n Creates database \"mysqltest\" with test" "\n tables and datas" "\n " APPNAME " -p PASSWORD -u USER -t tables -h myhost.org \\" "\n postgresql pgsqltest" "\n Creates database \"pgsqltest\" with test" "\n tables and data on host \"myhost.org\"" "\n"; } int main(int argc, char** argv) { // int minargs = 2; #ifndef NO_GUI bool gui = false; #endif QFileInfo info = QFileInfo(argv[0]); prgname = info.baseName().toLatin1(); QStringList args; for (int i=1; iisFileBased()) { qDebug() << "main: MIME types for" << driver->metaData()->id() << ":" << driver->metaData()->mimeTypes(); } const bool bufCursors = takeOption(args, "buffered-cursors"); QString queryParams = takeOptionWithArg(args, "query-params"); //open connection if (args.count() >= 2) db_name = args[1]; if (db_name_required && db_name.isEmpty()) { qDebug() << prgname << ": database name?"; return finish(1); } conn_data.setDatabaseName(db_name); if (!db_name.isEmpty() ) { //additional switches: if (bufCursors) { cursor_options |= KDbCursor::Option::Buffered; } conn = driver->createConnection(conn_data); if (!conn || driver->result().isError()) { qDebug() << driver->result(); return finish(1); } qDebug() << "main: KDbConnection object created."; if (!conn->connect()) { qDebug() << conn->result(); return finish(1); } qDebug() << "main: KDbConnection::connect() OK."; } //start test: int r = 0; if (test_name == "cursors") r = cursorsTest(); else if (test_name == "schema") r = schemaTest(); else if (test_name == "dbcreation") r = dbCreationTest(); else if (test_name == "tables") r = tablesTest(conn); #ifndef NO_GUI else if (test_name == "tableview") r = tableViewTest(); #endif else if (test_name == "parser") { QStringList params; if (!queryParams.isEmpty()) params = queryParams.split("|"); r = parserTest(KDbEscapedString(args[2]), params); } else if (test_name == "dr_prop") r = drPropTest(); else { qWarning() << "No such test:" << test_name; qWarning() << "Available tests are:" << tests; // usage(); return finish(1); } #ifndef NO_GUI if (app && r == 0 && gui) app->exec(); #endif if (r) qDebug() << "RECENT SQL STATEMENT: " << conn->recentSQLString(); if (conn && !conn->disconnect()) r = 1; // qDebug() << "!!! KDbTransaction::globalcount == " << KDbTransaction::globalCount(); // qDebug() << "!!! KDbTransactionData::globalcount == " << KDbTransactionData::globalCount(); delete app; return finish(r); } diff --git a/tests/features/parser_test.h b/tests/features/parser_test.h index 81de4485..1a38107f 100644 --- a/tests/features/parser_test.h +++ b/tests/features/parser_test.h @@ -1,70 +1,70 @@ /* This file is part of the KDE project Copyright (C) 2003-2004 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PARSER_TEST_H #define PARSER_TEST_H #include #include #include int parserTest(const KDbEscapedString &st, const QStringList ¶ms) { int r = 0; if (!conn->useDatabase()) { qDebug() << conn->result(); return 1; } KDbParser parser(conn); const bool ok = parser.parse(st); KDbQuerySchema *q = parser.query(); QList variantParams; for(const QString ¶m : params) { variantParams.append(param.toLocal8Bit()); } if (ok && q) { cout << qPrintable(KDbUtils::debugString(*q)) << '\n'; KDbNativeStatementBuilder builder(conn); KDbEscapedString sql; if (builder.generateSelectStatement(&sql, q, variantParams)) { cout << "-STATEMENT:\n" << sql.toByteArray().constData() << '\n'; } else { cout << "-CANNOT GENERATE STATEMENT\n"; } } else { qDebug() << parser.error(); r = 1; } delete q; - q = 0; + q = nullptr; if (!conn->closeDatabase()) { qDebug() << conn->result(); return 1; } return r; } #endif diff --git a/tests/features/tables_test_p.h b/tests/features/tables_test_p.h index 2fe5da44..ef17e1fb 100644 --- a/tests/features/tables_test_p.h +++ b/tests/features/tables_test_p.h @@ -1,100 +1,100 @@ /* This file is part of the KDE project Copyright (C) 2003-2004 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TABLETEST_P_H #define TABLETEST_P_H int tablesTest_createTables(KDbConnection *conn) { conn->setAutoCommit(false); KDbTransaction t = conn->beginTransaction(); if (conn->result().isError()) { qDebug() << conn->result(); return 1; } //now: lets create tables: KDbField *f; KDbTableSchema *t_persons = new KDbTableSchema("persons"); t_persons->setCaption("Persons in our factory"); t_persons->addField(f = new KDbField("id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); f->setCaption("ID"); - t_persons->addField(f = new KDbField("age", KDbField::Integer, 0, KDbField::Unsigned)); + t_persons->addField(f = new KDbField("age", KDbField::Integer, nullptr, KDbField::Unsigned)); f->setCaption("Age"); t_persons->addField(f = new KDbField("name", KDbField::Text)); f->setCaption("Name"); t_persons->addField(f = new KDbField("surname", KDbField::Text)); f->setCaption("Surname"); if (!conn->createTable(t_persons)) { qDebug() << conn->result(); return 1; } qDebug() << "-- PERSONS created --"; qDebug() << *t_persons; if (!conn->insertRecord(t_persons, QVariant(1), QVariant(27), QVariant("Jaroslaw"), QVariant("Staniek")) || !conn->insertRecord(t_persons, QVariant(2), QVariant(60), QVariant("Lech"), QVariant("Walesa")) || !conn->insertRecord(t_persons, QVariant(3), QVariant(45), QVariant("Bill"), QVariant("Gates")) || !conn->insertRecord(t_persons, QVariant(4), QVariant(35), QVariant("John"), QVariant("Smith")) ) { qDebug() << "-- PERSONS data err. --"; return 1; } qDebug() << "-- PERSONS data created --"; KDbTableSchema *t_cars = new KDbTableSchema("cars"); t_cars->setCaption("Cars owned by persons"); t_cars->addField(f = new KDbField("id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); f->setCaption("ID"); - t_cars->addField(f = new KDbField("owner", KDbField::Integer, 0, KDbField::Unsigned)); + t_cars->addField(f = new KDbField("owner", KDbField::Integer, nullptr, KDbField::Unsigned)); f->setCaption("Car owner"); t_cars->addField(f = new KDbField("model", KDbField::Text)); f->setCaption("Car model"); if (!conn->createTable(t_cars)) { qDebug() << conn->result(); return 1; } qDebug() << "-- CARS created --"; if (!conn->insertRecord(t_cars, QVariant(1), QVariant(1), QVariant("Fiat")) || !conn->insertRecord(t_cars, QVariant(2), QVariant(2), QVariant("Syrena")) || !conn->insertRecord(t_cars, QVariant(3), QVariant(3), QVariant("Chrysler")) || !conn->insertRecord(t_cars, QVariant(4), QVariant(3), QVariant("BMW")) || !conn->insertRecord(t_cars, QVariant(5), QVariant(4), QVariant("Volvo")) ) { qDebug() << "-- CARS data err. --"; return 1; } qDebug() << "-- CARS data created --"; if (!conn->commitTransaction(t)) { qDebug() << conn->result(); return 1; } qDebug() << "NOW, TABLE LIST: "; QStringList tnames = conn->tableNames(); for (QStringList::iterator it = tnames.begin(); it != tnames.end(); ++it) { qDebug() << " - " << (*it); } return 0; } #endif diff --git a/tools/sdc.py b/tools/sdc.py index 457f6c80..b4c2b06b 100755 --- a/tools/sdc.py +++ b/tools/sdc.py @@ -1,1009 +1,1009 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function # # This file is part of the KDE project # Copyright (C) 2010-2015 Jarosław Staniek # # Shared Data Compiler (SDC) version = '0.2' # A tool that takes a header file with C++ class declaration with specification # of class' data members and injects getters, setters and facilities for # Qt 5-compatible implicitly/explicitly sharing. Tedious, manual work is considerably # reduced. The generated code is enriched with Doxygen-style documentation. # # Syntax: # Add an //SDC: comment to the class declaration line # class MYLIB_EXPORT MyClass [: public SuperClass] //SDC: [CLASS_OPTIONS] # # supported CLASS_OPTIONS: namespace=NAMESPACE, with_from_to_map, # operator==, explicit, virtual_dtor, custom_clone # # TODO: explain details in README-SDC.md # # Specification of each class' data members should be in form: # # TYPE NAME; //SDC: [DATA_MEMBER_OPTIONS] # # Supported DATA_MEMBER_OPTIONS: default=DEFAULT_VALUE, no_getter, no_setter, # getter=CUSTOM_GETTER_NAME, # setter=CUSTOM_SETTER_NAME, # custom, custom_getter, custom_setter, # default_setter=DEFAULT_SETTER_PARAM, # mutable, simple_type, invokable, # internal, custom_clone # If NAME contains '(' then 'TYPE NAME;' is added to the shared data class # as a method declaration. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # # As a special exception, you may create a larger work that contains # code generated by the Shared Data Compiler and distribute that work # under terms of the GNU Lesser General Public License (LGPL) as published # by the Free Software Foundation; either version 2.1 of the License, # or (at your option) any later version or under terms that are fully # compatible with these licenses. # # Alternatively, if you modify or redistribute the Shared Data Compiler tool # itself, you may (at your option) remove this special exception, which will # cause the resulting generted source code files to be licensed under # the GNU General Public License (either version 2 of the License, or # at your option under any later version) without this special exception. # # This special exception was added by Jarosław Staniek. # Contact him for more licensing options, e.g. using in non-Open Source projects. # import os, sys, shlex line = '' APPNAME = "Shared Data Compiler" def usage(): print('''Usage: %s [INPUT] [OUTPUT] %s version %s ''' % (sys.argv[0], APPNAME, version)) def syntax_error(msg): print(APPNAME, "Syntax error in %s: %s" % (in_fname, msg), file=sys.stderr) sys.exit(1) def warning(*objs): print(APPNAME, "WARNING:", *objs, file=sys.stderr) if len(sys.argv) < 3: usage() sys.exit(0) # --- open --- in_fname = sys.argv[1] out_fname = sys.argv[2] try: infile = open(in_fname, "rb") # binary mode needed for Windows outfile = open(out_fname, "w") except Exception, inst: print(inst) sys.exit(1) outfile_sdc = None # --- utils --- def param(lst, name): for item in lst: s = item.split('=') if len(s) > 1 and s[0] == name: return s[1] return '' def param_exists(lst, name): try: if lst.index(name) >= 0: return True except ValueError: pass return False def find_index(list, elem): try: i = list.index(elem) except ValueError: i = -1 return i def find_last_index(list, elem): i = find_index(list[::-1], elem) #reverted return -1 if i == -1 else len(list) - i - 1 # --- process --- shared_class_name = '' shared_class_options = {} generated_code_inserted = False shared_class_inserted = False data_class_ctor = '' data_class_copy_ctor = '' data_class_members = '' members_list = [] data_accesors = '' protected_data_accesors = '' main_ctor = '' member = {} toMap_impl = '' fromMap_impl = '' def get_file(fname): if fname.rfind(os.sep) == -1: return fname return fname[fname.rfind(os.sep)+1:] def open_sdc(): global outfile_sdc, shared_class_options if not outfile_sdc: sdc_fname = out_fname.replace('.h', '_sdc.cpp') try: outfile_sdc = open(sdc_fname, "w") except Exception, inst: print(inst) sys.exit(1) outfile_sdc.write(warningHeader()) outfile_sdc.write("""#include "%s" #include """ % get_file(out_fname)) if shared_class_options['namespace']: outfile_sdc.write("""using namespace %s; """ % shared_class_options['namespace']) """ Inserts generated fromMap(), toMap() code into the output. Declarations are inserted into the header, definitions into extra *_sdc.cpp file """ def insert_fromMap_toMap_methods(): global outfile, shared_class_name, toMap_impl, fromMap_impl outfile.write(""" /*! @return map with saved attributes of the %s object. @see %s(const QMap&, bool *). */ QMap toMap() const { return d->toMap(); } """ % (shared_class_name, shared_class_name)) open_sdc() outfile_sdc.write("""%s::Data::Data(const QMap &map, bool *ok) { if (ok) { *ok = true; } %s} QMap %s::Data::toMap() const { QMap map; %s return map; } """ % (shared_class_name, fromMap_impl, shared_class_name, toMap_impl)) outfile_sdc.close() """ Inserts generated operator==() code for shared class into the output. """ def insert_operator_eq(): global outfile, shared_class_name, shared_class_options, superclass data = 'data()' if superclass else 'd' otherData = 'other.' + data if not shared_class_options['explicit']: # deep comparison only for implicitly shared data data = '*' + data otherData = '*' + otherData outfile.write(""" //! @return true if this object is equal to @a other; otherwise returns false. bool operator==(const %s &other) const { return %s == %s; } """ % (shared_class_name, data, otherData)) """ Inserts generated operator!=() code for shared class into the output. """ def insert_operator_neq(): global outfile, shared_class_name, shared_class_options, superclass outfile.write(""" //! @return true if this object is not equal to @a other; otherwise returns false. bool operator!=(const %s &other) const { return !operator==(other); } """ % (shared_class_name)) """ Inserts generated clone() method (makes sense for explicitly shared class). """ def insert_clone(): global outfile, shared_class_name, shared_class_options, superclass line = """ //! Clones the object with all attributes; the copy isn't shared with the original. - virtual %s clone() const%s""" % (shared_class_name, 'Q_DECL_OVERRIDE ' if superclass else '') + virtual %s clone() const%s""" % (shared_class_name, 'override ' if superclass else '') custom_clone = False; # not needed I guess: shared_class_options['custom_clone'] if custom_clone: line += """; """ else: line += """ { return %s(d->clone()); } """ % (shared_class_name) outfile.write(line) """ Inserts generated Data::operator==() code into the output. """ def insert_data_operator_eq(): global outfile, members_list, superclass outfile.write(""" bool operator==(const Data &other) const { """) outfile.write(" return ") first = True; space = """ && """ if superclass: outfile.write('%s::Data::operator==(other)' % superclass) first = False; for member in members_list: if member['internal']: continue outfile.write("""%s%s == other.%s""" % ('' if first else space, member['name'], member['name'])) if first: first = False outfile.write("""; } """) """ Inserts generated code into the output. """ def insert_generated_code(context): global infile, outfile, generated_code_inserted, data_class_ctor, data_class_copy_ctor, superclass global data_class_members, data_accesors, protected_data_accesors, main_ctor, shared_class_name, shared_class_options global prev_line if generated_code_inserted: return; #print "--------insert_generated_code--------" #outfile.write('//CONTEXT:' + str(context) + '\n') #outfile.write('//data_class_ctor>\n') outfile.write(data_class_ctor) #outfile.write('// &map, bool *ok); QMap toMap() const; """) if shared_class_options['operator=='] and not shared_class_options['explicit']: insert_data_operator_eq() outfile.write(data_class_members) outfile.write(main_ctor) outfile.write(data_accesors) outfile.write("\n") if shared_class_options['with_from_to_map']: insert_fromMap_toMap_methods() if shared_class_options['operator==']: insert_operator_eq() insert_operator_neq() if shared_class_options['explicit'] and (not superclass or shared_class_options['custom_clone']): insert_clone() if protected_data_accesors: outfile.write("protected:") outfile.write(protected_data_accesors) outfile.write("\npublic:") outfile.write("\n") generated_code_inserted = True """ Reads documentation for single section (setter or getter) and returns it. Leaves the file pointer before */ or another @getter/@setter mark. """ def read_getter_or_setter_doc(): global prev_line, line result = '' while True: prev_pos = infile.tell() prev_line = line line = infile.readline() if not line: break elif line.find('*/') != -1 or line.find('@getter') != -1 or line.find('@setter') != -1: #print "seek prev from " + line infile.seek(prev_pos) break else: result += line return result def process_docs(comment): global prev_line, line result = {} while True: prev_line = line line = infile.readline() #print "process_docs: " + line if not line: break elif line.find('*/') != -1: if result == {}: insert_generated_code(1) outfile.write(line) break elif line.find('@getter') != -1: result['getter'] = read_getter_or_setter_doc() elif line.find('@setter') != -1: result['setter'] = read_getter_or_setter_doc() else: insert_generated_code(2) outfile.write(comment) outfile.write(line) if result == {}: result = None #print "process_docs result: " + str(result) return result def try_read_member_docs(comment): global prev_line, line prev_pos = infile.tell() result = comment while True: prev_line = line line = infile.readline() if not line or line.find('@getter') != -1 or line.find('@setter') != -1: infile.seek(prev_pos) return None elif line.find('*/') != -1: return result else: result += line return None """ makes setter out of name or returns forceSetter if specified """ def makeSetter(name, forceSetter): if forceSetter: return forceSetter return 'set' + name[0].upper() + name[1:] def update_data_accesors(): global data_accesors, protected_data_accesors, member if not member['no_getter']: if member.has_key('getter_docs'): val = '\n /*!\n' + member['getter_docs'] + ' */' if member['access'] == 'public': data_accesors += val else: # protected protected_data_accesors += val getter = member['getter'] if not getter: getter = member['name'] invokable = 'Q_INVOKABLE ' if member['invokable'] else '' if member['custom_getter']: val = """ %s%s %s() const; """ % (invokable, member['type'], getter) else: val = """ %s%s %s() const { return %s->%s; } """ % (invokable, member['type'], getter, 'data()' if superclass else 'd', member['name']) if member['access'] == 'public': data_accesors += val else: # protected protected_data_accesors += val if not member['no_setter']: if member.has_key('setter_docs'): val = '\n /*!\n' + member['setter_docs'] + ' */' if member['access'] == 'public': data_accesors += val else: # protected protected_data_accesors += val # heuristics to check if the const & should be used: arg_type = member['type'] if arg_type.lower() != arg_type and not member['simple_type']: arg_type = 'const %s &' % arg_type setter = makeSetter(member['name'], member['setter']) default_setter = (' = ' + member['default_setter']) if member['default_setter'] else '' invokable = 'Q_INVOKABLE ' if member['invokable'] else '' if member['custom_setter']: val = """ %svoid %s(%s %s%s); """ % (invokable, setter, arg_type, member['name'], default_setter) else: val = """ %svoid %s(%s %s%s) { %s->%s = %s; } """ % (invokable, setter, arg_type, member['name'], default_setter, 'data()' if superclass else 'd', member['name'], member['name']) if member['access'] == 'public': data_accesors += val else: # protected protected_data_accesors += val def data_member_found(lst): return len(lst) > 2 and lst[0] != 'class' and find_index(lst, '//SDC:') >= 2 # sets shared_class_options[option_name] to proper value; returns lst with removed element option_name if exists def get_shared_class_option(lst, option_name): global shared_class_options shared_class_options[option_name] = param_exists(lst, option_name) if shared_class_options[option_name]: lst.remove(option_name) return lst """ like get_shared_class_option() but also gets value (not just checks for existence) """ def get_shared_class_option_with_value(lst, option_name): global shared_class_options for item in lst: s = item.split('=') if len(s) > 1 and s[0] == option_name: lst.remove(item) shared_class_options[option_name] = s[1] return lst shared_class_options[option_name] = False return lst def enabled_shared_class_options(): global shared_class_options result = [] for opt in shared_class_options: if shared_class_options[opt]: result.append(opt) # add some defaults if find_index(result, 'explicit') == -1: result.append('implicit') return result def warningHeader(): return """/**************************************************************************** ** Shared Class code from reading file '%s' ** ** Created ** by: The Shared Data Compiler version %s ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ """ % (get_file(in_fname), version) """ generates conversion code to string from many types, used by Data::toMap() @todo more types """ def generate_toString_conversion(name, _type): if _type == 'QString' or _type == 'QByteArray': return name elif _type == 'bool': return 'QString::number((int)%s)' % name # 0 or 1 return 'QVariant(%s).toString()' % name """ generates conversion code from string to many types, used by Data(QMap<..>) @todo more types """ def generate_fromString_conversion(name, _type): s = 'map[QLatin1String(\"%s\")]' % name if _type == 'bool': # 0 or 1 return """bool %sOk; %s = %s.toInt(&%sOk) == 1; if (!%sOk && ok) { *ok = false; }""" % (name, name, s, name, name) elif _type == 'int': return """bool %sOk; %s = %s.toInt(&%sOk); if (!%sOk && ok) { *ok = false; }""" % (name, name, s, name, name) else: # QString... return "%s = %s;" % (name, s) # returns position (line number) for #include or -1 if #include isn't needed def get_pos_for_QSharedData_h(): global infile, prev_line, line prev_pos = infile.tell() line_number = -1 infile.seek(0) # find last #include last_include = -1 while True: prev_line = line line = infile.readline().lower() if not line: break line_number += 1 if line.find('#include ') != -1: if line.find('qshareddata') != -1: last_include = -1 break else: last_include = line_number + 1 infile.seek(prev_pos) return last_include # replaces "Foo>" with "Foo< ABC< DEF > >" to avoid build errors def fix_templates(s): if s.count('<') < 2: return s result='' for c in s: if c == '>': result += ' ' result += c if c == '<': result += ' ' return result def other_comment(line): ln = line.strip(' ') return ln.startswith('/**') \ or ln.startswith('/*!') \ or ln .startswith('//!') \ or ln.startswith('///') """ @return (shared_class_name, export_macro, superclass) """ def get_shared_class_name_export_and_superclass(lst): # if 'lst' contains ':' and then 'public', we have inheritance i = find_last_index(lst, 'public') # last index because there can be multiple 'public/protected' and most likely we want the last #print i if i >= 3: inheritance_type = 'public' else: i = find_last_index(lst, 'protected') inheritance_type = 'protected' if i >= 3 else '' # - if there's inheritance and export_macro exists, lst has at least 6 elements: # ['class', export_macro, shared_class_name, ':', 'public/protected', superclass, ...]: # - if export_macro exists, lst has at least 3 elements: # ['class', export_macro, shared_class_name, ...]: expected_len = 6 if inheritance_type else 3 if len(lst) >= expected_len: _shared_class_name = lst[2] _export_macro = lst[1] else: # no export_macro _shared_class_name = lst[1] _export_macro = '' _superclass = lst[i + 1] if inheritance_type else '' return (_shared_class_name, _export_macro, _superclass) """ Strips the C++ comment //: @return lst with removed first found element that starts with '//' and all following """ def remove_cpp_comment(lst): result = [] for el in lst: if el.startswith('//'): break result.append(el) return result """ Simplifies a multi-line doc like this: /*! Foo */ to: /*! Foo */ """ def simplify_multiline_doc(doc): lines = doc.split('\n') result = [] if lines[0].strip() == '': result += '\n' lines.pop(0) if len(lines) < 2: return doc indentation = ''; i = 0; for c in lines[0]: if c == ' ' or c == '\t': indentation += c else: break line0 = lines[0].strip() line1 = lines[1].strip() if line0 == '/*!' and not line1.startswith('/*') and not line1.startswith('/*'): result += indentation + '/*! ' + line1 + '\n' lines = lines[2:] # because line 0 and 1 already processed for line in lines: if line.strip() == '*/': # add to prev line last = result.pop() result += last[:-1] + ' */\n' else: result += line + '\n' result[-1] = result[-1][:-1] # remove last \n return ''.join(result) def process(): global infile, outfile, generated_code_inserted, data_class_ctor, data_class_copy_ctor global shared_class_name, superclass, shared_class_options, shared_class_inserted, data_class_members global members_list, data_accesors, member, main_ctor, toMap_impl, fromMap_impl global prev_line, line outfile.write(warningHeader()) member = {} after_member = False data_class_ctor = '' data_class_copy_ctor = '' data_class_ctor_changed = False data_class_copy_ctor_changed = False position_for_include_QSharedData_h = get_pos_for_QSharedData_h() line_number = -1 # used for writing #include while True: prev_line = line line = infile.readline() line_number += 1 if not line: break # print line, lst = line.split() #print lst, find_index(lst, '//SDC:') if line_number == position_for_include_QSharedData_h: outfile.write("""#include """) position_for_include_QSharedData_h = -1 line_starts_with_class = len(lst) > 0 and lst[0] == 'class' if line_starts_with_class and find_index(lst, '//SDC:') < 0: shared_class_inserted = False # required because otherwise QSharedDataPointer d will be added to all classes outfile.write(line) elif line_starts_with_class and find_index(lst, '//SDC:') > 0: # re-init variables, needed if there are more than one shared class per file shared_class_inserted = False generated_code_inserted = False shared_class_options = {} data_class_ctor = '' data_class_copy_ctor = '' data_class_members = '' members_list = [] data_accesors = '' protected_data_accesors = '' toMap_impl = '' fromMap_impl = '' data_class_ctor_changed = False data_class_copy_ctor_changed = False lst = shlex.split(line) # better than default split() # Syntax: shared class [EXPORT_MACRO] [CLASS_OPTIONS] MyClass [: public SuperClass] # Any unrecognized bits are left out, this lets to use EXPORT_MACRO=MYLIB_EXPORT for example. # output: class [EXPORT_MACRO] MyClass [: public SuperClass] lst = get_shared_class_option(lst, 'explicit') lst = get_shared_class_option(lst, 'operator==') lst = get_shared_class_option(lst, 'with_from_to_map') lst = get_shared_class_option(lst, 'virtual_dtor') lst = get_shared_class_option(lst, 'custom_clone') # only insert declaration of clone() lst = get_shared_class_option_with_value(lst, 'namespace') (shared_class_name, export_macro, superclass) = get_shared_class_name_export_and_superclass(lst) if superclass: shared_class_options['virtual_dtor'] = True # inheritance implies this if shared_class_options['custom_clone'] and not shared_class_options['explicit']: warning('\'custom_clone\' class option only supported with \'explicit\' class option') shared_class_options['custom_clone'] = False main_ctor = """ }; %s() : %s(new Data) { } %s(const %s &other) : %s { } """ % (shared_class_name, superclass if superclass else 'd', shared_class_name, shared_class_name, (superclass + '(other)') if superclass else 'd(other.d)') if superclass: main_ctor += """ %s(const %s &other) : %s(other) { if (!data()) { // '@a 'other' does not store suitable data, create a new one and copy what we have d = new Data(*d.data()); } } """ % (shared_class_name, superclass, superclass) if shared_class_options['with_from_to_map']: main_ctor += """ /*! Constructor for %s object, takes attributes saved to map @a map. If @a ok is not 0, sets *ok to true on success and to false on failure. @see toMap(). */ %s(const QMap &map, bool *ok) : d(new Data(map, ok)) { } """ % (shared_class_name, shared_class_name) main_ctor += """ %s~%s(); """ % (('virtual ' if shared_class_options['virtual_dtor'] else ''), shared_class_name) if shared_class_options['explicit']: outfile.write("""/*! @note objects of this class are explicitly shared, what means they behave like regular C++ pointers, except that by doing reference counting and not deleting the shared data object until the reference count is 0, they avoid the dangling pointer problem. See Qt documentation. */ """) else: outfile.write("""/*! @note objects of this class are implicitly shared, what means they have value semantics by offering copy-on-write behaviour to maximize resource usage and minimize copying. Only a pointer to the data is passed around. See Qt documentation. */ """) # Finally output the class name: use all remaining elements of 'lst' except # the 0th (which is the 'shared' keyword): outfile.write('//! @note This class has been generated using the following SDC class options: ' + ', '.join(enabled_shared_class_options()) + '\n') outfile.write(' '.join(remove_cpp_comment(lst)) + '\n') while True: prev_line = line line = infile.readline() # output everything until 'public:' outfile.write(line) if line.strip().startswith('public:'): break shared_class_inserted = True elif len(lst) >= 2 and lst[0] == '#if' and lst[1] == '0': insert_generated_code(3) outfile.write(line) while True: prev_line = line line = infile.readline() lst = line.split() if not line: break elif len(lst) >= 1 and lst[0] == '#endif': outfile.write(line) break outfile.write(line) elif len(lst) == 1 and (lst[0] == '/**' or lst[0] == '/*!'): comment = line member_docs = try_read_member_docs(comment) #print "member_docs:" + str(member_docs) docs = None if member_docs: member = {} member['docs'] = '\n ' + member_docs.replace('\n', '\n ') + ' */'; else: docs = process_docs(comment) if docs: #print "DOCS:" + str(docs) member = {} if docs.has_key('getter'): member['getter_docs'] = docs['getter'] if docs.has_key('setter'): member['setter_docs'] = docs['setter'] elif not member_docs: insert_generated_code(4) outfile.write(comment) elif len(lst) >= 2 and lst[0] == 'enum': # skip enums: until '};' found outfile.write(line) while True: prev_line = line line = infile.readline() lst = line.split() if len(lst) > 0 and lst[0] == '};': outfile.write(line) break outfile.write(line) elif data_member_found(lst): """ for syntax see top of the file """ if lst[1].endswith(';'): lst[1] = lst[1][:-1] # strip ';' from member name sdc_index = find_index(lst, '//SDC:') options_list = lst[sdc_index+1:] #print lst member['name'] = lst[1] member['type'] = fix_templates(lst[0]) member['access'] = 'protected' if (find_index(options_list, 'protected') >= 0) else 'public' member['default'] = param(options_list, 'default') member['default_setter'] = param(options_list, 'default_setter') member['internal'] = param_exists(options_list, 'internal'); # skip Data::operators and setter/getter member['no_getter'] = param_exists(options_list, 'no_getter') or member['internal'] member['getter'] = param(options_list, 'getter') member['no_setter'] = param_exists(options_list, 'no_setter') or member['internal'] member['setter'] = param(options_list, 'setter') member['custom'] = param_exists(options_list, 'custom') member['custom_getter'] = param_exists(options_list, 'custom_getter') or member['custom'] member['custom_setter'] = param_exists(options_list, 'custom_setter') or member['custom'] member['mutable'] = param_exists(options_list, 'mutable') member['simple_type'] = param_exists(options_list, 'simple_type') member['invokable'] = param_exists(options_list, 'invokable') # '(' found in name so it's a method declaration -> just add it to the data class isMethodDeclaration = find_index(member['name'], '(') >= 0 isInternalMember = member['no_getter'] and member['no_setter'] #print member if not data_class_ctor_changed: data_class_ctor = """ //! @internal data class used to implement %s shared class %s. //! Provides thread-safe reference counting. class Data : public %s { public: Data() """ % ('explicitly' if shared_class_options['explicit'] else 'implicitly', shared_class_name, (superclass + '::Data') if superclass else 'QSharedData') if not data_class_copy_ctor_changed: data_class_copy_ctor = '' if superclass: data_class_copy_ctor += """ Data(const %s::Data &other) : %s::Data(other) { } """ % (superclass, superclass) data_class_copy_ctor += """ Data(const Data &other) : %s(other) """ % ((superclass + '::Data') if superclass else 'QSharedData') data_class_copy_ctor_changed = True if not isMethodDeclaration: members_list.append(member); if member['default']: data_class_ctor += ' ' if data_class_ctor_changed: data_class_ctor += ', ' else: data_class_ctor += ': ' data_class_ctor_changed = True data_class_ctor += member['name'] + '(' + member['default'] + ')\n' # print data_class_ctor if not member['internal']: data_class_copy_ctor += ' , %s(other.%s)\n' % (member['name'], member['name']) if member.has_key('docs'): data_class_members += simplify_multiline_doc(member['docs']) + '\n'; mutable = 'mutable ' if member['mutable'] else '' data_class_members += " %s%s;" % (mutable, ' '.join(lst[:sdc_index])) # add doc for shared data member if not isMethodDeclaration: if isInternalMember: data_class_members += ' //!< @internal' else: data_class_members += ' //!< @see ' if not member['no_getter']: getter = member['getter'] if not getter: getter = member['name'] data_class_members += "%s::%s()" % (shared_class_name, getter) if not member['no_setter']: if not member['no_getter']: data_class_members += ", " setter = makeSetter(member['name'], member['setter']) data_class_members += "%s::%s()" % (shared_class_name, setter) data_class_members += '\n' if not isMethodDeclaration: if shared_class_options['with_from_to_map']: toMap_impl += ' map[QLatin1String(\"%s\")] = %s;\n' % (member['name'], generate_toString_conversion(member['name'], member['type'])) fromMap_impl += ' %s\n' % generate_fromString_conversion(member['name'], member['type']) update_data_accesors() member = {} if len(prev_line.split()) == 0: # remove current line because it's not going to the output now; this helps to remove duplicated empty lines line = '' after_member = True elif len(lst) > 0 and lst[0] == '};' and line[:2] == '};' and shared_class_inserted: insert_generated_code(5) # outfile.write('\nprivate:\n'); outfile.write('\nprotected:\n'); if shared_class_options['explicit']: if superclass: outfile.write(""" virtual const Data* data() const { return dynamic_cast(%s::d.constData()); } virtual Data* data() { return dynamic_cast(%s::d.data()); } """ % (superclass, superclass)) else: outfile.write(""" %s(Data *data) : d(data) { } %s(QExplicitlySharedDataPointer<%s::Data> &data) : d(data) { } QExplicitlySharedDataPointer d; """ % (shared_class_name, shared_class_name, shared_class_name)) else: outfile.write(' QSharedDataPointer d;\n'); outfile.write(line) if shared_class_options['explicit']: outfile.write(""" template<> %s %s::Data *QSharedDataPointer<%s::Data>::clone(); """ % (export_macro, shared_class_name, shared_class_name)) open_sdc() outfile_sdc.write("""template<> %s %s::Data *QSharedDataPointer<%s::Data>::clone() { return d->clone(); } """ % (export_macro, shared_class_name, shared_class_name)) else: #outfile.write('____ELSE____\n'); if False and other_comment(line): prev_pos = infile.tell() prev_line_number = line_number ln = line[:-1].strip(' ') result = '' print("'" + ln + "'") if ln.startswith('/**') or ln.startswith('/*!'): while True: result += line if not line or ln.endswith('*/'): result = result[:-1] break prev_line = line line = infile.readline() line_number += 1 ln = line[:-1].strip(' ') print(result) if result: member['docs'] = result infile.seek(prev_pos) prev_line = line line = infile.readline() lst = line.split() line_number = prev_line_number if not lst: if len(prev_line.split()) > 0: #outfile.write('['+prev_line + ']prev_line[' + line +']') outfile.write(line) elif not after_member or len(lst) > 0: if shared_class_inserted: insert_generated_code(6) outfile.write(line) elif generated_code_inserted and len(lst) == 0: #outfile.write('ELSE>>>' + line) outfile.write(line) # else: # outfile.write(line) process() # --- close --- infile.close() outfile.close()