Em resumo, uma maneira seria copiar o novo arquivo de ativo para o local apropriado, enquanto mantém uma cópia do arquivo de banco de dados original, você pode aplicar as atualizações para preservar efetivamente os dados do usuário no banco de dados recém-copiado. MAS somente se o aplicativo estiver sendo atualizado para essa alteração específica e se o banco de dados existir.
Se o banco de dados não existir, a cópia padrão do arquivo de ativos deve ser realizada.
Aqui está um exemplo de como isso pode ser feito.
Este exemplo depende bastante de rotinas que permitem o gerenciamento e alguma interrogação do arquivo na pasta assets e ou do próprio arquivo de banco de dados.
Uma classe chamada DBAsetHandler.java atende ao acima (assim como a capacidade de extrair o user_version AKA a versão do banco de dados ao usar o SQLiteOpenHelper).
-
Observe que a classe também foi testada e, portanto, atende ao Android Pie e, portanto, ao registro de gravação antecipada (WAL, o padrão no Pie), bem como ao modo de diário, o padrão anterior.
-
Observe também que, se estiver usando o WAL, você deve garantir que o banco de dados seja totalmente verificado, consulte - Write-Ahead Logging
isto é :-
public class DBAssetHandler {
static final String[] tempfiles = new String[]{"-journal","-wal","-shm"}; // temporary files to rename
public static final String backup = "-backup"; //value to be appended to file name when renaming (psuedo delete)
public static final int OUCH = -666666666;
/**
* Check if the database already exists. NOTE will create the databases folder is it doesn't exist
* @return true if it exists, false if it doesn't
*/
public static boolean checkDataBase(Context context, String dbname) {
File db = new File(context.getDatabasePath(dbname).getPath()); //Get the file name of the database
Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove if publish App
if (db.exists()) return true; // If it exists then return doing nothing
// Get the parent (directory in which the database file would be)
File dbdir = db.getParentFile();
// If the directory does not exits then make the directory (and higher level directories)
if (!dbdir.exists()) {
db.getParentFile().mkdirs();
dbdir.mkdirs();
}
return false;
}
/**
* Copy database file from the assets folder
* (long version caters for asset file name being different to the database name)
* @param context Context is needed to get the applicable package
* @param dbname name of the database file
* @param assetfilename name of the asset file
* @param deleteExistingDB true if an existing database file should be deleted
* note will delete journal and wal files
* note doen't actually delete the files rater it renames
* the files by appended -backup to the file name
* SEE/USE clearForceBackups below to delete the renamed files
*/
public static void copyDataBase(Context context, String dbname, String assetfilename, boolean deleteExistingDB) {
final String TAG = "COPYDATABASE";
int stage = 0, buffer_size = 4096, blocks_copied = 0, bytes_copied = 0;
File f = new File(context.getDatabasePath(dbname).toString());
InputStream is;
OutputStream os;
/**
* If forcing then effectively delete (rename) current database files
*/
if (deleteExistingDB) {
//String[] tempfiles = new String[]{"-journal","-wal","-shm"};
//String backup = "-backup";
f.renameTo(context.getDatabasePath(dbname + "-backup"));
for (String s: tempfiles) {
File tmpf = new File(context.getDatabasePath(dbname + s).toString());
if (tmpf.exists()) {
tmpf.renameTo(context.getDatabasePath(dbname + s + backup));
}
}
}
//Open your local db as the input stream
Log.d(TAG,"Initiated Copy of the database file " + assetfilename + " from the assets folder."); //TODO remove if publishing
try {
is = context.getAssets().open(assetfilename); // Open the Asset file
stage++;
Log.d(TAG, "Asset file " + assetfilename + " found so attmepting to copy to " + f.getPath()); //TODO remove if publishing
os = new FileOutputStream(f);
stage++;
//transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[buffer_size];
int length;
while ((length = is.read(buffer)) > 0) {
blocks_copied++;
Log.d(TAG, "Attempting copy of block " + String.valueOf(blocks_copied) + " which has " + String.valueOf(length) + " bytes."); //TODO remove if publishing
os.write(buffer, 0, length);
bytes_copied += length;
}
stage++;
Log.d(TAG,
"Finished copying Database " + dbname +
" from the assets folder, to " + f.getPath() +
String.valueOf(bytes_copied) + "were copied, in " +
String.valueOf(blocks_copied) + " blocks of size " +
String.valueOf(buffer_size) + "."
); //TODO remove if publishing
//Close the streams
os.flush();
stage++;
os.close();
stage++;
is.close();
Log.d(TAG, "All Streams have been flushed and closed.");
} catch (IOException e) {
String exception_message = "";
e.printStackTrace();
switch (stage) {
case 0:
exception_message = "Error trying to open the asset " + dbname;
break;
case 1:
exception_message = "Error opening Database file for output, path is " + f.getPath();
break;
case 2:
exception_message = "Error flushing written database file " + f.getPath();
break;
case 3:
exception_message = "Error closing written database file " + f.getPath();
break;
case 4:
exception_message = "Error closing asset file " + f.getPath();
}
throw new RuntimeException("Unable to copy the database from the asset folder." + exception_message + " see starck-trace above.");
}
}
/**
* Copy the databsse from the assets folder where asset name and dbname are the same
* @param context
* @param dbname
* @param deleteExistingDB
*/
public static void copyDataBase(Context context, String dbname, boolean deleteExistingDB) {
copyDataBase(context, dbname,dbname,deleteExistingDB);
}
/**
* Get the SQLite_user_vesrion from the DB in the asset folder
*
* @param context needed to get the appropriate package assets
* @param assetfilename the name of the asset file (assumes/requires name matches database)
* @return the version number as stored in the asset DB
*/
public static int getVersionFromDBInAssetFolder(Context context, String assetfilename) {
InputStream is;
try {
is = context.getAssets().open(assetfilename);
} catch (IOException e) {
return OUCH;
}
return getDBVersionFromInputStream(is);
}
/**
* Get the version from the database itself without opening the database as an SQliteDatabase
* @param context Needed to ascertain package
* @param dbname the name of the dataabase
* @return the version number extracted
*/
public static int getVersionFromDBFile(Context context, String dbname) {
InputStream is;
try {
is = new FileInputStream(new File(context.getDatabasePath(dbname).toString()));
} catch (IOException e) {
return OUCH;
}
return getDBVersionFromInputStream(is);
}
/**
* Get the Database Version (user_version) from an inputstream
* Note the inputstream is closed
* @param is The Inputstream
* @return The extracted version number
*/
private static int getDBVersionFromInputStream(InputStream is) {
int rv = -1, dbversion_offset = 60, dbversion_length = 4 ;
byte[] dbfileheader = new byte[64];
byte[] dbversion = new byte[4];
try {
is.read(dbfileheader);
is.close();
} catch (IOException e) {
e.printStackTrace();
return rv;
}
for (int i = 0; i < dbversion_length; i++ ) {
dbversion[i] = dbfileheader[dbversion_offset + i];
}
return ByteBuffer.wrap(dbversion).getInt();
}
/**
* Check to see if the asset file exists
*
* @param context needed to get the appropriate package
* @param assetfilename the name of the asset file to check
* @return true if the asset file exists, else false
*/
public static boolean ifAssetFileExists(Context context, String assetfilename) {
try {
context.getAssets().open(assetfilename);
} catch (IOException e) {
return false;
}
return true;
}
/**
* Delete the backup
* @param context
* @param dbname
*/
public static void clearForceBackups(Context context, String dbname) {
String[] fulllist = new String[tempfiles.length + 1];
for (int i = 0;i < tempfiles.length; i++) {
fulllist[i] = tempfiles[i];
}
fulllist[tempfiles.length] = ""; // Add "" so database file backup is also deleted
for (String s: fulllist) {
File tmpf = new File(context.getDatabasePath(dbname + s + backup).toString());
if (tmpf.exists()) {
tmpf.delete();
}
}
}
}
- Esperamos que os nomes e comentários dos métodos expliquem o código acima.
Os arquivos de ativos, existem dois:-
- pev1.db - o banco de dados pré-existente original, conforme descrito acima
- pev1mod.db - o modificado (coluna extra, restrição UNIQUE e a linha adicional).
O Auxiliar de Banco de Dados (uma subclasse de SQLOpenHelper) é PEV2DBHelper.java , Deve-se notar que a Versão do Banco de Dados (DBVERSION ) é usado para controlar e é diferente da versão do APK (que pode mudar com mais frequência do que o DB)
- foram encontrados problemas ao tentar usar o onUpgrade método, portanto, uma abordagem alternativa, a de obter a versão_do_usuário dos bancos de dados do arquivo em vez de um SQLiteDatabase.
Aqui está PEV2DBHelper.java :-
/**
* MORE COMPLEX EXAMPLE RETAINING USER DATA
*/
public class PEV2DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "pev1.db";
public static final String ASSETTOCOPY_DBV2 = "pev1mod.db"; //<<<<<<<<<< changed DB
public static final int DBVERSION = 2; //<<<<<<<<<< increase and db file from assets will copied keeping existing data
Context mContext;
public PEV2DBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
int dbversion = DBAssetHandler.getVersionFromDBFile(context,DBNAME);
Log.d("DBFILEVERSION","Database File Version = " + String.valueOf(dbversion));
int af1version = DBAssetHandler.getVersionFromDBInAssetFolder(context,DBNAME);
Log.d("DBFILEVERSION","Asset Database File Version = " + String.valueOf(af1version));
int af2version = DBAssetHandler.getVersionFromDBInAssetFolder(context,ASSETTOCOPY_DBV2);
Log.d("DBFILEVERSION","Asset Database File Version = " + String.valueOf(af2version));
// cater for different DBVERSIONS (for testing )
if (!DBAssetHandler.checkDataBase(context,DBNAME)) {
//If new installation of the APP then copy the appropriate asset file for the DB
switch (DBVERSION) {
case 1:
DBAssetHandler.copyDataBase(context,DBNAME,DBNAME,false);
break;
case 2:
DBAssetHandler.copyDataBase(context,DBNAME,ASSETTOCOPY_DBV2,false);
break;
}
}
// If DBVERSION upgraded to 2 with modified DB but wanting to preserve used data
if (DBAssetHandler.checkDataBase(context,DBNAME) & (DBVERSION > DBAssetHandler.getVersionFromDBFile(context, DBNAME)) & (DBVERSION == 2) ) {
String[] oldcolumns = new String[]{"user","password"};
// Copy in the new DB noting that delete option renames old (truue flag important)
DBAssetHandler.copyDataBase(context,DBNAME,ASSETTOCOPY_DBV2,true);
//Get the newly copied database
SQLiteDatabase newdb = SQLiteDatabase.openDatabase(context.getDatabasePath(DBNAME).toString(),null,SQLiteDatabase.OPEN_READWRITE);
//Get the old database (backup copy)
SQLiteDatabase olddb = SQLiteDatabase.openDatabase(context.getDatabasePath(DBNAME + DBAssetHandler.backup).toString(),null,SQLiteDatabase.OPEN_READWRITE);
//Prepare to insert old rows (note user column is UNIQUE so pretty simple scenario just try inserting all and duplicates will be rejected)
ContentValues cv = new ContentValues();
Cursor oldcsr = olddb.query("user",null,null,null,null,null,null);
newdb.beginTransaction();
while (oldcsr.moveToNext()) {
cv.clear();
for (String columnname: oldcolumns) {
cv.put(columnname,oldcsr.getString(oldcsr.getColumnIndex(columnname)));
}
newdb.insert("user",null,cv);
}
newdb.setTransactionSuccessful();
newdb.endTransaction();
newdb.close();
olddb.close();
// Finally delete the renamed old database
DBAssetHandler.clearForceBackups(context,DBNAME);
}
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
- Observe que há pouco inchaço na forma de métodos para adicionar, excluir linhas de extração. No entanto, é um pouco complexo demais, pois lida com a alternância entre as versões para facilitar a demonstração.
Por último, há uma atividade de exemplo que invoca o PEV2DBHelper, gravando o esquema e as linhas da tabela no log.
A atividade usada é MainActivity.java e é :-
public class MainActivity extends AppCompatActivity {
PEV2DBHelper mDBHlpr2; //DBHelper for example that retains user data
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doPEV2(); // Test simple more complex scenario
}
private void doPEV2() {
mDBHlpr2 = new PEV2DBHelper(this);
SQLiteDatabase db = mDBHlpr2.getWritableDatabase();
Cursor csr = db.query("sqlite_master",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
csr = db.query("user",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
if (PEV2DBHelper.DBVERSION == 1) {
addUserData(db);
csr = db.query("user",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
}
csr.close();
db.close();
}
/**
* Add some user data for testing presevation of that data
* @param db the SQLitedatabase
*/
private void addUserData(SQLiteDatabase db) {
ContentValues cv = new ContentValues();
cv.put("user","mr new user");
cv.put("password","a password");
db.insert("user",null,cv);
}
}
Resultados
1. Quando executado pela primeira vez com o DBVERSION como 1 (novo aplicativo)
Nesse caso, o arquivo de recurso pev1.db é copiado da pasta de ativos, a saída é:-
2019-02-22 19:07:54.676 28670-28670/? D/DBFILEVERSION: Database File Version = -666666666
2019-02-22 19:07:54.677 28670-28670/? D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:07:54.677 28670-28670/? D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:07:54.677 28670-28670/? D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Initiated Copy of the database file pev1.db from the assets folder.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Asset file pev1.db found so attmepting to copy to /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Attempting copy of block 1 which has 4096 bytes.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Attempting copy of block 2 which has 4096 bytes.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Finished copying Database pev1.db from the assets folder, to /data/user/0/mjt.so54807516/databases/pev1.db8192were copied, in 2 blocks of size 4096.
2019-02-22 19:07:54.678 28670-28670/? D/COPYDATABASE: All Streams have been flushed and closed.
2019-02-22 19:07:54.678 28670-28670/? D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.701 28670-28670/? I/System.out: >>>>> Dumping cursor [email protected]
2019-02-22 19:07:54.701 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.701 28670-28670/? I/System.out: type=table
2019-02-22 19:07:54.701 28670-28670/? I/System.out: name=user
2019-02-22 19:07:54.701 28670-28670/? I/System.out: tbl_name=user
2019-02-22 19:07:54.702 28670-28670/? I/System.out: rootpage=2
2019-02-22 19:07:54.702 28670-28670/? I/System.out: sql=CREATE TABLE "user" (
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "_id" INTEGER NOT NULL,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "user" TEXT,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "password" TEXT,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: PRIMARY KEY ("_id")
2019-02-22 19:07:54.702 28670-28670/? I/System.out: )
2019-02-22 19:07:54.702 28670-28670/? I/System.out: }
2019-02-22 19:07:54.702 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.702 28670-28670/? I/System.out: type=table
2019-02-22 19:07:54.702 28670-28670/? I/System.out: name=android_metadata
2019-02-22 19:07:54.702 28670-28670/? I/System.out: tbl_name=android_metadata
2019-02-22 19:07:54.702 28670-28670/? I/System.out: rootpage=3
2019-02-22 19:07:54.702 28670-28670/? I/System.out: sql=CREATE TABLE android_metadata (locale TEXT)
2019-02-22 19:07:54.702 28670-28670/? I/System.out: }
2019-02-22 19:07:54.702 28670-28670/? I/System.out: <<<<<
2019-02-22 19:07:54.703 28670-28670/? I/System.out: >>>>> Dumping cursor [email protected]
2019-02-22 19:07:54.703 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.703 28670-28670/? I/System.out: _id=1
2019-02-22 19:07:54.703 28670-28670/? I/System.out: user=Fred
2019-02-22 19:07:54.703 28670-28670/? I/System.out: password=fredpassword
2019-02-22 19:07:54.703 28670-28670/? I/System.out: }
2019-02-22 19:07:54.703 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.703 28670-28670/? I/System.out: _id=2
2019-02-22 19:07:54.703 28670-28670/? I/System.out: user=Mary
2019-02-22 19:07:54.704 28670-28670/? I/System.out: password=marypassword
2019-02-22 19:07:54.704 28670-28670/? I/System.out: }
2019-02-22 19:07:54.704 28670-28670/? I/System.out: <<<<<
2019-02-22 19:07:54.705 28670-28670/? I/System.out: >>>>> Dumping cursor [email protected]
2019-02-22 19:07:54.705 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.705 28670-28670/? I/System.out: _id=1
2019-02-22 19:07:54.705 28670-28670/? I/System.out: user=Fred
2019-02-22 19:07:54.705 28670-28670/? I/System.out: password=fredpassword
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.706 28670-28670/? I/System.out: _id=2
2019-02-22 19:07:54.706 28670-28670/? I/System.out: user=Mary
2019-02-22 19:07:54.706 28670-28670/? I/System.out: password=marypassword
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: 2 {
2019-02-22 19:07:54.706 28670-28670/? I/System.out: _id=3
2019-02-22 19:07:54.706 28670-28670/? I/System.out: user=mr new user
2019-02-22 19:07:54.706 28670-28670/? I/System.out: password=a password
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: <<<<<
- -666666666 é a versão, pois nenhum arquivo existia, portanto, a tentativa de obter a versão do arquivo retornou o valor padrão para indicar que a versão não pôde ser obtida.
2. Segunda execução tudo igual, EXCETO que o número da versão é 1.
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Database File Version = 1
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:09:43.725 28730-28730/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:09:43.725 28730-28730/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:09:43.729 28730-28730/mjt.so54807516 I/System.out: >>>>>
..... etc
3. Próxima execução após alterar DBVERSION para 2
2019-02-22 19:13:49.157 28866-28866/mjt.so54807516 D/DBFILEVERSION: Database File Version = 1
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/COPYDATABASE: Initiated Copy of the database file pev1mod.db from the assets folder.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Asset file pev1mod.db found so attmepting to copy to /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 1 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 2 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 3 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 4 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Finished copying Database pev1.db from the assets folder, to /data/user/0/mjt.so54807516/databases/pev1.db16384were copied, in 4 blocks of size 4096.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: All Streams have been flushed and closed.
2019-02-22 19:13:49.186 28866-28866/mjt.so54807516 E/SQLiteDatabase: Error inserting password=fredpassword user=Fred
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: user.user (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at
.........
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2019-02-22 19:13:49.191 28866-28866/mjt.so54807516 E/SQLiteDatabase: Error inserting password=a password user=mr new user
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: user.user (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at
.............
2019-02-22 19:13:49.209 28866-28866/mjt.so54807516 I/System.out: >>>>> Dumping cursor [email protected]
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: 0 {
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: type=table
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: name=user
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: tbl_name=user
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: rootpage=2
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: sql=CREATE TABLE "user" (
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "_id" INTEGER NOT NULL,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "user" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "password" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "email" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: PRIMARY KEY ("_id"),
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: CONSTRAINT "user" UNIQUE ("user")
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: )
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: 1 {
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: type=index
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: name=sqlite_autoindex_user_1
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: tbl_name=user
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: rootpage=4
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: sql=null
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: 2 {
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: type=table
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: name=android_metadata
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: tbl_name=android_metadata
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: rootpage=3
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: sql=CREATE TABLE android_metadata (locale TEXT)
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: <<<<<
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: >>>>> Dumping cursor [email protected]
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: 0 {
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: _id=1
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: user=Fred
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: password=fredpassword
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: [email protected]
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: 1 {
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: _id=2
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out:
...... etc