blob: 5bee1a4abd6ba6ee7d8de6de04818f5bd2940960 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.adservices.data.measurement.migration;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.android.adservices.LogUtil;
import com.android.adservices.data.measurement.MeasurementTables;
import org.json.JSONArray;
import java.util.UUID;
/** Migrates Measurement DB from user version 3 to 6. */
public class MeasurementDbMigratorV6 extends AbstractMeasurementDbMigrator {
public static final String CREATE_TABLE_XNA_IGNORED_SOURCES_V6 =
"CREATE TABLE "
+ MeasurementTables.XnaIgnoredSourcesContract.TABLE
+ " ("
+ MeasurementTables.XnaIgnoredSourcesContract.SOURCE_ID
+ " TEXT NOT NULL, "
+ MeasurementTables.XnaIgnoredSourcesContract.ENROLLMENT_ID
+ " TEXT NOT NULL, "
+ "FOREIGN KEY ("
+ MeasurementTables.XnaIgnoredSourcesContract.SOURCE_ID
+ ") REFERENCES "
+ MeasurementTables.SourceContract.TABLE
+ "("
+ MeasurementTables.SourceContract.ID
+ ") ON DELETE CASCADE"
+ ")";
private static final String[] ALTER_STATEMENTS_SOURCE_REPORT = {
String.format(
"ALTER TABLE %1$s RENAME COLUMN %2$s TO %3$s",
MeasurementTables.SourceContract.TABLE,
MeasurementTablesDeprecated.Source.DEDUP_KEYS,
MeasurementTables.SourceContract.EVENT_REPORT_DEDUP_KEYS),
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.AGGREGATE_REPORT_DEDUP_KEYS),
String.format(
"ALTER TABLE %1$s ADD %2$s INTEGER",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.EVENT_REPORT_WINDOW),
String.format(
"ALTER TABLE %1$s ADD %2$s INTEGER",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.AGGREGATABLE_REPORT_WINDOW)
};
private static final String[] ALTER_STATEMENTS_XNA_ASYNC_REGISTRATION = {
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.AsyncRegistrationContract.TABLE,
MeasurementTables.AsyncRegistrationContract.REGISTRATION_ID),
};
private static final String[] ALTER_STATEMENTS_XNA_SOURCE = {
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.REGISTRATION_ID),
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.SHARED_AGGREGATION_KEYS),
String.format(
"ALTER TABLE %1$s ADD %2$s INTEGER",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.INSTALL_TIME)
};
private static final String[] ALTER_STATEMENTS_XNA_TRIGGER = {
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.ATTRIBUTION_CONFIG),
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.X_NETWORK_KEY_MAPPING)
};
private static final String[] UPDATE_XNA_IGNORED_SOURCES_TABLE_STATEMENT = {
String.format(
"DROP TABLE IF EXISTS %1$s", MeasurementTables.XnaIgnoredSourcesContract.TABLE),
CREATE_TABLE_XNA_IGNORED_SOURCES_V6,
};
public static final String UPDATE_SOURCE_REPORT_WINDOWS_STATEMENT =
String.format(
"UPDATE %1$s SET %2$s = %3$s, %4$s = %3$s",
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.EVENT_REPORT_WINDOW,
MeasurementTables.SourceContract.EXPIRY_TIME,
MeasurementTables.SourceContract.AGGREGATABLE_REPORT_WINDOW);
public static final String UPDATE_TRIGGER_STATEMENT = String.format(
"UPDATE %1$s SET %2$s = '%3$s' WHERE %2$s IS NULL",
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.EVENT_TRIGGERS,
new JSONArray().toString());
private static final String[] ALTER_TRIGGER_STATEMENTS_V6 = {
String.format(
"ALTER TABLE %1$s ADD %2$s TEXT",
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.AGGREGATABLE_DEDUPLICATION_KEYS)
};
public MeasurementDbMigratorV6() {
super(6);
}
@Override
protected void performMigration(SQLiteDatabase db) {
// See if registration_id column is present in the msmt_async_registration table.
// We use this as a proxy to determine if the db is already at v6.
if (MigrationHelpers.isColumnPresent(
db,
MeasurementTables.AsyncRegistrationContract.TABLE,
MeasurementTables.AsyncRegistrationContract.REGISTRATION_ID)) {
LogUtil.d("Registration id exists. Skipping Migration");
return;
}
// Drop and create a new XnaIgnoredSourcesTable if it exists.
for (String query : UPDATE_XNA_IGNORED_SOURCES_TABLE_STATEMENT) {
db.execSQL(query);
}
alterAsyncRegistrationTable(db);
alterSourceTable(db);
alterTriggerTable(db);
migrateSourceReportWindows(db);
migrateAsyncRegistrationRegistrationId(db);
migrateSourceRegistrationId(db);
migrateTriggerData(db);
}
private static void alterAsyncRegistrationTable(SQLiteDatabase db) {
if (!MigrationHelpers.isColumnPresent(
db,
MeasurementTables.AsyncRegistrationContract.TABLE,
MeasurementTables.AsyncRegistrationContract.REGISTRATION_ID)) {
for (String sql : ALTER_STATEMENTS_XNA_ASYNC_REGISTRATION) {
db.execSQL(sql);
}
}
}
private static void alterSourceTable(SQLiteDatabase db) {
if (!MigrationHelpers.isColumnPresent(
db,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.EVENT_REPORT_WINDOW)
&& !MigrationHelpers.isColumnPresent(
db,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.AGGREGATABLE_REPORT_WINDOW)) {
for (String sql : ALTER_STATEMENTS_SOURCE_REPORT) {
db.execSQL(sql);
}
}
if (!MigrationHelpers.isColumnPresent(
db,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.REGISTRATION_ID)
&& !MigrationHelpers.isColumnPresent(
db,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.SHARED_AGGREGATION_KEYS)
&& !MigrationHelpers.isColumnPresent(
db,
MeasurementTables.SourceContract.TABLE,
MeasurementTables.SourceContract.INSTALL_TIME)) {
for (String sql : ALTER_STATEMENTS_XNA_SOURCE) {
db.execSQL(sql);
}
}
}
private static void alterTriggerTable(SQLiteDatabase db) {
if (!MigrationHelpers.isColumnPresent(
db,
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.ATTRIBUTION_CONFIG)
&& !MigrationHelpers.isColumnPresent(
db,
MeasurementTables.TriggerContract.TABLE,
MeasurementTables.TriggerContract.X_NETWORK_KEY_MAPPING)) {
for (String sql : ALTER_STATEMENTS_XNA_TRIGGER) {
db.execSQL(sql);
}
for (String sql : ALTER_TRIGGER_STATEMENTS_V6) {
db.execSQL(sql);
}
}
}
private static void migrateSourceReportWindows(SQLiteDatabase db) {
db.execSQL(UPDATE_SOURCE_REPORT_WINDOWS_STATEMENT);
}
private static void migrateAsyncRegistrationRegistrationId(SQLiteDatabase db) {
try (Cursor cursor =
db.query(
MeasurementTables.AsyncRegistrationContract.TABLE,
new String[] {
MeasurementTables.AsyncRegistrationContract.ID,
MeasurementTables.AsyncRegistrationContract.REGISTRATION_ID
},
null,
null,
null,
null,
null,
null)) {
while (cursor.moveToNext()) {
updateAsyncRegistrationId(db, cursor);
}
}
}
private static void migrateSourceRegistrationId(SQLiteDatabase db) {
try (Cursor cursor =
db.query(
MeasurementTables.SourceContract.TABLE,
new String[] {
MeasurementTables.SourceContract.ID,
MeasurementTables.SourceContract.REGISTRATION_ID
},
null,
null,
null,
null,
null,
null)) {
while (cursor.moveToNext()) {
updateSourceRegistrationId(db, cursor);
}
}
}
private static void updateAsyncRegistrationId(SQLiteDatabase db, Cursor cursor) {
String id =
cursor.getString(
cursor.getColumnIndex(MeasurementTables.AsyncRegistrationContract.ID));
ContentValues values = new ContentValues();
values.put(
MeasurementTables.AsyncRegistrationContract.REGISTRATION_ID,
UUID.randomUUID().toString());
long rowCount =
db.update(
MeasurementTables.AsyncRegistrationContract.TABLE,
values,
MeasurementTables.AsyncRegistrationContract.ID + " = ?",
new String[] {id});
if (rowCount != 1) {
LogUtil.d("MeasurementDbMigratorV6: failed to update source record.");
}
}
private static void updateSourceRegistrationId(SQLiteDatabase db, Cursor cursor) {
String id = cursor.getString(cursor.getColumnIndex(MeasurementTables.SourceContract.ID));
ContentValues values = new ContentValues();
values.put(MeasurementTables.SourceContract.REGISTRATION_ID, UUID.randomUUID().toString());
long rowCount =
db.update(
MeasurementTables.SourceContract.TABLE,
values,
MeasurementTables.SourceContract.ID + " = ?",
new String[] {id});
if (rowCount != 1) {
LogUtil.d("MeasurementDbMigratorV6: failed to update source record.");
}
}
private static void migrateTriggerData(SQLiteDatabase db) {
db.execSQL(UPDATE_TRIGGER_STATEMENT);
}
}