Import database from bytes #3132
-
After the answer #2777 (comment) I tried to figure out what I was missing but didn't found. With the browser dev tools I can confirm that the old database is deleted and a new one is saved in "Storage", but the data isn't available in the app. // drive code
Future<Database> resetDatabases() async {
final database = <DriftDatabase>;
await database.close();
await DatabaseConnection()
.deleteDatabase(databaseName: database.databaseName);
final fileBytes =
(await httpClient.get(Uri.parse(snapshotUrl))).bodyBytes;
return Database(fileBytes: fileBytes);
}
// web implementation
class DatabaseConnector implements p.DatabaseConnector {
final Logger _logger = Logger('WebDatabaseConnector');
@override
QueryExecutor createDatabaseConnection({
required String databaseName,
QueryInterceptor? interceptor,
String? path,
FutureOr<void> Function()? isolateSetup,
Uint8List? databaseBytes,
}) {
return DatabaseConnection.delayed(Future(() async {
final result = await WasmDatabase.open(
databaseName: databaseName,
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse(
kReleaseMode ? 'drift_worker.dart.min.js' : 'drift_worker.dart.js',
),
initializeDatabase: () => databaseBytes,
);
if (result.missingFeatures.isNotEmpty) {
// Depending how central local persistence is to your app, you may want
// to show a warning to the user if only unreliable implementations
// are available.
_logger
.info('Using ${result.chosenImplementation} due to missing browser '
'features: ${result.missingFeatures}');
}
DatabaseConnection databaseConnection = result.resolvedExecutor;
if (interceptor != null) {
databaseConnection = databaseConnection.interceptWith(interceptor);
}
return databaseConnection;
}));
}
@override
Future<void> deleteDatabase(
{required String databaseName, String? path}) async {
final probe = await WasmDatabase.probe(
databaseName: databaseName,
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse(
kReleaseMode ? 'drift_worker.dart.min.js' : 'drift_worker.dart.js',
),
);
await probe.deleteDatabase(probe.existingDatabases.single);
}
}
// native implementation
class DatabaseConnector implements p.DatabaseConnector {
@override
QueryExecutor createDatabaseConnection({
required String databaseName,
QueryInterceptor? interceptor,
String? path,
FutureOr<void> Function()? isolateSetup,
Uint8List? databaseBytes,
}) {
return LazyDatabase(() async {
// put the database file into the documents folder for the app.
final dbFolder = path ?? (await getApplicationSupportDirectory()).path;
final databaseFile = File(join(dbFolder, '$databaseName.sqlite'));
if (databaseBytes != null) {
await databaseFile.create(recursive: true);
await databaseFile.writeAsBytes(databaseBytes);
}
DatabaseConnection databaseConnection;
// Also work around limitations on old Android versions
if (Platform.isAndroid) {
await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
// We can't access /tmp on Android, which sqlite3 would try by default.
// Explicitly tell it about the correct temporary directory.
sqlite3.tempDirectory = (await getTemporaryDirectory()).path;
}
databaseConnection = NativeDatabase.createBackgroundConnection(
databaseFile,
isolateSetup: isolateSetup ??
() {
open.overrideFor(OperatingSystem.android, openCipherOnAndroid);
},
setup: (db) {
// Check that we're actually running with SQLCipher by quering the
// cipher_version pragma.
final result = db.select('pragma cipher_version');
if (result.isEmpty) {
throw UnsupportedError(
'This database needs to run with SQLCipher, but that library is '
'not available!',
);
}
// Then, apply the key to encrypt the database. Unfortunately, this
// pragma doesn't seem to support prepared statements so we inline the
// key.
final escapedKey = _encryptionPassword.replaceAll("'", "''");
db.execute("pragma key = '$escapedKey'");
// Test that the key is correct by selecting from a table
db.execute('select count(*) from sqlite_master');
},
);
if (interceptor != null) {
databaseConnection = databaseConnection.interceptWith(interceptor);
}
return databaseConnection;
});
}
@override
Future<void> deleteDatabase(
{required String databaseName, String? path}) async {
final dbDirectoryPath =
path ?? (await getApplicationSupportDirectory()).path;
final dbPath = join(dbDirectoryPath, '$databaseName.sqlite');
final dbFile = File(dbPath);
if (dbFile.existsSync()) dbFile.deleteSync();
}
}
|
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 3 replies
-
Do you have a complete (runnable) way to reproduce this? The web implementation looks correct to me, I'm doing something similar and that works. So I'd love to debug this, but it's hard to do with the snippets alone because they're very similar to what I'm also doing (so it might be something outside of that). |
Beta Was this translation helpful? Give feedback.
-
I'm trying to isolate the problem, when I'm done I will send it here. I don't know if this information helps but when the
|
Beta Was this translation helpful? Give feedback.
-
Code to reproduce -> https://github.com/AlexandreAndrade00/drift_replace_db_bug In the example, I used an asset instead of an HTTP request, but the behavior is equal (discard how the bytes are obtained). To obtain the SQLite file, I ran the application in native (Linux). |
Beta Was this translation helpful? Give feedback.
The reason you're getting this uncaught exception is because it happens in the insert in
body_widget.dart
, which is not awaited.You can see the actual error when adapting the stream, for instance: