Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable migrations support #739

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public class AllConstants {
public static final String COMBINE_CHECKBOX_OPTION_VALUES = "combine_checkbox_option_values";

public static final String GPS = "gps";
public static final String MIGRATION_FILENAME_PATTERN = "(\\d)\\.(up|down)\\.sql";


public static class Immunizations {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,9 @@ public List<Pair<String, String>> getGlobalSettingsQueryParams() {
public boolean validateUserAssignments() {
return true;
}
// TODO: Rename this method
public boolean isMigrationsConfigurationEnabled() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public class AllSharedPreferences {
public static final String FORMS_VERSION = "FORMS_VERSION";
private static final String ENCRYPTED_PASSPHRASE_KEY = "ENCRYPTED_PASSPHRASE_KEY";
private static final String DB_ENCRYPTION_VERSION = "DB_ENCRYPTION_VERSION";
private static final String DB_VERSION = "DB_VERSION";
private static final String DB_MIGRATION_REQUIRED = "DB_MIGRATION_REQUIRED";
private SharedPreferences preferences;

public AllSharedPreferences(SharedPreferences preferences) {
Expand Down Expand Up @@ -382,8 +384,24 @@ public int getDBEncryptionVersion() {
return preferences.getInt(DB_ENCRYPTION_VERSION, 0);
}

public void setDBEncryptionVersion(int encryptionVersion) {
preferences.edit().putInt(DB_ENCRYPTION_VERSION, encryptionVersion).commit();
public boolean setDBEncryptionVersion(int encryptionVersion) {
return preferences.edit().putInt(DB_ENCRYPTION_VERSION, encryptionVersion).commit();
}

public int getDbVersion() {
return preferences.getInt(DB_VERSION, 0);
}

public boolean setDbVersion(int dbVersion) {
return preferences.edit().putInt(DB_VERSION, dbVersion).commit();
}

public boolean isDbMigrationRequired() {
return preferences.getBoolean(DB_MIGRATION_REQUIRED, false);
}

public boolean setDbMigrationRequired(boolean isRequired) {
return preferences.edit().putBoolean(DB_MIGRATION_REQUIRED, isRequired).commit();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.smartregister.repository;

import org.smartregister.repository.contract.MigrationSource;

/**
* Created by Ephraim Kigamba - [email protected] on 29-01-2021.
*/
public class MigrationImpl implements MigrationSource.Migration {

private int dbVersion;
private String[] migrations;
private MigrationType migrationType;

@Override
public int getDbVersion() {
return dbVersion;
}

@Override
public String[] getUpMigrationQueries() {
return migrations;
}

@Override
public void setDbVersion(int dbVersion) {
this.dbVersion = dbVersion;
}

@Override
public void setMigrations(String[] migrations) {
this.migrations = migrations;
}

@Override
public MigrationType getMigrationType() {
return migrationType;
}

@Override
public void setMigrationType(MigrationType migrationType) {
this.migrationType = migrationType;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.smartregister.repository;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.VisibleForTesting;

Expand All @@ -12,8 +13,12 @@
import org.apache.commons.lang3.StringUtils;
import org.smartregister.AllConstants;
import org.smartregister.CoreLibrary;
import org.smartregister.SyncConfiguration;
import org.smartregister.commonregistry.CommonFtsObject;
import org.smartregister.exception.DatabaseMigrationException;
import org.smartregister.repository.contract.MigrationSource;
import org.smartregister.repository.dao.AppFolderMigrationSource;
import org.smartregister.repository.dao.AssetMigrationSource;
import org.smartregister.repository.helper.OpenSRPDatabaseErrorHandler;
import org.smartregister.util.DatabaseMigrationUtils;
import org.smartregister.util.Session;
Expand All @@ -23,6 +28,8 @@
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;

Expand Down Expand Up @@ -143,6 +150,40 @@ public void onCreate(SQLiteDatabase database) {

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration();
if (syncConfiguration != null && syncConfiguration.isMigrationsConfigurationEnabled()) {
AssetMigrationSource assetMigrationSource = new AssetMigrationSource(context);
AppFolderMigrationSource appFolderMigrationSource = new AppFolderMigrationSource(context);

HashMap<Integer, ArrayList<MigrationSource.Migration>> assetMigrations
= assetMigrationSource.getMigrations(oldVersion);
HashMap<Integer, ArrayList<MigrationSource.Migration>> appFolderMigrationSourceMigrations
= appFolderMigrationSource.getMigrations(oldVersion);

for (int version = oldVersion + 1; version <= newVersion; version++) {
ArrayList<MigrationSource.Migration> migrations = assetMigrations.get(version);

if (migrations == null) {
migrations = appFolderMigrationSourceMigrations.get(version);
}

if (migrations != null) {
for (MigrationSource.Migration migration: migrations) {
if (MigrationSource.Migration.MigrationType.UP.equals(migration.getMigrationType())) {
Timber.i("Running %s migration for v%d -> %d", migration.getMigrationType().name(), version, migrations.size());

for (String migrationQuery: migration.getUpMigrationQueries()) {
migrationQuery = migrationQuery.trim();
if (!TextUtils.isEmpty(migrationQuery)){
Timber.i("Running query [%s]", migrationQuery);
sqLiteDatabase.execSQL(migrationQuery);
}
}
}
}
}
}
}
}

public SQLiteDatabase getReadableDatabase() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.smartregister.repository.contract;

import java.util.ArrayList;
import java.util.HashMap;

/**
* Created by Ephraim Kigamba - [email protected] on 29-01-2021.
*/
public interface MigrationSource {

HashMap<Integer, ArrayList<Migration>> getMigrations();

HashMap<Integer, ArrayList<Migration>> getMigrations(int fromDbVersion);

interface Migration {

enum MigrationType {
UP,
DOWN
}

int getDbVersion();

String[] getUpMigrationQueries();

void setDbVersion(int dbVersion);

void setMigrations(String[] migrations);

MigrationType getMigrationType();

void setMigrationType(MigrationType migrationType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.smartregister.repository.dao;

import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;

import org.apache.commons.io.FileUtils;
import org.smartregister.AllConstants;
import org.smartregister.repository.MigrationImpl;
import org.smartregister.repository.contract.MigrationSource;
import org.smartregister.util.Utils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import timber.log.Timber;

/**
* Created by Ephraim Kigamba - [email protected] on 29-01-2021.
*/
public class AppFolderMigrationSource implements MigrationSource {

public Context context;

public AppFolderMigrationSource(Context context) {
this.context = context;
}

@Override
public HashMap<Integer, ArrayList<Migration>> getMigrations() {
return getMigrations(0);
}

@Override
public HashMap<Integer, ArrayList<Migration>> getMigrations(int fromDbVersion) {
HashMap<Integer, ArrayList<Migration>> migrationMap = new HashMap<>();
try {
File appFolderDirectory = new File(Environment.getDataDirectory(), "/data/" + Utils.getAppId(context) + "/files/migrations");
String[] migrationFileNames = appFolderDirectory.list();

String regex = AllConstants.MIGRATION_FILENAME_PATTERN;
Pattern filePattern = Pattern.compile(regex);

if (migrationFileNames != null) {
for (String migrationFile: migrationFileNames) {
// Check if migration file name matches the Regex
Matcher fileNameMatcher = filePattern.matcher(migrationFile);
if (fileNameMatcher.matches()) {
String versionString = fileNameMatcher.group(1);
String migrationType = fileNameMatcher.group(2);

int version = Integer.parseInt(versionString);

if (version >= fromDbVersion) {

File file = new File(appFolderDirectory, migrationFile);
String queries = FileUtils.readFileToString(file);

Migration versionMigration = new MigrationImpl();
versionMigration.setDbVersion(version);
versionMigration.setMigrations(queries.split("\\n"));

if (!TextUtils.isEmpty(migrationType)) {
versionMigration.setMigrationType(Migration.MigrationType.UP.name().equals(migrationType)
? Migration.MigrationType.UP : Migration.MigrationType.DOWN);
}

ArrayList<Migration> versionMigrations = migrationMap.get(version);
if (versionMigrations == null) {
versionMigrations = new ArrayList<>();
}

versionMigrations.add(versionMigration);
migrationMap.put(version, versionMigrations);
}
}
}
}

} catch (IOException e) {
Timber.e(e);
}

return migrationMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.smartregister.repository.dao;

import android.content.Context;
import android.text.TextUtils;

import org.smartregister.AllConstants;
import org.smartregister.repository.MigrationImpl;
import org.smartregister.repository.contract.MigrationSource;
import org.smartregister.util.AssetHandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import timber.log.Timber;

/**
* Created by Ephraim Kigamba - [email protected] on 29-01-2021.
*/
public class AssetMigrationSource implements MigrationSource {

public Context context;

public AssetMigrationSource(Context context) {
this.context = context;
}

@Override
public HashMap<Integer, ArrayList<Migration>> getMigrations() {
return getMigrations(0);
}

@Override
public HashMap<Integer, ArrayList<Migration>> getMigrations(int fromDbVersion) {
HashMap<Integer, ArrayList<Migration>> migrationMap = new HashMap<>();
try {
String[] migrationFileNames = context.getAssets().list("config/migrations");

String regex = AllConstants.MIGRATION_FILENAME_PATTERN;
Pattern filePattern = Pattern.compile(regex);

if (migrationFileNames != null) {
for (String migrationFile: migrationFileNames) {
// Check if migration file name matches the Regex
Matcher fileNameMatcher = filePattern.matcher(migrationFile);
if (fileNameMatcher.matches()) {
String versionString = fileNameMatcher.group(1);
String migrationType = fileNameMatcher.group(2).toUpperCase();

int version = Integer.parseInt(versionString);

if (version >= fromDbVersion) {

String queries = AssetHandler.readFileFromAssetsFolder("config/migrations/" + migrationFile, context);

Migration versionMigration = new MigrationImpl();
versionMigration.setDbVersion(version);
versionMigration.setMigrations(queries.split("\\n"));

if (!TextUtils.isEmpty(migrationType)) {
versionMigration.setMigrationType(Migration.MigrationType.UP.name().equals(migrationType)
? Migration.MigrationType.UP : Migration.MigrationType.DOWN);
}

ArrayList<Migration> versionMigrations = migrationMap.get(version);
if (versionMigrations == null) {
versionMigrations = new ArrayList<>();
}

versionMigrations.add(versionMigration);
migrationMap.put(version, versionMigrations);
}
}
}
}

} catch (IOException e) {
Timber.e(e);
}

return migrationMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,8 @@ public Context getContext() {
return context;
}

public int getDbVersion() {
return getContext().allSharedPreferences().getDbVersion();
}

}
1 change: 1 addition & 0 deletions opensrp-app/src/test/assets/config/migrations/1.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE IF NOT EXISTS TABLE key_value(key VARCHAR, val VARCHAR);
1 change: 1 addition & 0 deletions opensrp-app/src/test/assets/config/migrations/2.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE IF NOT EXISTS TABLE clients(id INTEGER, full_name VARCHAR, age INTEGER, dob INTEGER);
1 change: 1 addition & 0 deletions opensrp-app/src/test/assets/config/migrations/3.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE IF NOT EXISTS TABLE events(event_id INTEGER, json VARCHAR, server_version INTEGER);
Loading