mirror of https://github.com/AMT-Cheif/drift.git
Ignore internal tables when reading schema from db
This commit is contained in:
parent
0e395bbef3
commit
b520568984
|
@ -1,5 +1,6 @@
|
|||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:drift_dev/src/analysis/options.dart';
|
||||
import 'package:drift_dev/src/services/schema/verifier_impl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:sqlite3/common.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
@ -17,16 +18,9 @@ import '../../analysis/results/results.dart';
|
|||
Future<List<DriftElement>> extractDriftElementsFromDatabase(
|
||||
CommonDatabase database) async {
|
||||
// Put everything from sqlite_schema into a fake drift file, analyze it.
|
||||
final contents = database
|
||||
.select('select * from sqlite_master')
|
||||
.map((row) => row['sql'])
|
||||
.whereType<String>()
|
||||
.map((sql) => sql.endsWith(';') ? sql : '$sql;')
|
||||
.join('\n');
|
||||
|
||||
final logger = Logger('extractDriftElementsFromDatabase');
|
||||
final uri = Uri.parse('db.drift');
|
||||
final backend = _SingleFileNoAnalyzerBackend(logger, contents, uri);
|
||||
final backend = _SingleFileNoAnalyzerBackend(logger, uri);
|
||||
final driver = DriftAnalysisDriver(
|
||||
backend,
|
||||
DriftOptions.defaults(
|
||||
|
@ -37,8 +31,38 @@ Future<List<DriftElement>> extractDriftElementsFromDatabase(
|
|||
),
|
||||
);
|
||||
|
||||
final file = await driver.fullyAnalyze(uri);
|
||||
final engineForParsing = driver.newSqlEngine();
|
||||
final entities = <String, String>{};
|
||||
final virtualTableNames = <String>[];
|
||||
for (final row in database.select('select * from sqlite_master')) {
|
||||
final name = row['name'] as String?;
|
||||
var sql = row['sql'] as String?;
|
||||
|
||||
if (name == null ||
|
||||
sql == null ||
|
||||
isInternalElement(name, virtualTableNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sql.endsWith(';')) {
|
||||
sql += ';';
|
||||
}
|
||||
|
||||
final parsed = engineForParsing.parse(sql).rootNode;
|
||||
|
||||
// Virtual table modules often add auxiliary tables that aren't part of the
|
||||
// user-defined database schema. So we need to keep track of them to be
|
||||
// able to filter internal tables out.
|
||||
if (parsed is CreateVirtualTableStatement) {
|
||||
virtualTableNames.add(parsed.tableName);
|
||||
}
|
||||
|
||||
entities[name] = sql;
|
||||
}
|
||||
entities.removeWhere((name, _) => isInternalElement(name, virtualTableNames));
|
||||
backend.contents = entities.values.join('\n');
|
||||
|
||||
final file = await driver.fullyAnalyze(uri);
|
||||
return [
|
||||
for (final entry in file.analysis.values)
|
||||
if (entry.result != null) entry.result!
|
||||
|
@ -49,10 +73,10 @@ class _SingleFileNoAnalyzerBackend extends DriftBackend {
|
|||
@override
|
||||
final Logger log;
|
||||
|
||||
final String file;
|
||||
late final String contents;
|
||||
final Uri uri;
|
||||
|
||||
_SingleFileNoAnalyzerBackend(this.log, this.file, this.uri);
|
||||
_SingleFileNoAnalyzerBackend(this.log, this.uri);
|
||||
|
||||
Never _noAnalyzer() =>
|
||||
throw UnsupportedError('Dart analyzer not available here');
|
||||
|
@ -64,7 +88,7 @@ class _SingleFileNoAnalyzerBackend extends DriftBackend {
|
|||
|
||||
@override
|
||||
Future<String> readAsString(Uri uri) {
|
||||
return Future.value(file);
|
||||
return Future.value(contents);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -81,16 +81,26 @@ class VerifierImplementation implements SchemaVerifier {
|
|||
Input? _parseInputFromSchemaRow(
|
||||
Map<String, Object?> row, List<String> virtualTables) {
|
||||
final name = row['name'] as String;
|
||||
if (isInternalElement(name, virtualTables)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Input(name, row['sql'] as String);
|
||||
}
|
||||
|
||||
/// Attempts to recognize whether [name] is likely the name of an internal
|
||||
/// sqlite3 table (like `sqlite3_sequence`) that we should not consider when
|
||||
/// comparing schemas.
|
||||
bool isInternalElement(String name, List<String> virtualTables) {
|
||||
// Skip sqlite-internal tables, https://www.sqlite.org/fileformat2.html#intschema
|
||||
if (name.startsWith('sqlite_')) return null;
|
||||
if (virtualTables.any((v) => name.startsWith('${v}_'))) return null;
|
||||
if (name.startsWith('sqlite_')) return true;
|
||||
if (virtualTables.any((v) => name.startsWith('${v}_'))) return true;
|
||||
|
||||
// This file is added on some Android versions when using the native Android
|
||||
// database APIs, https://github.com/simolus3/drift/discussions/2042
|
||||
if (name == 'android_metadata') return null;
|
||||
if (name == 'android_metadata') return true;
|
||||
|
||||
return Input(name, row['sql'] as String);
|
||||
return false;
|
||||
}
|
||||
|
||||
extension CollectSchemaDb on DatabaseConnectionUser {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:drift_dev/src/analysis/results/results.dart';
|
||||
import 'package:drift_dev/src/services/schema/sqlite_to_drift.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('can extract elements from database', () async {
|
||||
final database = sqlite3.openInMemory()
|
||||
..execute('CREATE TABLE foo (id INTEGER PRIMARY KEY, bar TEXT);')
|
||||
..execute('CREATE INDEX my_idx ON foo (bar)')
|
||||
..execute('CREATE VIEW my_view AS SELECT bar FROM foo')
|
||||
..execute('CREATE TRIGGER my_trigger AFTER UPDATE ON foo BEGIN '
|
||||
'UPDATE foo SET bar = old.bar; '
|
||||
'END;');
|
||||
addTearDown(database.dispose);
|
||||
|
||||
final elements = await extractDriftElementsFromDatabase(database);
|
||||
expect(
|
||||
elements,
|
||||
unorderedEquals([
|
||||
isA<DriftTable>().having((e) => e.schemaName, 'schemaName', 'foo'),
|
||||
isA<DriftIndex>().having((e) => e.schemaName, 'schemaName', 'my_idx'),
|
||||
isA<DriftView>().having((e) => e.schemaName, 'schemaName', 'my_view'),
|
||||
isA<DriftTrigger>()
|
||||
.having((e) => e.schemaName, 'schemaName', 'my_trigger'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('ignores internal tables', () async {
|
||||
final database = sqlite3.openInMemory()
|
||||
..execute('CREATE TABLE my_table (id INTEGER PRIMARY KEY AUTOINCREMENT)')
|
||||
..execute('CREATE VIRTUAL TABLE foo USING fts5(x,y, z);');
|
||||
|
||||
addTearDown(database.dispose);
|
||||
|
||||
final elements = await extractDriftElementsFromDatabase(database);
|
||||
expect(
|
||||
elements,
|
||||
unorderedEquals([
|
||||
isA<DriftTable>().having((e) => e.schemaName, 'schemaName', 'my_table'),
|
||||
isA<DriftTable>().having((e) => e.schemaName, 'schemaName', 'foo'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
}
|
|
@ -168,8 +168,6 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
|
|||
return Entries(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
|
|
@ -168,8 +168,6 @@ class Entries extends Table with TableInfo<Entries, Entrie> {
|
|||
return Entries(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue