com.abubusoft.kripton.android.sqlite.SQLiteTestUtils Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2015, 2017 Francesco Benincasa ([email protected]).
*
* 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.abubusoft.kripton.android.sqlite;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import com.abubusoft.kripton.android.Logger;
import com.abubusoft.kripton.android.sqlite.SQLiteUpdateTaskHelper.OnResultListener;
import com.abubusoft.kripton.android.sqlite.SQLiteUpdateTaskHelper.QueryType;
import com.abubusoft.kripton.android.sqlite.commons.IOUtils;
import com.abubusoft.kripton.android.sqlite.internals.MigrationSQLChecker;
import com.abubusoft.kripton.common.StringUtils;
import com.abubusoft.kripton.exception.KriptonRuntimeException;
import com.abubusoft.kripton.processor.sqlite.grammars.jsql.JqlBaseListener;
import com.abubusoft.kripton.processor.sqlite.grammars.jsql.JqlLexer;
import com.abubusoft.kripton.processor.sqlite.grammars.jsql.JqlParser;
import com.abubusoft.kripton.processor.sqlite.grammars.jsql.JqlParser.Sql_stmtContext;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
/**
*
* Utility class containing useful methods to:
*
*
*
* - clear test database
* - massive table renaming
* - massive table drops
* - retrieve table and index lists
* - execute SQL statements
* - schema verification
*
*
* @author Francesco Benincasa ([email protected])
*/
public abstract class SQLiteTestUtils {
/**
* Reset instance.
*
* @param classDataSource
* the class data source
*/
public static void resetDataSourceInstance(Class extends AbstractDataSource> classDataSource) {
Field threadLocalField;
try {
threadLocalField = classDataSource.getDeclaredField("instance");
threadLocalField.setAccessible(true);
threadLocalField.set(null, null);
threadLocalField.setAccessible(false);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
e.printStackTrace();
throw (new KriptonRuntimeException(e));
}
}
/**
* Query.
*
* @param db
* the db
* @param conditions
* the conditions
* @param type
* the type
* @param listener
* the listener
*/
private static void query(SQLiteDatabase db, String conditions, QueryType type, OnResultListener listener) {
SQLiteUpdateTaskHelper.query(db, conditions, type, listener);
}
/**
* Drop all entity of particular type (table or index). If prefix is
* specified, the drop operation is applied only to entity with prefix.
*
* @param db
* the db
* @param type
* the type
* @param prefix
* the prefix
*/
private static void drop(SQLiteDatabase db, final QueryType type, String prefix) {
String dropSQL = StringUtils.hasText(prefix) ? "name like '" + prefix + "' || '%'" : null;
query(db, dropSQL, type, new OnResultListener() {
@Override
public void onRow(SQLiteDatabase db, String name, String sql) {
String drop = "DROP " + type.toString().toUpperCase() + " " + name;
Logger.info(drop);
db.execSQL(drop);
}
});
}
/**
* Retrieve all table as a Map of (name, sql).
*
* @param db
* the db
* @return the all tables
*/
public static Map getAllTables(SQLiteDatabase db) {
return SQLiteUpdateTaskHelper.getAllTables(db);
}
/**
* Add to all schema's table a specifix prefix.
*
* @param db
* the db
* @param prefix
* the prefix
*/
public static void renameAllTablesWithPrefix(SQLiteDatabase db, final String prefix) {
Logger.info("MASSIVE TABLE RENAME OPERATION: ADD PREFIX " + prefix);
query(db, null, QueryType.TABLE, new OnResultListener() {
@Override
public void onRow(SQLiteDatabase db, String name, String sql) {
sql = String.format("ALTER TABLE %s RENAME TO %s%s;", name, prefix, name);
Logger.info(sql);
db.execSQL(sql);
}
});
}
/**
* Drop all table with specific prefix.
*
* @param db
* the db
* @param prefix
* the prefix
*/
public static void dropTablesWithPrefix(SQLiteDatabase db, String prefix) {
Logger.info("MASSIVE TABLE DROP OPERATION%s", StringUtils.ifNotEmptyAppend(prefix, " WITH PREFIX "));
drop(db, QueryType.TABLE, prefix);
}
/**
* Drop tables and indices.
*
* @param db
* the db
*/
public static void dropTablesAndIndices(SQLiteDatabase db) {
drop(db, QueryType.INDEX, null);
drop(db, QueryType.TABLE, null);
}
/**
* Retrieve all indexes as a Map of (name, sql).
*
* @param db
* the db
* @return indexes
*/
public static Map getAllIndexes(SQLiteDatabase db) {
return SQLiteUpdateTaskHelper.getAllIndexes(db);
}
/**
* Execute SQL contained in raw resource.
*
* @param database
* the database
* @param context
* the context
* @param rawResourceId
* the raw resource id
*/
public static void executeSQL(final SQLiteDatabase database, Context context, int rawResourceId) {
String[] c = IOUtils.readTextFile(context, rawResourceId).split(";");
List commands = Arrays.asList(c);
executeSQL(database, commands);
}
/**
* Read SQL from file.
*
* @param fileName
* the file name
* @return the list
*/
public static List readSQLFromFile(String fileName) {
try {
return readSQLFromFile(new FileInputStream(fileName));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* Execute SQL from file.
*
* @param database the database
* @param fileName the sql definition file
*/
public static void executeSQLFromFile(SQLiteDatabase database, String fileName) {
List executionList = readSQLFromFile(fileName);
for (String item : executionList) {
Logger.info(item);
database.execSQL(item);
}
}
/**
* Read SQL from file.
*
* @param fileInputStream
* the file input stream
* @return the list
*/
public static List readSQLFromFile(InputStream fileInputStream) {
String content = IOUtils.readText(fileInputStream);
// remove comments
content = content.replaceAll("\\/\\*.*\\*\\/", "");
content = content.replaceAll("--.*\n", "");
content = content.replaceAll("\n", "");
final String buffer = content;
final List executionList = new ArrayList<>();
MigrationSQLChecker.getInstance().analyze(content, new JqlBaseListener() {
public void enterSql_stmt(Sql_stmtContext ctx) {
int start = ctx.getStart().getStartIndex();
int stop = ctx.getStop().getStopIndex() + 1;
if (start == stop)
return;
String statement = buffer.substring(start, stop).trim();
executionList.add(statement);
};
});
return executionList;
/*
* String[] c = content.split(";"); List commands = new
* ArrayList<>(); for (String i : c) { if (StringUtils.hasText(i)) {
* commands.add(i.trim()); } }
*
* return commands;
*/
}
/**
* Execute SQL.
*
* @param database
* the database
* @param fileInputStream
* the file input stream
*/
public static void executeSQL(final SQLiteDatabase database, InputStream fileInputStream) {
List commands = readSQLFromFile(fileInputStream);
executeSQL(database, commands);
}
/**
* Execute SQL.
*
* @param database
* the database
* @param commands
* the commands
*/
public static void executeSQL(final SQLiteDatabase database, List commands) {
for (String command : commands) {
executeSQL(database, command);
}
// commands.forEach(command -> {
// executeSQL(database, command);
// });
}
/**
* Execute SQL.
*
* @param database
* the database
* @param command
* the command
*/
public static void executeSQL(final SQLiteDatabase database, String command) {
// remove comments
command = command.replaceAll("\\/\\*.*\\*\\/", "");
command = command.replaceAll("--.*$", "");
if (command.trim().length() > 0) {
Logger.info(command);
database.execSQL(command);
}
}
/**
* Verify schema.
*
* @param database
* the database
* @param inputStream
* the input stream
*/
public static void verifySchema(SQLiteDatabase database, InputStream inputStream) {
List ddl = extractCommands(database, inputStream);
verifySchemaInternal(database, ddl);
}
/**
* Verify schema.
*
* @param
* the generic type
* @param dataSource
* the data source
* @param inputStream
* the input stream
*/
public static void verifySchema(H dataSource, InputStream inputStream) {
verifySchema(dataSource.openWritableDatabase(), inputStream);
}
/**
* Verify schema.
*
* @param database
* the database
* @param context
* the context
* @param rawId
* the raw id
*/
public static void verifySchema(SQLiteDatabase database, Context context, int rawId) {
verifySchema(database, context.getResources().openRawResource(rawId));
}
/**
* Extract commands.
*
* @param database
* the database
* @param inputStream
* the input stream
* @return the list
*/
static List extractCommands(SQLiteDatabase database, InputStream inputStream) {
final List result = new ArrayList<>();
final String input = IOUtils.readText(inputStream);
JqlLexer lexer = new JqlLexer(CharStreams.fromString(input));
CommonTokenStream tokens = new CommonTokenStream(lexer);
JqlParser parser = new JqlParser(tokens);
ParserRuleContext parseContext = parser.parse();
ParseTreeWalker walk = new ParseTreeWalker();
walk.walk(new JqlBaseListener() {
@Override
public void enterSql_stmt(Sql_stmtContext ctx) {
int start = ctx.getStart().getStartIndex();
int stop = ctx.getStop().getStopIndex() + 1;
if (start == stop)
return;
result.add(input.substring(start, stop));
}
}, parseContext);
return result;
}
/**
* Verify schema.
*
* @param
* the generic type
* @param dataSource
* the data source
* @param context
* the context
* @param rawId
* the raw id
*/
public static void verifySchema(H dataSource, Context context, int rawId) {
verifySchema(dataSource.openWritableDatabase(), context, rawId);
}
/**
* Verify schema internal.
*
* @param database
* the database
* @param expectedSQL
* the expected SQL
*/
static void verifySchemaInternal(SQLiteDatabase database, List expectedSQL) {
Set actualSql = new HashSet();
actualSql.addAll(SQLiteTestUtils.getAllTables(database).values());
actualSql.addAll(SQLiteTestUtils.getAllIndexes(database).values());
Logger.info("Database schema version %s", database.getVersion());
if (actualSql.size() != expectedSQL.size()) {
Logger.error(
"Database schema comparison result: ERROR - Number of tables and indexes between aspected and actual schemas are different");
for (String item1 : actualSql) {
Logger.info("actual: " + item1);
}
for (String item1 : expectedSQL) {
Logger.info("expected: " + item1);
}
throw new KriptonRuntimeException(
"Number of tables and indexes between aspected and actual schemas are different");
}
for (String item : expectedSQL) {
if (!actualSql.contains(item)) {
Logger.error("Database schema comparison result: ERROR - Actual and expected schemas are NOT the same");
for (String item1 : actualSql) {
Logger.info("actual: " + item1);
}
for (String item1 : expectedSQL) {
Logger.info("expected: " + item1);
}
throw new KriptonRuntimeException("not found element: " + item);
}
}
Logger.info("Database schema comparison result: OK - Actual and expected schemas are the same!");
database.close();
}
/**
* Force a schema update for a datasource. Note that no DDL was execute
* untill the database was opened.
*
* @param
* the element type
* @param dataSource
* the data source
* @param version
* to upgrade.
*/
public static void forceDataSourceSchemaUpdate(H dataSource, int version) {
dataSource.forceClose();
dataSource.version = version;
dataSource.database = null;
dataSource.sqliteHelper = null;
dataSource.openWritableDatabase();
}
/**
* Clear datasource.
*
* @param
* the element type
* @param dataSource
* the data source
*/
public static void clearDataSource(H dataSource) {
if (dataSource.options.inMemory) {
dataSource.close();
return;
}
dataSource.openWritableDatabase();
File file = new File(dataSource.database.getPath(), dataSource.name);
if (dataSource.isOpen()) {
dataSource.forceClose();
dataSource.close();
}
Logger.info("Clear database file %s", file.getAbsolutePath());
if (!file.delete()) {
Logger.warn("Can not delete database " + file.getAbsolutePath());
}
}
}