Use interface SupportSQLiteDatabase in Android Databases Plugin (#1196)

Summary:
This change will allow to use various SQLiteDatabase implementations: standard Android implementation, [requery/sqlite-android](https://github.com/requery/sqlite-android) and so on. See issue https://github.com/facebook/flipper/issues/1183

## Changelog
Android Databases Plugin: `SqliteDatabaseConnectionProvider` returns `SupportSQLiteDatabase` instead of `SQLiteDatabase`.

Pull Request resolved: https://github.com/facebook/flipper/pull/1196

Test Plan: Check that Databases Plugin shows correct data on the sample Android application.

Reviewed By: mweststrate

Differential Revision: D23294272

Pulled By: passy

fbshipit-source-id: c07ebeb869ab01d41281f75541cbb3411f0ebae0
This commit is contained in:
a.artikov
2020-09-09 04:22:46 -07:00
committed by Facebook GitHub Bot
parent e13d63ea6f
commit 7a7a88bfde
7 changed files with 154 additions and 54 deletions

View File

@@ -65,6 +65,7 @@ android {
implementation deps.soloader implementation deps.soloader
implementation deps.jsr305 implementation deps.jsr305
implementation deps.supportAppCompat implementation deps.supportAppCompat
implementation deps.supportSqlite
testImplementation deps.mockito testImplementation deps.mockito
testImplementation deps.robolectric testImplementation deps.robolectric

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.databases.impl;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.io.File;
public class DefaultSqliteDatabaseConnectionProvider implements SqliteDatabaseConnectionProvider {
@Override
public SupportSQLiteDatabase openDatabase(File databaseFile) throws SQLiteException {
int flags = SQLiteDatabase.OPEN_READWRITE;
SQLiteDatabase database =
SQLiteDatabase.openDatabase(databaseFile.getAbsolutePath(), null, flags);
return FrameworkSQLiteDatabaseWrapping.wrap(database);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.databases.impl;
import android.content.Context;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class DefaultSqliteDatabaseProvider implements SqliteDatabaseProvider {
private Context context;
public DefaultSqliteDatabaseProvider(Context context) {
this.context = context;
}
@Override
public List<File> getDatabaseFiles() {
List<File> databaseFiles = new ArrayList<>();
for (String databaseName : context.databaseList()) {
databaseFiles.add(context.getDatabasePath(databaseName));
}
return databaseFiles;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.databases.impl;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.lang.reflect.Constructor;
/** Gives access to package-private class FrameworkSQLiteDatabase */
public class FrameworkSQLiteDatabaseWrapping {
public static SupportSQLiteDatabase wrap(SQLiteDatabase database) throws SQLiteException {
try {
Class<?> clazz = Class.forName("androidx.sqlite.db.framework.FrameworkSQLiteDatabase");
Constructor<?> constructor = clazz.getDeclaredConstructor(SQLiteDatabase.class);
constructor.setAccessible(true);
return (SupportSQLiteDatabase) constructor.newInstance(database);
} catch (Exception e) {
String errorMessage =
"Failed to instantiate androidx.sqlite.db.framework.FrameworkSQLiteDatabase";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
throw new SQLiteException(errorMessage, e);
} else {
throw new SQLiteException(errorMessage);
}
}
}
}

View File

@@ -7,11 +7,11 @@
package com.facebook.flipper.plugins.databases.impl; package com.facebook.flipper.plugins.databases.impl;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.io.File; import java.io.File;
public interface SqliteDatabaseConnectionProvider { public interface SqliteDatabaseConnectionProvider {
SQLiteDatabase openDatabase(File databaseFile) throws SQLiteException; SupportSQLiteDatabase openDatabase(File databaseFile) throws SQLiteException;
} }

View File

@@ -9,16 +9,17 @@ package com.facebook.flipper.plugins.databases.impl;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteStatement;
import com.facebook.flipper.plugins.databases.DatabaseDescriptor; import com.facebook.flipper.plugins.databases.DatabaseDescriptor;
import com.facebook.flipper.plugins.databases.DatabaseDriver; import com.facebook.flipper.plugins.databases.DatabaseDriver;
import com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver.SqliteDatabaseDescriptor; import com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver.SqliteDatabaseDescriptor;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -33,37 +34,18 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
private static final String SCHEMA_TABLE = "sqlite_master"; private static final String SCHEMA_TABLE = "sqlite_master";
private static final String[] UNINTERESTING_FILENAME_SUFFIXES = private static final String[] UNINTERESTING_FILENAME_SUFFIXES =
new String[] {"-journal", "-shm", "-uid", "-wal"}; new String[] {"-journal", "-shm", "-uid", "-wal"};
private static final String TAG = "SqliteDatabaseDriver";
private final SqliteDatabaseProvider sqliteDatabaseProvider; private final SqliteDatabaseProvider sqliteDatabaseProvider;
private final SqliteDatabaseConnectionProvider sqliteDatabaseConnectionProvider; private final SqliteDatabaseConnectionProvider sqliteDatabaseConnectionProvider;
public SqliteDatabaseDriver(final Context context) { public SqliteDatabaseDriver(final Context context) {
this( this(context, new DefaultSqliteDatabaseProvider(context));
context,
new SqliteDatabaseProvider() {
@Override
public List<File> getDatabaseFiles() {
List<File> databaseFiles = new ArrayList<>();
for (String databaseName : context.databaseList()) {
databaseFiles.add(context.getDatabasePath(databaseName));
}
return databaseFiles;
}
});
} }
public SqliteDatabaseDriver( public SqliteDatabaseDriver(
final Context context, final SqliteDatabaseProvider sqliteDatabaseProvider) { final Context context, final SqliteDatabaseProvider sqliteDatabaseProvider) {
this( this(context, sqliteDatabaseProvider, new DefaultSqliteDatabaseConnectionProvider());
context,
sqliteDatabaseProvider,
new SqliteDatabaseConnectionProvider() {
@Override
public SQLiteDatabase openDatabase(File databaseFile) throws SQLiteException {
int flags = SQLiteDatabase.OPEN_READWRITE;
return SQLiteDatabase.openDatabase(databaseFile.getAbsolutePath(), null, flags);
}
});
} }
public SqliteDatabaseDriver( public SqliteDatabaseDriver(
@@ -90,11 +72,11 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
@Override @Override
public List<String> getTableNames(SqliteDatabaseDescriptor databaseDescriptor) { public List<String> getTableNames(SqliteDatabaseDescriptor databaseDescriptor) {
try { try {
SQLiteDatabase database = SupportSQLiteDatabase database =
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file); sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
try { try {
Cursor cursor = Cursor cursor =
database.rawQuery( database.query(
"SELECT name FROM " + SCHEMA_TABLE + " WHERE type IN (?, ?)", "SELECT name FROM " + SCHEMA_TABLE + " WHERE type IN (?, ?)",
new String[] {"table", "view"}); new String[] {"table", "view"});
try { try {
@@ -107,7 +89,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
cursor.close(); cursor.close();
} }
} finally { } finally {
database.close(); close(database);
} }
} catch (SQLiteException ex) { } catch (SQLiteException ex) {
return Collections.emptyList(); return Collections.emptyList();
@@ -117,7 +99,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
@Override @Override
public DatabaseExecuteSqlResponse executeSQL( public DatabaseExecuteSqlResponse executeSQL(
SqliteDatabaseDescriptor databaseDescriptor, String query) { SqliteDatabaseDescriptor databaseDescriptor, String query) {
SQLiteDatabase database = SupportSQLiteDatabase database =
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file); sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
try { try {
String firstWordUpperCase = getFirstWord(query).toUpperCase(); String firstWordUpperCase = getFirstWord(query).toUpperCase();
@@ -135,7 +117,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
return executeRawQuery(database, query); return executeRawQuery(database, query);
} }
} finally { } finally {
database.close(); close(database);
} }
} }
@@ -147,13 +129,19 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
boolean reverse, boolean reverse,
int start, int start,
int count) { int count) {
SQLiteDatabase database = SupportSQLiteDatabase database =
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file); sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
try { try {
String orderBy = order != null ? order + (reverse ? " DESC" : " ASC") : null; String orderBy = order != null ? order + (reverse ? " DESC" : " ASC") : null;
String limitBy = start + ", " + count; String query;
Cursor cursor = database.query(table, null, null, null, null, null, orderBy, limitBy); if (orderBy != null) {
long total = DatabaseUtils.queryNumEntries(database, table); query = "SELECT * from " + table + " ORDER BY " + orderBy + " LIMIT ?, ?";
} else {
query = "SELECT * from " + table + " LIMIT ?, ?";
}
Cursor cursor = database.query(query, new Object[] {start, count});
long total = queryNumEntries(database, table);
try { try {
String[] columnNames = cursor.getColumnNames(); String[] columnNames = cursor.getColumnNames();
List<List<Object>> rows = cursorToList(cursor); List<List<Object>> rows = cursorToList(cursor);
@@ -163,19 +151,19 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
cursor.close(); cursor.close();
} }
} finally { } finally {
database.close(); close(database);
} }
} }
@Override @Override
public DatabaseGetTableStructureResponse getTableStructure( public DatabaseGetTableStructureResponse getTableStructure(
SqliteDatabaseDescriptor databaseDescriptor, String table) { SqliteDatabaseDescriptor databaseDescriptor, String table) {
SQLiteDatabase database = SupportSQLiteDatabase database =
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file); sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
try { try {
Cursor structureCursor = database.rawQuery("PRAGMA table_info(" + table + ")", null); Cursor structureCursor = database.query("PRAGMA table_info(" + table + ")", null);
Cursor foreignKeysCursor = database.rawQuery("PRAGMA foreign_key_list(" + table + ")", null); Cursor foreignKeysCursor = database.query("PRAGMA foreign_key_list(" + table + ")", null);
Cursor indexesCursor = database.rawQuery("PRAGMA index_list(" + table + ")", null); Cursor indexesCursor = database.query("PRAGMA index_list(" + table + ")", null);
try { try {
// Structure & foreign keys // Structure & foreign keys
@@ -221,7 +209,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
while (indexesCursor.moveToNext()) { while (indexesCursor.moveToNext()) {
List<String> indexedColumnNames = new ArrayList<>(); List<String> indexedColumnNames = new ArrayList<>();
String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name")); String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name"));
Cursor indexInfoCursor = database.rawQuery("PRAGMA index_info(" + indexName + ")", null); Cursor indexInfoCursor = database.query("PRAGMA index_info(" + indexName + ")", null);
try { try {
while (indexInfoCursor.moveToNext()) { while (indexInfoCursor.moveToNext()) {
indexedColumnNames.add( indexedColumnNames.add(
@@ -246,20 +234,19 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
indexesCursor.close(); indexesCursor.close();
} }
} finally { } finally {
database.close(); close(database);
} }
} }
@Override @Override
public DatabaseGetTableInfoResponse getTableInfo( public DatabaseGetTableInfoResponse getTableInfo(
SqliteDatabaseDescriptor databaseDescriptor, String table) { SqliteDatabaseDescriptor databaseDescriptor, String table) {
SQLiteDatabase database = SupportSQLiteDatabase database =
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file); sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
try { try {
Cursor definitionCursor = Cursor definitionCursor =
database.rawQuery( database.query("SELECT sql FROM " + SCHEMA_TABLE + " WHERE name=?", new String[] {table});
"SELECT sql FROM " + SCHEMA_TABLE + " WHERE name=?", new String[] {table});
try { try {
// Definition // Definition
@@ -271,7 +258,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
definitionCursor.close(); definitionCursor.close();
} }
} finally { } finally {
database.close(); close(database);
} }
} }
@@ -304,20 +291,22 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
} }
private static DatabaseExecuteSqlResponse executeUpdateDelete( private static DatabaseExecuteSqlResponse executeUpdateDelete(
SQLiteDatabase database, String query) { SupportSQLiteDatabase database, String query) {
SQLiteStatement statement = database.compileStatement(query); SupportSQLiteStatement statement = database.compileStatement(query);
int count = statement.executeUpdateDelete(); int count = statement.executeUpdateDelete();
return DatabaseExecuteSqlResponse.successfulUpdateDelete(count); return DatabaseExecuteSqlResponse.successfulUpdateDelete(count);
} }
private static DatabaseExecuteSqlResponse executeInsert(SQLiteDatabase database, String query) { private static DatabaseExecuteSqlResponse executeInsert(
SQLiteStatement statement = database.compileStatement(query); SupportSQLiteDatabase database, String query) {
SupportSQLiteStatement statement = database.compileStatement(query);
long insertedId = statement.executeInsert(); long insertedId = statement.executeInsert();
return DatabaseExecuteSqlResponse.successfulInsert(insertedId); return DatabaseExecuteSqlResponse.successfulInsert(insertedId);
} }
private static DatabaseExecuteSqlResponse executeSelect(SQLiteDatabase database, String query) { private static DatabaseExecuteSqlResponse executeSelect(
Cursor cursor = database.rawQuery(query, null); SupportSQLiteDatabase database, String query) {
Cursor cursor = database.query(query, null);
try { try {
String[] columnNames = cursor.getColumnNames(); String[] columnNames = cursor.getColumnNames();
List<List<Object>> rows = cursorToList(cursor); List<List<Object>> rows = cursorToList(cursor);
@@ -327,7 +316,8 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
} }
} }
private static DatabaseExecuteSqlResponse executeRawQuery(SQLiteDatabase database, String query) { private static DatabaseExecuteSqlResponse executeRawQuery(
SupportSQLiteDatabase database, String query) {
database.execSQL(query); database.execSQL(query);
return DatabaseExecuteSqlResponse.successfulRawQuery(); return DatabaseExecuteSqlResponse.successfulRawQuery();
} }
@@ -361,6 +351,24 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
} }
} }
private long queryNumEntries(SupportSQLiteDatabase database, String table) {
Cursor cursor = database.query("SELECT COUNT(*) FROM " + table);
try {
cursor.moveToFirst();
return cursor.getLong(0);
} finally {
cursor.close();
}
}
private void close(SupportSQLiteDatabase database) {
try {
database.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close SQLite database", e);
}
}
static class SqliteDatabaseDescriptor implements DatabaseDescriptor { static class SqliteDatabaseDescriptor implements DatabaseDescriptor {
public final File file; public final File file;

View File

@@ -56,6 +56,7 @@ ext.deps = [
supportCoreUi : "androidx.legacy:legacy-support-core-ui:$ANDROIDX_VERSION", supportCoreUi : "androidx.legacy:legacy-support-core-ui:$ANDROIDX_VERSION",
supportRecyclerView: "androidx.recyclerview:recyclerview:$ANDROIDX_VERSION", supportRecyclerView: "androidx.recyclerview:recyclerview:$ANDROIDX_VERSION",
supportConstraintLayout: "androidx.constraintlayout:constraintlayout:1.1.2", supportConstraintLayout: "androidx.constraintlayout:constraintlayout:1.1.2",
supportSqlite : "androidx.sqlite:sqlite-framework:2.1.0",
supportEspresso : 'androidx.test.espresso:espresso-core:3.1.0', supportEspresso : 'androidx.test.espresso:espresso-core:3.1.0',
supportDesign : "com.google.android.material:material:1.0.0-rc01", supportDesign : "com.google.android.material:material:1.0.0-rc01",
supportEspressoIntents : 'androidx.test.espresso:espresso-intents:3.1.0', supportEspressoIntents : 'androidx.test.espresso:espresso-intents:3.1.0',