File indexing completed on 2025-01-05 04:46:58

0001 /***************************************************************************
0002  *   SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>            *
0003  *   SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>          *
0004  *                                                                         *
0005  *   SPDX-License-Identifier: LGPL-2.0-or-later                            *
0006  ***************************************************************************/
0007 
0008 #include "storage/dbinitializer_p.h"
0009 #include "shared/akranges.h"
0010 
0011 using namespace Akonadi;
0012 using namespace Akonadi::Server;
0013 
0014 // BEGIN MySQL
0015 
0016 DbInitializerMySql::DbInitializerMySql(const QSqlDatabase &database)
0017     : DbInitializer(database)
0018 {
0019 }
0020 
0021 QString DbInitializerMySql::sqlType(const ColumnDescription &col, int size) const
0022 {
0023     if (col.type == QLatin1StringView("QString")) {
0024         return QLatin1StringView("VARBINARY(") + QString::number(size <= 0 ? 255 : size) + QLatin1StringView(")");
0025     } else {
0026         return DbInitializer::sqlType(col, size);
0027     }
0028 }
0029 
0030 QString DbInitializerMySql::buildCreateTableStatement(const TableDescription &tableDescription) const
0031 {
0032     QStringList columns;
0033     QStringList references;
0034 
0035     for (const ColumnDescription &columnDescription : tableDescription.columns) {
0036         columns.append(buildColumnStatement(columnDescription, tableDescription));
0037 
0038         if (!columnDescription.refTable.isEmpty() && !columnDescription.refColumn.isEmpty()) {
0039             references << QStringLiteral("FOREIGN KEY (%1) REFERENCES %2Table(%3) ")
0040                               .arg(columnDescription.name, columnDescription.refTable, columnDescription.refColumn)
0041                     + buildReferentialAction(columnDescription.onUpdate, columnDescription.onDelete);
0042         }
0043     }
0044 
0045     if (tableDescription.primaryKeyColumnCount() > 1) {
0046         columns.push_back(buildPrimaryKeyStatement(tableDescription));
0047     }
0048     columns << references;
0049 
0050     QString tableProperties = QStringLiteral(" COLLATE=utf8_general_ci DEFAULT CHARSET=utf8");
0051     if (tableDescription.columns | AkRanges::Actions::any([](const auto &col) {
0052             return col.type == QLatin1StringView("QString") && col.size > 255;
0053         })) {
0054         tableProperties += QStringLiteral(" ROW_FORMAT=DYNAMIC");
0055     }
0056 
0057     return QStringLiteral("CREATE TABLE %1 (%2) %3").arg(tableDescription.name, columns.join(QStringLiteral(", ")), tableProperties);
0058 }
0059 
0060 QString DbInitializerMySql::buildColumnStatement(const ColumnDescription &columnDescription, const TableDescription &tableDescription) const
0061 {
0062     QString column = columnDescription.name;
0063 
0064     column += QLatin1Char(' ') + sqlType(columnDescription, columnDescription.size);
0065 
0066     if (!columnDescription.allowNull) {
0067         column += QLatin1StringView(" NOT NULL");
0068     }
0069 
0070     if (columnDescription.isAutoIncrement) {
0071         column += QLatin1StringView(" AUTO_INCREMENT");
0072     }
0073 
0074     if (columnDescription.isPrimaryKey && tableDescription.primaryKeyColumnCount() == 1) {
0075         column += QLatin1StringView(" PRIMARY KEY");
0076     }
0077 
0078     if (columnDescription.isUnique) {
0079         column += QLatin1StringView(" UNIQUE");
0080     }
0081 
0082     if (!columnDescription.defaultValue.isEmpty()) {
0083         const QString defaultValue = sqlValue(columnDescription, columnDescription.defaultValue);
0084 
0085         if (!defaultValue.isEmpty()) {
0086             column += QStringLiteral(" DEFAULT %1").arg(defaultValue);
0087         }
0088     }
0089 
0090     return column;
0091 }
0092 
0093 QString DbInitializerMySql::buildInsertValuesStatement(const TableDescription &tableDescription, const DataDescription &dataDescription) const
0094 {
0095     QMap<QString, QString> data = dataDescription.data;
0096     QStringList keys;
0097     QStringList values;
0098     for (auto it = data.begin(), end = data.end(); it != end; ++it) {
0099         it.value().replace(QLatin1StringView("\\"), QLatin1StringView("\\\\"));
0100         keys.push_back(it.key());
0101         values.push_back(it.value());
0102     }
0103 
0104     return QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)").arg(tableDescription.name, keys.join(QLatin1Char(',')), values.join(QLatin1Char(',')));
0105 }
0106 
0107 QStringList DbInitializerMySql::buildAddForeignKeyConstraintStatements(const TableDescription &table, const ColumnDescription &column) const
0108 {
0109     return {QStringLiteral("ALTER TABLE %1 ADD FOREIGN KEY (%2) REFERENCES %4Table(%5) %6")
0110                 .arg(table.name, column.name, column.refTable, column.refColumn, buildReferentialAction(column.onUpdate, column.onDelete))};
0111 }
0112 
0113 QStringList DbInitializerMySql::buildRemoveForeignKeyConstraintStatements(const DbIntrospector::ForeignKey &fk, const TableDescription &table) const
0114 {
0115     return {QStringLiteral("ALTER TABLE %1 DROP FOREIGN KEY %2").arg(table.name, fk.name)};
0116 }
0117 
0118 // END MySQL
0119 
0120 // BEGIN Sqlite
0121 
0122 DbInitializerSqlite::DbInitializerSqlite(const QSqlDatabase &database)
0123     : DbInitializer(database)
0124 {
0125 }
0126 
0127 QString DbInitializerSqlite::buildCreateTableStatement(const TableDescription &tableDescription) const
0128 {
0129     QStringList columns;
0130 
0131     columns.reserve(tableDescription.columns.count() + 1);
0132     for (const ColumnDescription &columnDescription : std::as_const(tableDescription.columns)) {
0133         columns.append(buildColumnStatement(columnDescription, tableDescription));
0134     }
0135 
0136     if (tableDescription.primaryKeyColumnCount() > 1) {
0137         columns.push_back(buildPrimaryKeyStatement(tableDescription));
0138     }
0139     QStringList references;
0140     for (const ColumnDescription &columnDescription : std::as_const(tableDescription.columns)) {
0141         if (!columnDescription.refTable.isEmpty() && !columnDescription.refColumn.isEmpty()) {
0142             const auto constraintName =
0143                 QStringLiteral("%1%2_%3%4_fk").arg(tableDescription.name, columnDescription.name, columnDescription.refTable, columnDescription.refColumn);
0144             references << QStringLiteral("CONSTRAINT %1 FOREIGN KEY (%2) REFERENCES %3Table(%4) %5 DEFERRABLE INITIALLY DEFERRED")
0145                               .arg(constraintName,
0146                                    columnDescription.name,
0147                                    columnDescription.refTable,
0148                                    columnDescription.refColumn,
0149                                    buildReferentialAction(columnDescription.onUpdate, columnDescription.onDelete));
0150         }
0151     }
0152     columns << references;
0153 
0154     return QStringLiteral("CREATE TABLE %1 (%2)").arg(tableDescription.name, columns.join(QStringLiteral(", ")));
0155 }
0156 
0157 QString DbInitializerSqlite::buildColumnStatement(const ColumnDescription &columnDescription, const TableDescription &tableDescription) const
0158 {
0159     QString column = columnDescription.name + QLatin1Char(' ');
0160 
0161     if (columnDescription.isAutoIncrement) {
0162         column += QLatin1StringView("INTEGER");
0163     } else {
0164         column += sqlType(columnDescription, columnDescription.size);
0165     }
0166 
0167     if (columnDescription.isPrimaryKey && tableDescription.primaryKeyColumnCount() == 1) {
0168         column += QLatin1StringView(" PRIMARY KEY");
0169     } else if (columnDescription.isUnique) {
0170         column += QLatin1StringView(" UNIQUE");
0171     }
0172 
0173     if (columnDescription.isAutoIncrement) {
0174         column += QLatin1StringView(" AUTOINCREMENT");
0175     }
0176 
0177     if (!columnDescription.allowNull) {
0178         column += QLatin1StringView(" NOT NULL");
0179     }
0180 
0181     if (!columnDescription.defaultValue.isEmpty()) {
0182         const QString defaultValue = sqlValue(columnDescription, columnDescription.defaultValue);
0183 
0184         if (!defaultValue.isEmpty()) {
0185             column += QStringLiteral(" DEFAULT %1").arg(defaultValue);
0186         }
0187     }
0188 
0189     return column;
0190 }
0191 
0192 QString DbInitializerSqlite::buildInsertValuesStatement(const TableDescription &tableDescription, const DataDescription &dataDescription) const
0193 {
0194     QMap<QString, QString> data = dataDescription.data;
0195     QStringList keys;
0196     QStringList values;
0197     for (auto it = data.begin(), end = data.end(); it != end; ++it) {
0198         it.value().replace(QLatin1StringView("true"), QLatin1StringView("1"));
0199         it.value().replace(QLatin1StringView("false"), QLatin1StringView("0"));
0200         keys.push_back(it.key());
0201         values.push_back(it.value());
0202     }
0203 
0204     return QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)").arg(tableDescription.name, keys.join(QLatin1Char(',')), values.join(QLatin1Char(',')));
0205 }
0206 
0207 QString DbInitializerSqlite::sqlValue(const ColumnDescription &col, const QString &value) const
0208 {
0209     if (col.type == QLatin1StringView("bool")) {
0210         if (value == QLatin1StringView("false")) {
0211             return QStringLiteral("0");
0212         } else if (value == QLatin1StringView("true")) {
0213             return QStringLiteral("1");
0214         }
0215         return value;
0216     }
0217 
0218     return Akonadi::Server::DbInitializer::sqlValue(col, value);
0219 }
0220 
0221 QStringList DbInitializerSqlite::buildAddForeignKeyConstraintStatements(const TableDescription &table, const ColumnDescription & /*column*/) const
0222 {
0223     return buildUpdateForeignKeyConstraintsStatements(table);
0224 }
0225 
0226 QStringList DbInitializerSqlite::buildRemoveForeignKeyConstraintStatements(const DbIntrospector::ForeignKey & /*fk*/, const TableDescription &table) const
0227 {
0228     return buildUpdateForeignKeyConstraintsStatements(table);
0229 }
0230 
0231 QStringList DbInitializerSqlite::buildUpdateForeignKeyConstraintsStatements(const TableDescription &table) const
0232 {
0233     // Unfortunately, SQLite does not support adding or removing foreign keys through ALTER TABLE,
0234     // this is the only way how to do it.
0235     return {QStringLiteral("PRAGMA defer_foreign_keys=ON"),
0236             QStringLiteral("BEGIN TRANSACTION"),
0237             QStringLiteral("ALTER TABLE %1 RENAME TO %1_old").arg(table.name),
0238             buildCreateTableStatement(table),
0239             QStringLiteral("INSERT INTO %1 SELECT * FROM %1_old").arg(table.name),
0240             QStringLiteral("DROP TABLE %1_old").arg(table.name),
0241             QStringLiteral("COMMIT"),
0242             QStringLiteral("PRAGMA defer_foreign_keys=OFF")};
0243 }
0244 
0245 // END Sqlite
0246 
0247 // BEGIN PostgreSQL
0248 
0249 DbInitializerPostgreSql::DbInitializerPostgreSql(const QSqlDatabase &database)
0250     : DbInitializer(database)
0251 {
0252 }
0253 
0254 QString DbInitializerPostgreSql::sqlType(const ColumnDescription &col, int size) const
0255 {
0256     if (col.type == QLatin1StringView("qint64")) {
0257         return QStringLiteral("int8");
0258     } else if (col.type == QLatin1StringView("QByteArray")) {
0259         return QStringLiteral("BYTEA");
0260     } else if (col.isEnum) {
0261         return QStringLiteral("SMALLINT");
0262     }
0263 
0264     return DbInitializer::sqlType(col, size);
0265 }
0266 
0267 QString DbInitializerPostgreSql::buildCreateTableStatement(const TableDescription &tableDescription) const
0268 {
0269     QStringList columns;
0270     columns.reserve(tableDescription.columns.size() + 1);
0271 
0272     for (const ColumnDescription &columnDescription : tableDescription.columns) {
0273         columns.append(buildColumnStatement(columnDescription, tableDescription));
0274     }
0275 
0276     if (tableDescription.primaryKeyColumnCount() > 1) {
0277         columns.push_back(buildPrimaryKeyStatement(tableDescription));
0278     }
0279 
0280     return QStringLiteral("CREATE TABLE %1 (%2)").arg(tableDescription.name, columns.join(QStringLiteral(", ")));
0281 }
0282 
0283 QString DbInitializerPostgreSql::buildColumnStatement(const ColumnDescription &columnDescription, const TableDescription &tableDescription) const
0284 {
0285     QString column = columnDescription.name + QLatin1Char(' ');
0286 
0287     if (columnDescription.isAutoIncrement) {
0288         column += QLatin1StringView("SERIAL");
0289     } else {
0290         column += sqlType(columnDescription, columnDescription.size);
0291     }
0292 
0293     if (columnDescription.isPrimaryKey && tableDescription.primaryKeyColumnCount() == 1) {
0294         column += QLatin1StringView(" PRIMARY KEY");
0295     } else if (columnDescription.isUnique) {
0296         column += QLatin1StringView(" UNIQUE");
0297     }
0298 
0299     if (!columnDescription.allowNull && !(columnDescription.isPrimaryKey && tableDescription.primaryKeyColumnCount() == 1)) {
0300         column += QLatin1StringView(" NOT NULL");
0301     }
0302 
0303     if (!columnDescription.defaultValue.isEmpty()) {
0304         const QString defaultValue = sqlValue(columnDescription, columnDescription.defaultValue);
0305 
0306         if (!defaultValue.isEmpty()) {
0307             column += QStringLiteral(" DEFAULT %1").arg(defaultValue);
0308         }
0309     }
0310 
0311     return column;
0312 }
0313 
0314 QString DbInitializerPostgreSql::buildInsertValuesStatement(const TableDescription &tableDescription, const DataDescription &dataDescription) const
0315 {
0316     QStringList keys;
0317     QStringList values;
0318     for (auto it = dataDescription.data.cbegin(), end = dataDescription.data.cend(); it != end; ++it) {
0319         keys.push_back(it.key());
0320         values.push_back(it.value());
0321     }
0322 
0323     return QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)").arg(tableDescription.name, keys.join(QLatin1Char(',')), values.join(QLatin1Char(',')));
0324 }
0325 
0326 QStringList DbInitializerPostgreSql::buildAddForeignKeyConstraintStatements(const TableDescription &table, const ColumnDescription &column) const
0327 {
0328     // constraints must have name in PostgreSQL
0329     const QString constraintName = table.name + column.name + QLatin1StringView("_") + column.refTable + column.refColumn + QLatin1StringView("_fk");
0330     return {QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 FOREIGN KEY (%3) REFERENCES %4Table(%5) %6 DEFERRABLE INITIALLY DEFERRED")
0331                 .arg(table.name, constraintName, column.name, column.refTable, column.refColumn, buildReferentialAction(column.onUpdate, column.onDelete))};
0332 }
0333 
0334 QStringList DbInitializerPostgreSql::buildRemoveForeignKeyConstraintStatements(const DbIntrospector::ForeignKey &fk, const TableDescription &table) const
0335 {
0336     return {QStringLiteral("ALTER TABLE %1 DROP CONSTRAINT %2").arg(table.name, fk.name)};
0337 }
0338 
0339 // END PostgreSQL