예제 #1
0
void Database::UpdateDatabaseSchema(int version, QSqlDatabase& db) {
    QString filename;
    if (version == 0)
        filename = ":/schema/schema.sql";
    else
        filename = QString(":/schema/schema-%1.sql").arg(version);

    if (version == 31) {
        // This version used to do a bad job of converting filenames in the songs
        // table to file:// URLs.  Now we do it properly here instead.
        ScopedTransaction t(&db);

        UrlEncodeFilenameColumn("songs", db);
        UrlEncodeFilenameColumn("playlist_items", db);

        for (const QString& table : db.tables()) {
            if (table.startsWith("device_") && table.endsWith("_songs")) {
                UrlEncodeFilenameColumn(table, db);
            }
        }
        qLog(Debug) << "Applying database schema update" << version << "from"
                    << filename;
        ExecSchemaCommandsFromFile(db, filename, version - 1, true);
        t.Commit();
    } else {
        qLog(Debug) << "Applying database schema update" << version << "from"
                    << filename;
        ExecSchemaCommandsFromFile(db, filename, version - 1);
    }
}
예제 #2
0
QSqlDatabase Database::Connect() {
    QMutexLocker l(&connect_mutex_);

    // Create the directory if it doesn't exist
    if (!QFile::exists(directory_)) {
        QDir dir;
        if (!dir.mkpath(directory_)) {
        }
    }

    const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(
                                      reinterpret_cast<quint64>(QThread::currentThread()));

    // Try to find an existing connection for this thread
    QSqlDatabase db = QSqlDatabase::database(connection_id);
    if (db.isOpen()) {
        return db;
    }

    db = QSqlDatabase::addDatabase("QSQLITE", connection_id);

    if (!injected_database_name_.isNull())
        db.setDatabaseName(injected_database_name_);
    else
        db.setDatabaseName(directory_ + "/" + kDatabaseFilename);

    if (!db.open()) {
        app_->AddError("Database: " + db.lastError().text());
        return db;
    }

    // Find Sqlite3 functions in the Qt plugin.
    StaticInit();

    {
        QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db);
        set_fts_tokenizer.bindValue(":name", "unicode");
        set_fts_tokenizer.bindValue(
            ":pointer", QByteArray(reinterpret_cast<const char*>(&sFTSTokenizer),
                                   sizeof(&sFTSTokenizer)));
        if (!set_fts_tokenizer.exec()) {
            qLog(Warning) << "Couldn't register FTS3 tokenizer";
        }
        // Implicit invocation of ~QSqlQuery() when leaving the scope
        // to release any remaining database locks!
    }

    if (db.tables().count() == 0) {
        // Set up initial schema
        qLog(Info) << "Creating initial database schema";
        UpdateDatabaseSchema(0, db);
    }

    // Attach external databases
    for (const QString& key : attached_databases_.keys()) {
        QString filename = attached_databases_[key].filename_;

        if (!injected_database_name_.isNull()) filename = injected_database_name_;

        // Attach the db
        QSqlQuery q("ATTACH DATABASE :filename AS :alias", db);
        q.bindValue(":filename", filename);
        q.bindValue(":alias", key);
        if (!q.exec()) {
            qFatal("Couldn't attach external database '%s'",
                   key.toAscii().constData());
        }
    }

    if (startup_schema_version_ == -1) {
        UpdateMainSchema(&db);
    }

    // We might have to initialise the schema in some attached databases now, if
    // they were deleted and don't match up with the main schema version.
    for (const QString& key : attached_databases_.keys()) {
        if (attached_databases_[key].is_temporary_ &&
                attached_databases_[key].schema_.isEmpty())
            continue;
        // Find out if there are any tables in this database
        QSqlQuery q(QString(
                        "SELECT ROWID FROM %1.sqlite_master"
                        " WHERE type='table'").arg(key),
                    db);
        if (!q.exec() || !q.next()) {
            q.finish();
            ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
        }
    }

    return db;
}
예제 #3
0
QSqlDatabase Database::Connect() {
  QMutexLocker l(&connect_mutex_);

  // Create the directory if it doesn't exist
  if (!QFile::exists(directory_)) {
    QDir dir;
    if (!dir.mkpath(directory_)) {
    }
  }

  const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(
      reinterpret_cast<quint64>(QThread::currentThread()));

  // Try to find an existing connection for this thread
  QSqlDatabase db = QSqlDatabase::database(connection_id);
  if (db.isOpen()) {
    return db;
  }

  db = QSqlDatabase::addDatabase("QSQLITE", connection_id);

  if (!injected_database_name_.isNull())
    db.setDatabaseName(injected_database_name_);
  else
    db.setDatabaseName(directory_ + "/" + kDatabaseFilename);

  if (!db.open()) {
    app_->AddError("Database: " + db.lastError().text());
    return db;
  }

  // Find Sqlite3 functions in the Qt plugin.
  StaticInit();

  {

#ifdef SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
    // In case sqlite>=3.12 is compiled without -DSQLITE_ENABLE_FTS3_TOKENIZER (generally a good idea 
    // due to security reasons) the fts3 support should be enabled explicitly.
    // see https://github.com/clementine-player/Clementine/issues/5297
    //
    // See https://www.sqlite.org/fts3.html#custom_application_defined_tokenizers
    QVariant v = db.driver()->handle();
    if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) {
      sqlite3* handle = *static_cast<sqlite3**>(v.data());
      if (!handle || sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, nullptr) != SQLITE_OK) {
        qLog(Fatal) << "Failed to enable FTS3 tokenizer";
      }
    }
#endif

    QSqlQuery set_fts_tokenizer("SELECT fts3_tokenizer(:name, :pointer)", db);
    set_fts_tokenizer.bindValue(":name", "unicode");
    set_fts_tokenizer.bindValue(
        ":pointer", QByteArray(reinterpret_cast<const char*>(&sFTSTokenizer),
                               sizeof(&sFTSTokenizer)));
    if (!set_fts_tokenizer.exec()) {
      qLog(Warning) << "Couldn't register FTS3 tokenizer";
    }
    // Implicit invocation of ~QSqlQuery() when leaving the scope
    // to release any remaining database locks!
  }

  if (db.tables().count() == 0) {
    // Set up initial schema
    qLog(Info) << "Creating initial database schema";
    UpdateDatabaseSchema(0, db);
  }

  // Attach external databases
  for (const QString& key : attached_databases_.keys()) {
    QString filename = attached_databases_[key].filename_;

    if (!injected_database_name_.isNull()) filename = injected_database_name_;

    // Attach the db
    QSqlQuery q("ATTACH DATABASE :filename AS :alias", db);
    q.bindValue(":filename", filename);
    q.bindValue(":alias", key);
    if (!q.exec()) {
      qFatal("Couldn't attach external database '%s'",
             key.toAscii().constData());
    }
  }

  if (startup_schema_version_ == -1) {
    UpdateMainSchema(&db);
  }

  // We might have to initialise the schema in some attached databases now, if
  // they were deleted and don't match up with the main schema version.
  for (const QString& key : attached_databases_.keys()) {
    if (attached_databases_[key].is_temporary_ &&
        attached_databases_[key].schema_.isEmpty())
      continue;
    // Find out if there are any tables in this database
    QSqlQuery q(QString(
                    "SELECT ROWID FROM %1.sqlite_master"
                    " WHERE type='table'").arg(key),
                db);
    if (!q.exec() || !q.next()) {
      q.finish();
      ExecSchemaCommandsFromFile(db, attached_databases_[key].schema_, 0);
    }
  }

  return db;
}