@@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
22import type { IFieldRo , ILinkFieldOptions , IConvertFieldRo } from '@teable/core' ;
33import { FieldType , Relationship , isLinkLookupOptions } from '@teable/core' ;
44import type { Field , TableMeta } from '@teable/db-main-prisma' ;
5- import { PrismaService } from '@teable/db-main-prisma' ;
5+ import { Prisma , PrismaService } from '@teable/db-main-prisma' ;
66import type {
77 IGraphEdge ,
88 IGraphNode ,
@@ -47,6 +47,12 @@ interface ITinyTable {
4747 dbTableName : string ;
4848}
4949
50+ interface IAffectedCountQuery {
51+ fieldId : string ;
52+ fieldName : string ;
53+ query : string ;
54+ }
55+
5056@Injectable ( )
5157export class GraphService {
5258 private logger = new Logger ( GraphService . name ) ;
@@ -455,45 +461,127 @@ export class GraphService {
455461 fieldMap : IFieldMap ,
456462 fieldId2DbTableName : Record < string , string >
457463 ) : Promise < number > {
458- const queries = fieldIds . map ( ( fieldId ) => {
459- const field = fieldMap [ fieldId ] ;
460- const lookupOptions = field . lookupOptions ;
464+ const queries = fieldIds
465+ . map ( ( fieldId ) =>
466+ this . buildAffectedCountQuery ( hostFieldId , fieldId , fieldMap , fieldId2DbTableName )
467+ )
468+ . filter ( ( query ) : query is IAffectedCountQuery => query != null ) ;
461469
462- if ( field . id !== hostFieldId ) {
463- if ( field . type === FieldType . Link ) {
464- const { relationship, fkHostTableName, selfKeyName, foreignKeyName } =
465- field . options as ILinkFieldOptions ;
466- const query =
467- relationship === Relationship . OneOne || relationship === Relationship . ManyOne
468- ? this . knex . count ( foreignKeyName , { as : 'count' } ) . from ( fkHostTableName )
469- : this . knex . countDistinct ( selfKeyName , { as : 'count' } ) . from ( fkHostTableName ) ;
470-
471- return query . toQuery ( ) ;
470+ let total = 0 ;
471+ for ( const { fieldId, fieldName, query } of queries ) {
472+ try {
473+ const [ { count } ] = await this . prismaService . $queryRawUnsafe < { count : bigint } [ ] > ( query ) ;
474+ total += Number ( count ) ;
475+ } catch ( error ) {
476+ if ( this . shouldSkipAffectedCountError ( error ) ) {
477+ this . logger . warn (
478+ `Skip affected cell count for field=${ fieldId } name="${ fieldName } " due to broken storage: ${
479+ error . meta ?. message || error . message
480+ } `
481+ ) ;
482+ continue ;
472483 }
484+ throw error ;
485+ }
486+ }
487+ return total ;
488+ }
473489
474- if ( lookupOptions && isLinkLookupOptions ( lookupOptions ) ) {
475- const { relationship, fkHostTableName, selfKeyName, foreignKeyName } = lookupOptions ;
476- const query =
477- relationship === Relationship . OneOne || relationship === Relationship . ManyOne
478- ? this . knex . count ( foreignKeyName , { as : 'count' } ) . from ( fkHostTableName )
479- : this . knex . countDistinct ( selfKeyName , { as : 'count' } ) . from ( fkHostTableName ) ;
490+ private buildAffectedCountQuery (
491+ hostFieldId : string ,
492+ fieldId : string ,
493+ fieldMap : IFieldMap ,
494+ fieldId2DbTableName : Record < string , string >
495+ ) : IAffectedCountQuery | null {
496+ const field = fieldMap [ fieldId ] ;
480497
481- return query . toQuery ( ) ;
482- }
498+ if ( ! field ) {
499+ this . logger . warn ( `Skip affected cell count for missing field metadata: ${ fieldId } ` ) ;
500+ return null ;
501+ }
502+
503+ const lookupOptions = field . lookupOptions ;
504+
505+ if ( field . id !== hostFieldId ) {
506+ if ( field . type === FieldType . Link ) {
507+ return this . buildLinkAffectedCountQuery (
508+ field . id ,
509+ field . name ,
510+ field . options as ILinkFieldOptions
511+ ) ;
483512 }
484513
485- const dbTableName = fieldId2DbTableName [ fieldId ] ;
486- return this . knex . count ( '*' , { as : 'count' } ) . from ( dbTableName ) . toQuery ( ) ;
487- } ) ;
488- // console.log('queries', queries);
514+ if ( lookupOptions && isLinkLookupOptions ( lookupOptions ) ) {
515+ return this . buildLinkAffectedCountQuery ( field . id , field . name , lookupOptions ) ;
516+ }
517+ }
489518
490- let total = 0 ;
491- for ( const query of queries ) {
492- const [ { count } ] = await this . prismaService . $queryRawUnsafe < { count : bigint } [ ] > ( query ) ;
493- // console.log('count', count);
494- total += Number ( count ) ;
519+ const dbTableName = fieldId2DbTableName [ fieldId ] ;
520+ if ( ! dbTableName ) {
521+ this . logger . warn (
522+ `Skip affected cell count for field=${ fieldId } name="${ field . name } " because db table name is missing`
523+ ) ;
524+ return null ;
495525 }
496- return total ;
526+
527+ return {
528+ fieldId,
529+ fieldName : field . name ,
530+ query : this . knex . count ( '*' , { as : 'count' } ) . from ( dbTableName ) . toQuery ( ) ,
531+ } ;
532+ }
533+
534+ private buildLinkAffectedCountQuery (
535+ fieldId : string ,
536+ fieldName : string ,
537+ options : Pick <
538+ ILinkFieldOptions ,
539+ 'relationship' | 'fkHostTableName' | 'selfKeyName' | 'foreignKeyName'
540+ >
541+ ) : IAffectedCountQuery | null {
542+ const { relationship, fkHostTableName, selfKeyName, foreignKeyName } = options ;
543+
544+ if ( ! fkHostTableName || ! foreignKeyName ) {
545+ this . logger . warn (
546+ `Skip affected cell count for field=${ fieldId } name="${ fieldName } " because link storage metadata is incomplete`
547+ ) ;
548+ return null ;
549+ }
550+
551+ if (
552+ relationship !== Relationship . OneOne &&
553+ relationship !== Relationship . ManyOne &&
554+ ! selfKeyName
555+ ) {
556+ this . logger . warn (
557+ `Skip affected cell count for field=${ fieldId } name="${ fieldName } " because link key metadata is incomplete`
558+ ) ;
559+ return null ;
560+ }
561+
562+ const query =
563+ relationship === Relationship . OneOne || relationship === Relationship . ManyOne
564+ ? this . knex . count ( foreignKeyName , { as : 'count' } ) . from ( fkHostTableName )
565+ : this . knex . countDistinct ( selfKeyName as string , { as : 'count' } ) . from ( fkHostTableName ) ;
566+
567+ return {
568+ fieldId,
569+ fieldName,
570+ query : query . toQuery ( ) ,
571+ } ;
572+ }
573+
574+ private shouldSkipAffectedCountError (
575+ error : unknown
576+ ) : error is Prisma . PrismaClientKnownRequestError & {
577+ meta ?: { code ?: string ; message ?: string } ;
578+ } {
579+ if ( ! ( error instanceof Prisma . PrismaClientKnownRequestError ) || error . code !== 'P2010' ) {
580+ return false ;
581+ }
582+
583+ const storageErrorCode = ( error . meta as { code ?: string } | undefined ) ?. code ;
584+ return storageErrorCode === '42703' || storageErrorCode === '42P01' ;
497585 }
498586
499587 @Timing ( )
0 commit comments