@@ -35,11 +35,22 @@ import 'utils.dart' show canonicalizeVersion, ByteArrayEqualsExt;
3535final _logger = Logger ('integrity.check' );
3636final _random = math.Random .secure ();
3737
38+ /// The unmapped/unused fields that we expect to be present on some entities.
39+ /// The presence of such fields won't be reported as integrity issue, only
40+ /// the absent ones will be reported.
41+ const _allowedUnmappedFields = {
42+ 'Package.isWithheld' ,
43+ 'Package.withheldReason' ,
44+ };
45+
3846/// Checks the integrity of the datastore.
3947class IntegrityChecker {
4048 final DatastoreDB _db;
4149 final int _concurrency;
4250
51+ /// Maps an unmapped field in the form of `<ClassName>.<fieldName>` to an
52+ /// object identifier (usually the `id` value of the entity).
53+ final _unmappedFieldsToObject = < String , String > {};
4354 final _userToOauth = < String , String ? > {};
4455 final _oauthToUser = < String , String > {};
4556 final _deletedUsers = < String > {};
@@ -93,7 +104,13 @@ class IntegrityChecker {
93104 yield * _checkAuditLogs ();
94105 yield * _checkModerationCases ();
95106 yield * _reportPubspecVersionIssues ();
96- // TODO: report unmapped properties
107+
108+ if (_unmappedFieldsToObject.isNotEmpty) {
109+ for (final entry in _unmappedFieldsToObject.entries) {
110+ if (_allowedUnmappedFields.contains (entry.key)) continue ;
111+ yield 'Unmapped field found: "${entry .key }" on entity "${entry .value }".' ;
112+ }
113+ }
97114 } finally {
98115 _httpClient.close ();
99116 }
@@ -448,6 +465,7 @@ class IntegrityChecker {
448465 }
449466
450467 await for (final pvi in pviQuery.run ()) {
468+ _updateUnmappedFields (pvi);
451469 final key = pvi.qualifiedVersionKey;
452470 pviKeys.add (key);
453471 yield * checkPackageVersionKey ('PackageVersionInfo' , key);
@@ -475,6 +493,7 @@ class IntegrityChecker {
475493 ..filter ('package =' , p.name);
476494 final foundAssetIds = < String ? > {};
477495 await for (final pva in pvaQuery.run ()) {
496+ _updateUnmappedFields (pva);
478497 final key = pva.qualifiedVersionKey;
479498 if (pva.id !=
480499 Uri (pathSegments: [pva.package! , pva.version! , pva.kind! ]).path) {
@@ -907,13 +926,24 @@ class IntegrityChecker {
907926 }
908927 }
909928
929+ void _updateUnmappedFields (Model m) {
930+ if (m is ExpandoModel && m.additionalProperties.isNotEmpty) {
931+ for (final key in m.additionalProperties.keys) {
932+ final qualifiedField = [m.runtimeType.toString (), key].join ('.' );
933+ if (_unmappedFieldsToObject.containsKey (qualifiedField)) continue ;
934+ _unmappedFieldsToObject[qualifiedField] = m.id.toString ();
935+ }
936+ }
937+ }
938+
910939 Stream <String > _queryWithPool <R extends Model >(
911940 Stream <String > Function (R model) fn) async * {
912941 final query = _db.query <R >();
913942 final pool = Pool (_concurrency);
914943 final futures = < Future <List <String >>> [];
915944 try {
916945 await for (final m in query.run ()) {
946+ _updateUnmappedFields (m);
917947 final f = pool.withResource (() => fn (m).toList ());
918948 futures.add (f);
919949 }
0 commit comments