Skip to content

Commit

Permalink
fix(sequelize): get table name dynamically
Browse files Browse the repository at this point in the history
based on name provided in @model decorator or fallback to lowercase
for postgres to match loopback connector's behaviour

Signed-off-by: Shubham P <shubham.prajapat@sourcefuse.com>
  • Loading branch information
shubhamp-sf authored and samarpanB committed Jun 14, 2023
1 parent 311e97f commit 2a7ca80
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 1 deletion.
22 changes: 22 additions & 0 deletions extensions/sequelize/src/__tests__/fixtures/models/test.model.ts
@@ -0,0 +1,22 @@
import {Entity, model} from '@loopback/repository';

export const eventTableName = 'tbl_event';
@model({
name: eventTableName,
})
export class Event extends Entity {
// No properties are needed, This model is just used for testing the table names

constructor(data?: Partial<Event>) {
super(data);
}
}

@model()
export class Box extends Entity {
// No properties are needed, This model is just used for testing the table names

constructor(data?: Partial<Box>) {
super(data);
}
}
Expand Up @@ -3,15 +3,19 @@ import {RestApplication} from '@loopback/rest';
import {
Client,
createRestAppClient,
createStubInstance,
expect,
givenHttpServerConfig,
StubbedInstanceWithSinonAccessor,
TestSandbox,
} from '@loopback/testlab';
import {resolve} from 'path';
import {SequelizeCrudRepository, SequelizeDataSource} from '../../sequelize';
import {SequelizeSandboxApplication} from '../fixtures/application';
import {config as primaryDataSourceConfig} from '../fixtures/datasources/primary.datasource';
import {config as secondaryDataSourceConfig} from '../fixtures/datasources/secondary.datasource';
import {TableInSecondaryDB} from '../fixtures/models';
import {Box, Event, eventTableName} from '../fixtures/models/test.model';
import {UserRepository} from '../fixtures/repositories';

type Entities =
Expand All @@ -29,6 +33,7 @@ describe('Sequelize CRUD Repository (integration)', () => {
let app: SequelizeSandboxApplication;
let userRepo: UserRepository;
let client: Client;
let datasource: StubbedInstanceWithSinonAccessor<SequelizeDataSource>;

beforeEach('reset sandbox', () => sandbox.reset());
beforeEach(getAppAndClient);
Expand Down Expand Up @@ -257,6 +262,23 @@ describe('Sequelize CRUD Repository (integration)', () => {
);
expect(getResponse.body.length).to.be.equal(filter.limit);
});

it('uses table name if explicitly specified in model', async () => {
const repo = new SequelizeCrudRepository(Event, datasource);
expect(repo.getTableName()).to.be.eql(eventTableName);
});

it('uses exact model class name as table name for mysql', async () => {
datasource.stubs.sequelizeConfig = {dialect: 'mysql'};
const repo = new SequelizeCrudRepository(Box, datasource);
expect(repo.getTableName()).to.be.eql(Box.name);
});

it('uses lowercased model class name as table name for postgres', async () => {
datasource.stubs.sequelizeConfig = {dialect: 'postgres'};
const repo = new SequelizeCrudRepository(Box, datasource);
expect(repo.getTableName()).to.be.eql(Box.name.toLowerCase());
});
});

describe('With Relations', () => {
Expand Down Expand Up @@ -655,6 +677,7 @@ describe('Sequelize CRUD Repository (integration)', () => {
await app.start();

userRepo = await app.getRepository(UserRepository);
datasource = createStubInstance(SequelizeDataSource);
client = createRestAppClient(app as RestApplication);
}

Expand Down
28 changes: 27 additions & 1 deletion extensions/sequelize/src/sequelize/sequelize.repository.base.ts
Expand Up @@ -637,7 +637,7 @@ export class SequelizeCrudRepository<
this.getSequelizeModelAttributes(entityClass.definition.properties),
{
timestamps: false,
tableName: entityClass.modelName.toLowerCase(),
tableName: this.getTableName(entityClass),
freezeTableName: true,
},
);
Expand Down Expand Up @@ -717,6 +717,32 @@ export class SequelizeCrudRepository<
return sourceModel;
}

/**
* This function retrieves the table name associated with a given entity class.
* Different loopback connectors have different conventions for picking up table names,
* unless the name is specified in the @model decorator.
*
* The function follows the following cases to determine the table name:
* - It checks if the name property is specified in the @model decorator and uses it. (this takes precedence over all other cases)
* - If the dialect of the dataSource is PostgreSQL, it uses the lowercased version of the model class name.
* - If the dialect is MySQL or any other dialect, it uses the default model class name.
* @param {Entity} entityClass - The entity class for which the table name is being retrieved.
* @returns {string} - The table name associated with the entity class. Which is used when performing the query.
*/
getTableName(entityClass = this.entityClass) {
let tableName = entityClass.name; // model class name

if (entityClass.definition.name !== tableName) {
// name is specified in decorator
tableName = entityClass.definition.name;
} else if (this.dataSource.sequelizeConfig.dialect === 'postgres') {
// postgres is being used and name is not specified in @model decorator
tableName = entityClass.modelName.toLowerCase();
}

return tableName;
}

/**
* Run CREATE TABLE query for the target sequelize model, Useful for quick testing
* @param options Sequelize Sync Options
Expand Down

0 comments on commit 2a7ca80

Please sign in to comment.