diff --git a/packages/core/server/src/main-data-source.ts b/packages/core/server/src/main-data-source.ts index 41c8a64cdf7..fb68cff4b7f 100644 --- a/packages/core/server/src/main-data-source.ts +++ b/packages/core/server/src/main-data-source.ts @@ -119,7 +119,9 @@ export class MainDataSource extends SequelizeDataSource { const loadedData = {}; for (const collection of collections) { const c = db.getCollection(collection.name); - loadedData[c.tableName()] = { + // Use the physical table/view name so view collections whose logical name + // differs from `viewName` can still be matched during introspection sync. + loadedData[c.model.tableName] = { ...collection.toJSON(), fields: collection.fields.map((field: Model) => { const f = c.getField(field.name); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/view-collection.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/view-collection.test.ts index 907084efc38..0ecf6246785 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/view-collection.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/view-collection.test.ts @@ -412,6 +412,71 @@ SELECT * FROM numbers; expect(viewCollectionWithEmail.getField('email')).toBeTruthy(); }); + it.each([ + { collectionName: 'view', viewName: 'test_view' }, + { collectionName: 'test_view', viewName: 'test_view' }, + ])( + 'should sync main data source fields when collection name is $collectionName and underlying view is $viewName', + async ({ collectionName, viewName }) => { + await app.db.getRepository('collections').create({ + values: { + name: 'users', + fields: [ + { + name: 'name', + type: 'string', + }, + ], + }, + context: {}, + }); + + await app.db.sync(); + const UserCollection = app.db.getCollection('users'); + const createViewName = app.db.options.schema ? `${app.db.options.schema}.${viewName}` : viewName; + const dropSQL = `DROP VIEW IF EXISTS ${createViewName}`; + await app.db.sequelize.query(dropSQL); + const viewSQL = `CREATE VIEW ${createViewName} AS SELECT * FROM ${UserCollection.quotedTableName()}`; + await app.db.sequelize.query(viewSQL); + + const viewDetailResponse = await agent.resource('dbViews').get({ + filterByTk: viewName, + schema: app.db.options.schema, + }); + + await app.db.getRepository('collections').create({ + values: { + name: collectionName, + view: true, + viewName, + schema: app.db.inDialect('postgres') ? app.db.options.schema || 'public' : undefined, + fields: Object.values(viewDetailResponse.body.data.fields), + }, + context: {}, + }); + + UserCollection.addField('email', { type: 'string' }); + await app.db.sync(); + + await app.db.sequelize.query(dropSQL); + const viewSQL2 = `CREATE VIEW ${createViewName} AS SELECT * FROM ${UserCollection.quotedTableName()}`; + await app.db.sequelize.query(viewSQL2); + + const syncResponse = await agent.resource('mainDataSource').syncFields(); + + expect(syncResponse.status).toEqual(200); + + const viewCollectionModel = await app.db.getRepository('collections').findOne({ + filter: { + name: collectionName, + }, + appends: ['fields'], + }); + + expect(viewCollectionModel.fields.some((field) => field.name === 'email')).toBeTruthy(); + }, + ); + it('should access view collection resource', async () => { const UserCollection = app.db.collection({ name: 'users',