File indexing completed on 2022-11-29 19:02:26

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this program; see the file COPYING.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KDbNativeStatementBuilder.h"
0021 #include "KDbConnection.h"
0022 #include "kdb_debug.h"
0023 #include "KDbDriverBehavior.h"
0024 #include "KDbDriver_p.h"
0025 #include "KDbExpression.h"
0026 #include "KDbLookupFieldSchema.h"
0027 #include "KDbOrderByColumn.h"
0028 #include "KDbQueryAsterisk.h"
0029 #include "KDbQuerySchema.h"
0030 #include "KDbQuerySchemaParameter.h"
0031 #include "KDbRelationship.h"
0032 
0033 KDbSelectStatementOptions::~KDbSelectStatementOptions()
0034 {
0035 }
0036 
0037 //================================================
0038 
0039 class Q_DECL_HIDDEN KDbNativeStatementBuilder::Private
0040 {
0041 public:
0042     Private() {}
0043     //! @todo use equivalent of QPointer<KDbConnection>
0044     KDbConnection *connection;
0045     KDb::IdentifierEscapingType dialect;
0046 
0047 private:
0048     Q_DISABLE_COPY(Private)
0049 };
0050 
0051 //================================================
0052 
0053 KDbNativeStatementBuilder::KDbNativeStatementBuilder(KDbConnection *connection,
0054                                                      KDb::IdentifierEscapingType dialect)
0055     : d(new Private)
0056 {
0057     d->connection = connection;
0058     d->dialect = dialect;
0059 }
0060 
0061 KDbNativeStatementBuilder::~KDbNativeStatementBuilder()
0062 {
0063     delete d;
0064 }
0065 
0066 static bool selectStatementInternal(KDbEscapedString *target,
0067                                     KDbConnection *connection,
0068                                     KDb::IdentifierEscapingType dialect,
0069                                     KDbQuerySchema* querySchema,
0070                                     const KDbSelectStatementOptions& options,
0071                                     const QList<QVariant>& parameters)
0072 {
0073     Q_ASSERT(target);
0074     Q_ASSERT(querySchema);
0075 //"SELECT FROM ..." is theoretically allowed "
0076 //if (querySchema.fieldCount()<1)
0077 //  return QString();
0078 // Each SQL identifier needs to be escaped in the generated query.
0079 
0080     const KDbDriver *driver = dialect == KDb::DriverEscaping ? connection->driver() : nullptr;
0081 
0082     if (!querySchema->statement().isEmpty()) {
0083 //! @todo replace with KDbNativeQuerySchema? It shouldn't be here.
0084         *target = querySchema->statement();
0085         return true;
0086     }
0087 
0088 //! @todo looking at singleTable is visually nice but a field name can conflict
0089 //!   with function or variable name...
0090     int number = 0;
0091     QList<KDbTableSchema*>* tables = querySchema->tables();
0092     bool singleTable = tables->count() <= 1;
0093     if (singleTable) {
0094         //make sure we will have single table:
0095         foreach(KDbField *f, *querySchema->fields()) {
0096             if (querySchema->isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema(*f)) {
0097                 //uups, no, there's at least one left join
0098                 singleTable = false;
0099                 break;
0100             }
0101             number++;
0102         }
0103     }
0104 
0105     KDbEscapedString sql; //final sql string
0106     sql.reserve(4096);
0107     KDbEscapedString s_additional_joins; //additional joins needed for lookup fields
0108     KDbEscapedString s_additional_fields; //additional fields to append to the fields list
0109     int internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases
0110     int internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases
0111     number = 0;
0112     QList<KDbQuerySchema*> subqueries_for_lookup_data; // subqueries will be added to FROM section
0113     const QString kdb_subquery_prefix = QStringLiteral("__kdb_subquery_");
0114     KDbQuerySchemaParameterValueListIterator paramValuesIt(parameters);
0115     KDbQuerySchemaParameterValueListIterator *paramValuesItPtr
0116         = parameters.isEmpty() ? nullptr : &paramValuesIt;
0117     foreach(KDbField *f, *querySchema->fields()) {
0118         if (querySchema->isColumnVisible(number)) {
0119             if (!sql.isEmpty())
0120                 sql += ", ";
0121 
0122             if (f->isQueryAsterisk()) {
0123                 KDbQueryAsterisk *asterisk = static_cast<KDbQueryAsterisk*>(f);
0124                 if (!singleTable && asterisk->isSingleTableAsterisk()) { //single-table *
0125                     sql.append(KDb::escapeIdentifier(driver, asterisk->table()->name())).append(".*");
0126                 } else {
0127                     /* All-tables asterisk
0128                      NOTE: do not output in this form because there can be extra tables
0129                      automatically added for obtaining lookup data what changes number of fields.
0130                      Reliable solution to that: for tables T1..Tn output T1.*,..Tn.*
0131                      Example for Northwind:
0132                      - instead of: SELECT * FROM orders LEFT OUTER JOIN
0133                                    customers ON orders.customerid=customers.customerid
0134                      - use this: SELECT orders.*, customers.contactname FROM orders LEFT OUTER JOIN
0135                                  customers ON orders.customerid=customers.customerid
0136                     */
0137                     KDbEscapedString s_tables;
0138                     for (KDbTableSchema *table : *tables) {
0139                         if (!s_tables.isEmpty()) {
0140                             s_tables += ", ";
0141                         }
0142                         s_tables.append(KDb::escapeIdentifier(driver, table->name()) + QLatin1String(".*"));
0143                     }
0144                     sql += s_tables;
0145                 }
0146             } else {
0147                 if (f->isExpression()) {
0148                     sql += f->expression().toString(driver, paramValuesItPtr);
0149                 } else {
0150                     if (!f->table()) {//sanity check
0151                         return false;
0152                     }
0153 
0154                     QString tableName;
0155                     int tablePosition = querySchema->tableBoundToColumn(number);
0156                     if (tablePosition >= 0) {
0157                         tableName = KDb::iifNotEmpty(querySchema->tableAlias(tablePosition),
0158                                                            f->table()->name());
0159                     }
0160                     if (options.addVisibleLookupColumns()) { // try to find table/alias name harder
0161                         if (tableName.isEmpty()) {
0162                             tableName = querySchema->tableAlias(f->table()->name());
0163                         }
0164                         if (tableName.isEmpty()) {
0165                             tableName = f->table()->name();
0166                         }
0167                     }
0168                     if (!singleTable && !tableName.isEmpty()) {
0169                         sql.append(KDb::escapeIdentifier(driver, tableName)).append('.');
0170                     }
0171                     sql += KDb::escapeIdentifier(driver, f->name());
0172                 }
0173                 const QString aliasString(querySchema->columnAlias(number));
0174                 if (!aliasString.isEmpty()) {
0175                     sql.append(" AS ").append(KDb::escapeIdentifier(driver, aliasString));
0176                 }
0177 //! @todo add option that allows to omit "AS" keyword
0178             }
0179             KDbLookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns() && f->table())
0180                                                    ? f->table()->lookupFieldSchema(*f) : nullptr;
0181             if (lookupFieldSchema && lookupFieldSchema->boundColumn() >= 0) {
0182                 // Lookup field schema found
0183                 // Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
0184                 // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
0185                 // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
0186                 KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource();
0187                 if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) {
0188                     KDbTableSchema *lookupTable = connection->tableSchema(recordSource.name());
0189                     KDbFieldList* visibleColumns = nullptr;
0190                     KDbField *boundField = nullptr;
0191                     if (lookupTable
0192                             && lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
0193                             && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))
0194                             && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) {
0195                         //add LEFT OUTER JOIN
0196                         if (!s_additional_joins.isEmpty())
0197                             s_additional_joins += ' ';
0198                         const QString internalUniqueTableAlias(
0199                             QLatin1String("__kdb_") + lookupTable->name() + QLatin1Char('_')
0200                             + QString::number(internalUniqueTableAliasNumber++));
0201                         s_additional_joins += KDbEscapedString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6")
0202                             .arg(KDb::escapeIdentifier(driver, lookupTable->name()))
0203                             .arg(KDb::escapeIdentifier(driver, internalUniqueTableAlias))
0204                             .arg(KDb::escapeIdentifier(driver, querySchema->tableAliasOrName(f->table()->name())))
0205                             .arg(KDb::escapeIdentifier(driver, f->name()))
0206                             .arg(KDb::escapeIdentifier(driver, internalUniqueTableAlias))
0207                             .arg(KDb::escapeIdentifier(driver, boundField->name()));
0208 
0209                         //add visibleField to the list of SELECTed fields //if it is not yet present there
0210                         if (!s_additional_fields.isEmpty())
0211                             s_additional_fields += ", ";
0212 //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
0213 //! @todo Add possibility for joining the values at client side.
0214                         s_additional_fields += visibleColumns->sqlFieldsList(
0215                                                    connection, QLatin1String(" || ' ' || "), internalUniqueTableAlias,
0216                                                    dialect);
0217                     }
0218                     delete visibleColumns;
0219                 } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) {
0220                     KDbQuerySchema *lookupQuery = connection->querySchema(recordSource.name());
0221                     if (!lookupQuery) {
0222                         kdbWarning() << "!lookupQuery";
0223                         return false;
0224                     }
0225                     const KDbQueryColumnInfo::Vector fieldsExpanded(
0226                         lookupQuery->fieldsExpanded(connection));
0227                     if (lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) {
0228                         kdbWarning() << "lookupFieldSchema->boundColumn() >= fieldsExpanded.count()";
0229                         return false;
0230                     }
0231                     KDbQueryColumnInfo *boundColumnInfo = fieldsExpanded.at(lookupFieldSchema->boundColumn());
0232                     if (!boundColumnInfo) {
0233                         kdbWarning() << "!boundColumnInfo";
0234                         return false;
0235                     }
0236                     KDbField *boundField = boundColumnInfo->field();
0237                     if (!boundField) {
0238                         kdbWarning() << "!boundField";
0239                         return false;
0240                     }
0241                     //add LEFT OUTER JOIN
0242                     if (!s_additional_joins.isEmpty())
0243                         s_additional_joins += ' ';
0244                     KDbEscapedString internalUniqueQueryAlias(KDb::escapeIdentifier(
0245                         driver,
0246                         kdb_subquery_prefix + lookupQuery->name() + QLatin1Char('_')
0247                             + QString::number(internalUniqueQueryAliasNumber++)));
0248                     KDbNativeStatementBuilder builder(connection, dialect);
0249                     KDbEscapedString subSql;
0250                     if (!builder.generateSelectStatement(&subSql, lookupQuery, options,
0251                                                          parameters))
0252                     {
0253                         return false;
0254                     }
0255                     s_additional_joins += KDbEscapedString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6")
0256                         .arg(subSql)
0257                         .arg(internalUniqueQueryAlias)
0258                         .arg(KDb::escapeIdentifier(driver, f->table()->name()))
0259                         .arg(KDb::escapeIdentifier(driver, f->name()))
0260                         .arg(internalUniqueQueryAlias)
0261                         .arg(KDb::escapeIdentifier(driver, boundColumnInfo->aliasOrName()));
0262 
0263                     if (!s_additional_fields.isEmpty())
0264                         s_additional_fields += ", ";
0265                     const QList<int> visibleColumns(lookupFieldSchema->visibleColumns());
0266                     KDbEscapedString expression;
0267                     foreach(int visibleColumnIndex, visibleColumns) {
0268 //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?"
0269 //! @todo Add possibility for joining the values at client side.
0270                         if (fieldsExpanded.count() <= visibleColumnIndex) {
0271                             kdbWarning() << "fieldsExpanded.count() <= (*visibleColumnsIt) : "
0272                             << fieldsExpanded.count() << " <= " << visibleColumnIndex;
0273                             return false;
0274                         }
0275                         if (!expression.isEmpty())
0276                             expression += " || ' ' || ";
0277                         expression += (
0278                             internalUniqueQueryAlias + '.'
0279                             + KDb::escapeIdentifier(driver, fieldsExpanded.value(visibleColumnIndex)->aliasOrName())
0280                         );
0281                     }
0282                     s_additional_fields += expression;
0283                 }
0284                 else {
0285                     kdbWarning() << "unsupported record source type" << recordSource.typeName();
0286                     return false;
0287                 }
0288             }
0289         }
0290         number++;
0291     }
0292 
0293     //add lookup fields
0294     if (!s_additional_fields.isEmpty())
0295         sql += (", " + s_additional_fields);
0296 
0297     if (driver && options.alsoRetrieveRecordId()) { //append rowid column
0298         //! @todo Check if the rowid isn't already part of regular SELECT columns, if so, don't add
0299         KDbEscapedString s;
0300         if (!sql.isEmpty())
0301             s = ", ";
0302         if (querySchema->masterTable()) {
0303             s += KDb::escapeIdentifier(driver, querySchema->tableAliasOrName(querySchema->masterTable()->name()));
0304             s += '.';
0305         }
0306         s += KDbDriverPrivate::behavior(driver)->ROW_ID_FIELD_NAME;
0307         sql += s;
0308     }
0309 
0310     if (sql.isEmpty()) {
0311         sql.prepend("SELECT"); // "SELECT FROM ..." case
0312     } else {
0313         sql.prepend("SELECT ");
0314     }
0315     if (!tables->isEmpty() || !subqueries_for_lookup_data.isEmpty()) {
0316         sql += " FROM ";
0317         KDbEscapedString s_from;
0318         number = 0;
0319         foreach(KDbTableSchema *table, *tables) {
0320             if (!s_from.isEmpty())
0321                 s_from += ", ";
0322             s_from += KDb::escapeIdentifier(driver, table->name());
0323             const QString aliasString(querySchema->tableAlias(number));
0324             if (!aliasString.isEmpty())
0325                 s_from.append(" AS ").append(KDb::escapeIdentifier(driver, aliasString));
0326             number++;
0327         }
0328         // add subqueries for lookup data
0329         int subqueries_for_lookup_data_counter = 0;
0330         foreach(KDbQuerySchema* subQuery, subqueries_for_lookup_data) {
0331             if (!s_from.isEmpty())
0332                 s_from += ", ";
0333             KDbEscapedString subSql;
0334             if (!selectStatementInternal(&subSql, connection, dialect, subQuery, options, parameters)) {
0335                 return false;
0336             }
0337             s_from += '(' + subSql + ") AS "
0338                 + KDb::escapeIdentifier(
0339                       driver,
0340                       kdb_subquery_prefix + QString::number(subqueries_for_lookup_data_counter++));
0341         }
0342         sql += s_from;
0343     }
0344     KDbEscapedString s_where;
0345     s_where.reserve(4096);
0346 
0347     //JOINS
0348     if (!s_additional_joins.isEmpty()) {
0349         sql += ' ' + s_additional_joins + ' ';
0350     }
0351 
0352 //! @todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later
0353 
0354     //WHERE
0355     bool wasWhere = false; //for later use
0356     foreach(KDbRelationship *rel, *querySchema->relationships()) {
0357         if (s_where.isEmpty()) {
0358             wasWhere = true;
0359         } else
0360             s_where += " AND ";
0361         KDbEscapedString s_where_sub;
0362         foreach(const KDbField::Pair &pair, *rel->fieldPairs()) {
0363             if (!s_where_sub.isEmpty())
0364                 s_where_sub += " AND ";
0365             s_where_sub +=
0366                KDbEscapedString(KDb::escapeIdentifier(driver, pair.first->table()->name())) + '.' +
0367                KDb::escapeIdentifier(driver, pair.first->name()) + " = " +
0368                KDb::escapeIdentifier(driver, pair.second->table()->name()) + '.' +
0369                KDb::escapeIdentifier(driver, pair.second->name());
0370         }
0371         if (rel->fieldPairs()->count() > 1) {
0372             s_where_sub.prepend('(');
0373             s_where_sub += ')';
0374         }
0375         s_where += s_where_sub;
0376     }
0377     //EXPLICITLY SPECIFIED WHERE EXPRESSION
0378     if (!querySchema->whereExpression().isNull()) {
0379         if (wasWhere) {
0380             //! @todo () are not always needed
0381             s_where = '(' + s_where + ") AND ("
0382                 + querySchema->whereExpression().toString(driver, paramValuesItPtr) + ')';
0383         } else {
0384             s_where = querySchema->whereExpression().toString(driver, paramValuesItPtr);
0385         }
0386     }
0387     if (!s_where.isEmpty())
0388         sql += " WHERE " + s_where;
0389 //! @todo (js) add other sql parts
0390     //(use wasWhere here)
0391 
0392     // ORDER BY
0393     KDbEscapedString orderByString(querySchema->orderByColumnList()->toSqlString(
0394         !singleTable /*includeTableName*/, connection, querySchema, dialect));
0395     const QVector<int> pkeyFieldsOrder(querySchema->pkeyFieldsOrder(connection));
0396     if (dialect == KDb::DriverEscaping  && orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) {
0397         // Native only: add automatic ORDER BY if there is no explicitly defined one
0398         // (especially helps when there are complex JOINs)
0399         KDbOrderByColumnList automaticPKOrderBy;
0400         const KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded(connection));
0401         foreach(int pkeyFieldsIndex, pkeyFieldsOrder) {
0402             if (pkeyFieldsIndex < 0) // no field mentioned in this query
0403                 continue;
0404             if (pkeyFieldsIndex >= fieldsExpanded.count()) {
0405                 kdbWarning() << "ORDER BY: (*it) >= fieldsExpanded.count() - "
0406                         << pkeyFieldsIndex << " >= " << fieldsExpanded.count();
0407                 continue;
0408             }
0409             KDbQueryColumnInfo *ci = fieldsExpanded[ pkeyFieldsIndex ];
0410             automaticPKOrderBy.appendColumn(ci);
0411         }
0412         orderByString = automaticPKOrderBy.toSqlString(!singleTable /*includeTableName*/,
0413                                                        connection, querySchema, dialect);
0414     }
0415     if (!orderByString.isEmpty())
0416         sql += (" ORDER BY " + orderByString);
0417 
0418     //kdbDebug() << sql;
0419     *target = sql;
0420     return true;
0421 }
0422 
0423 bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target,
0424                                                         KDbQuerySchema* querySchema,
0425                                                         const KDbSelectStatementOptions& options,
0426                                                         const QList<QVariant>& parameters) const
0427 {
0428     return selectStatementInternal(target, d->connection, d->dialect, querySchema, options, parameters);
0429 }
0430 
0431 bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target,
0432                                                         KDbQuerySchema* querySchema,
0433                                                         const QList<QVariant>& parameters) const
0434 {
0435     return selectStatementInternal(target, d->connection, d->dialect, querySchema, KDbSelectStatementOptions(),
0436                                    parameters);
0437 }
0438 
0439 bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target,
0440                                                         KDbTableSchema* tableSchema,
0441                                                         const KDbSelectStatementOptions& options) const
0442 {
0443     return generateSelectStatement(target, tableSchema->query(), options);
0444 }
0445 
0446 bool KDbNativeStatementBuilder::generateCreateTableStatement(KDbEscapedString *target,
0447                                                              const KDbTableSchema& tableSchema) const
0448 {
0449     if (!target) {
0450         return false;
0451     }
0452     // Each SQL identifier needs to be escaped in the generated query.
0453     const KDbDriver *driver = d->dialect == KDb::DriverEscaping ? d->connection->driver() : nullptr;
0454     KDbEscapedString sql;
0455     sql.reserve(4096);
0456     sql = KDbEscapedString("CREATE TABLE ")
0457             + KDb::escapeIdentifier(driver, tableSchema.name()) + " (";
0458     bool first = true;
0459     for (const KDbField *field : *tableSchema.fields()) {
0460         if (first)
0461             first = false;
0462         else
0463             sql += ", ";
0464         KDbEscapedString v = KDbEscapedString(KDb::escapeIdentifier(driver, field->name())) + ' ';
0465         const bool autoinc = field->isAutoIncrement();
0466         const bool pk = field->isPrimaryKey() || (autoinc && driver && driver->behavior()->AUTO_INCREMENT_REQUIRES_PK);
0467 //! @todo warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true!
0468         const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive
0469         if (autoinc && d->connection->driver()->behavior()->SPECIAL_AUTO_INCREMENT_DEF) {
0470             if (pk)
0471                 v.append(d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE).append(' ')
0472                  .append(d->connection->driver()->behavior()->AUTO_INCREMENT_PK_FIELD_OPTION);
0473             else
0474                 v.append(d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE).append(' ')
0475                  .append(d->connection->driver()->behavior()->AUTO_INCREMENT_FIELD_OPTION);
0476         } else {
0477             if (autoinc && !d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE.isEmpty())
0478                 v += d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE;
0479             else
0480                 v += d->connection->driver()->sqlTypeName(type, *field);
0481 
0482             if (KDbField::isIntegerType(type) && field->isUnsigned()) {
0483                 v.append(' ').append(d->connection->driver()->behavior()->UNSIGNED_TYPE_KEYWORD);
0484             }
0485 
0486             if (KDbField::isFPNumericType(type) && field->precision() > 0) {
0487                 if (field->scale() > 0)
0488                     v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale());
0489                 else
0490                     v += QString::fromLatin1("(%1)").arg(field->precision());
0491             }
0492             else if (type == KDbField::Text) {
0493                 int realMaxLen;
0494                 if (d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH == 0) {
0495                     realMaxLen = field->maxLength(); // allow to skip (N)
0496                 }
0497                 else { // max length specified by driver
0498                     if (field->maxLength() == 0) { // as long as possible
0499                         realMaxLen = d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH;
0500                     }
0501                     else { // not longer than specified by driver
0502                         realMaxLen = qMin(d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH, field->maxLength());
0503                     }
0504                 }
0505                 if (realMaxLen > 0) {
0506                     v += QString::fromLatin1("(%1)").arg(realMaxLen);
0507                 }
0508             }
0509 
0510             if (autoinc) {
0511                 v.append(' ').append(pk ? d->connection->driver()->behavior()->AUTO_INCREMENT_PK_FIELD_OPTION
0512                                         : d->connection->driver()->behavior()->AUTO_INCREMENT_FIELD_OPTION);
0513             }
0514             else {
0515                 //! @todo here is automatically a single-field key created
0516                 if (pk)
0517                     v += " PRIMARY KEY";
0518             }
0519             if (!pk && field->isUniqueKey())
0520                 v += " UNIQUE";
0521 ///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull())
0522             if (!autoinc && !pk && field->isNotNull())
0523                 v += " NOT NULL"; //only add not null option if no autocommit is set
0524             if (d->connection->driver()->supportsDefaultValue(*field) && field->defaultValue().isValid()) {
0525                 KDbEscapedString valToSql(d->connection->driver()->valueToSql(field, field->defaultValue()));
0526                 if (!valToSql.isEmpty()) //for sanity
0527                     v += " DEFAULT " + valToSql;
0528             }
0529         }
0530         sql += v;
0531     }
0532     sql += ')';
0533     *target = sql;
0534     return true;
0535 }