Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -686,21 +686,52 @@ describe('DefaultCrudRepository', () => {
expect(ok).to.be.true();
});

it('implements Repository.execute()', async () => {
// Dummy implementation for execute() in datasource
ds.execute = (...args: unknown[]) => {
return Promise.resolve(args);
};
const repo = new DefaultCrudRepository(Note, ds);
const result = await repo.execute('query', ['arg']);
expect(result).to.deepEqual(['query', ['arg'], undefined]);
});
describe('Repository.execute()', () => {
beforeEach(() => {
// Dummy implementation for execute() in datasource
ds.execute = (...args: unknown[]) => {
return Promise.resolve(args);
};
});

it(`throws error when execute() not implemented by ds connector`, async () => {
const repo = new DefaultCrudRepository(Note, ds);
await expect(repo.execute('query', [])).to.be.rejectedWith(
'execute() must be implemented by the connector',
);
it('implements SQL variant', async () => {
const repo = new DefaultCrudRepository(Note, ds);
const result = await repo.execute('query', ['arg']);
expect(result).to.deepEqual(['query', ['arg']]);
});

it('implements MongoDB variant', async () => {
const repo = new DefaultCrudRepository(Note, ds);
const result = await repo.execute('MyCollection', 'aggregate', [
{$unwind: '$data'},
{$out: 'tempData'},
]);
expect(result).to.deepEqual([
'MyCollection',
'aggregate',
[{$unwind: '$data'}, {$out: 'tempData'}],
]);
});

it('implements a generic variant', async () => {
const repo = new DefaultCrudRepository(Note, ds);
const command = {
query: 'MATCH (u:User {email: {email}}) RETURN u',
params: {
email: 'alice@example.com',
},
};
const result = await repo.execute(command);
expect(result).to.deepEqual([command]);
});

it(`throws error when execute() not implemented by ds connector`, async () => {
delete ds.execute;
const repo = new DefaultCrudRepository(Note, ds);
await expect(repo.execute('query', [])).to.be.rejectedWith(
'execute() must be implemented by the connector',
);
});
});

it('has the property inclusionResolvers', () => {
Expand Down
89 changes: 86 additions & 3 deletions packages/repository/src/repositories/legacy-juggler-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,12 +573,95 @@ export class DefaultCrudRepository<
return ensurePromise(this.modelClass.exists(id, options));
}

async execute(
/**
* Execute a SQL command.
*
* **WARNING:** In general, it is always better to perform database actions
* through repository methods. Directly executing SQL may lead to unexpected
* results, corrupted data, security vulnerabilities and other issues.
*
* @example
*
* ```ts
* // MySQL
* const result = await repo.execute(
* 'SELECT * FROM Products WHERE size > ?',
* [42]
* );
*
* // PostgreSQL
* const result = await repo.execute(
* 'SELECT * FROM Products WHERE size > $1',
* [42]
* );
* ```
*
* @param command A parameterized SQL command or query.
* Check your database documentation for information on which characters to
* use as parameter placeholders.
* @param parameters List of parameter values to use.
* @param options Additional options, for example `transaction`.
* @returns A promise which resolves to the command output as returned by the
* database driver. The output type (data structure) is database specific and
* often depends on the command executed.
*/
execute(
command: Command,
parameters: NamedParameters | PositionalParameters,
options?: Options,
): Promise<AnyObject> {
return ensurePromise(this.dataSource.execute(command, parameters, options));
): Promise<AnyObject>;

/**
* Execute a MongoDB command.
*
* **WARNING:** In general, it is always better to perform database actions
* through repository methods. Directly executing MongoDB commands may lead
* to unexpected results and other issues.
*
* @example
*
* ```ts
* const result = await repo.execute('MyCollection', 'aggregate', [
* {$lookup: {
* // ...
* }},
* {$unwind: '$data'},
* {$out: 'tempData'}
* ]);
* ```
*
* @param collectionName The name of the collection to execute the command on.
* @param command The command name. See
* [Collection API docs](http://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html)
* for the list of commands supported by the MongoDB client.
* @param parameters Command parameters (arguments), as described in MongoDB API
* docs for individual collection methods.
* @returns A promise which resolves to the command output as returned by the
* database driver.
*/
execute(
collectionName: string,
command: string,
...parameters: PositionalParameters
): Promise<AnyObject>;

/**
* Execute a raw database command using a connector that's not described
* by LoopBack's `execute` API yet.
*
* **WARNING:** In general, it is always better to perform database actions
* through repository methods. Directly executing database commands may lead
* to unexpected results and other issues.
*
* @param args Command and parameters, please consult your connector's
* documentation to learn about supported commands and their parameters.
* @returns A promise which resolves to the command output as returned by the
* database driver.
*/
execute(...args: PositionalParameters): Promise<AnyObject>;

async execute(...args: PositionalParameters): Promise<AnyObject> {
return ensurePromise(this.dataSource.execute(...args));
}

protected toEntity<R extends T>(model: juggler.PersistedModel): R {
Expand Down