File indexing completed on 2024-10-06 04:18:02
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 : ¶mValuesIt; 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 : qAsConst(*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 }