fix: mysql column in where clause is ambiguous (#756)

* fix: mysql column in where clause is ambiguous

* feat: add test case

* fix: sqlite error
This commit is contained in:
chenos 2022-08-20 23:24:02 +08:00 committed by GitHub
parent a1dc139cf4
commit fdfa79cf8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 15 deletions

View File

@ -0,0 +1,66 @@
import { mockDatabase } from '../';
import { Database } from '../../database';
describe('array field', () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase();
});
afterEach(async () => {
await db.close();
});
it('should not be ambiguous', async () => {
db.collection({
name: 'a',
fields: [
{
type: 'array',
name: 'arr',
},
{
type: 'belongsToMany',
name: 'b',
target: 'b',
},
],
});
db.collection({
name: 'b',
fields: [
{
type: 'array',
name: 'arr',
},
],
});
await db.sync();
const repository = db.getRepository('a');
await repository.find({
filter: {
'arr.$match': ['aa'],
},
appends: ['b'],
});
await repository.find({
filter: {
'arr.$notMatch': ['aa'],
},
appends: ['b'],
});
await repository.find({
filter: {
'arr.$anyOf': ['aa'],
},
appends: ['b'],
});
await repository.find({
filter: {
'arr.$noneOf': ['aa'],
},
appends: ['b'],
});
});
});

View File

@ -124,12 +124,13 @@ export default class FilterParser {
skipPrefix = origins.join('.');
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
const [fieldName, fullName] = this.getFieldNameFromQueryPath(skipPrefix);
value = opKey(queryValue, {
app: this.context.app,
db: this.database,
path: skipPrefix,
fieldName: this.getFieldNameFromQueryPath(skipPrefix),
fullName,
fieldName,
model: this.model,
});
break;
@ -230,14 +231,14 @@ export default class FilterParser {
private getFieldNameFromQueryPath(queryPath: string) {
const paths = queryPath.split('.');
let fieldName;
let fullPaths = [];
for (const path of paths) {
if (path.startsWith('$') || !lodash.isNaN(parseInt(path))) {
continue;
}
fullPaths.push(path);
fieldName = path;
}
return fieldName;
return [fieldName, fullPaths.join('.')];
}
}

View File

@ -1,5 +1,5 @@
import { Op, Sequelize } from 'sequelize';
import { isPg, isMySQL } from './utils';
import { isMySQL, isPg } from './utils';
const getFieldName = (ctx) => {
const fieldName = ctx.fieldName;
@ -13,10 +13,11 @@ const escape = (value, ctx) => {
const sqliteExistQuery = (value, ctx) => {
const fieldName = getFieldName(ctx);
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
const sqlArray = `(${value.map((v) => `'${v}'`).join(', ')})`;
const subQuery = `exists (select * from json_each(${fieldName}) where json_each.value in ${sqlArray})`;
const subQuery = `exists (select * from json_each(${name}) where json_each.value in ${sqlArray})`;
return subQuery;
};
@ -53,7 +54,8 @@ export default {
value = escape(JSON.stringify(value.sort()), ctx);
if (isMySQL(ctx)) {
return Sequelize.literal(`JSON_CONTAINS(${fieldName}, ${value}) AND JSON_CONTAINS(${value}, ${fieldName})`);
const name = ctx.fullName === fieldName ? `\`${ctx.model.name}\`.\`${fieldName}\`` : `\`${fieldName}\``;
return Sequelize.literal(`JSON_CONTAINS(${name}, ${value}) AND JSON_CONTAINS(${value}, ${name})`);
}
return {
@ -66,11 +68,13 @@ export default {
value = escape(JSON.stringify(value), ctx);
if (isPg(ctx)) {
return Sequelize.literal(`not (${fieldName} <@ ${value}::JSONB and ${fieldName} @> ${value}::JSONB)`);
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
return Sequelize.literal(`not (${name} <@ ${value}::JSONB and ${name} @> ${value}::JSONB)`);
}
if (isMySQL(ctx)) {
return Sequelize.literal(`not (JSON_CONTAINS(${fieldName}, ${value}) AND JSON_CONTAINS(${value}, ${fieldName}))`);
const name = ctx.fullName === fieldName ? `\`${ctx.model.name}\`.\`${fieldName}\`` : `\`${fieldName}\``;
return Sequelize.literal(`not (JSON_CONTAINS(${name}, ${value}) AND JSON_CONTAINS(${value}, ${name}))`);
}
return {
[Op.ne]: Sequelize.literal(`json(${value})`),
@ -81,8 +85,9 @@ export default {
const fieldName = getFieldName(ctx);
if (isPg(ctx)) {
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
return Sequelize.literal(
`${fieldName} ?| ${escape(
`${name} ?| ${escape(
value.map((i) => `${i}`),
ctx,
)}`,
@ -91,8 +96,8 @@ export default {
if (isMySQL(ctx)) {
value = escape(JSON.stringify(value), ctx);
return Sequelize.literal(`JSON_OVERLAPS(${fieldName}, ${value})`);
const name = ctx.fullName === fieldName ? `\`${ctx.model.name}\`.\`${fieldName}\`` : `\`${fieldName}\``;
return Sequelize.literal(`JSON_OVERLAPS(${name}, ${value})`);
}
const subQuery = sqliteExistQuery(value, ctx);
@ -105,9 +110,10 @@ export default {
if (isPg(ctx)) {
const fieldName = getFieldName(ctx);
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
// pg single quote
where = Sequelize.literal(
`not (${fieldName} ?| ${escape(
`not (${name} ?| ${escape(
value.map((i) => `${i}`),
ctx,
)})`,
@ -115,7 +121,8 @@ export default {
} else if (isMySQL(ctx)) {
const fieldName = getFieldName(ctx);
value = escape(JSON.stringify(value), ctx);
where = Sequelize.literal(`NOT JSON_OVERLAPS(${fieldName}, ${value})`);
const name = ctx.fullName === fieldName ? `\`${ctx.model.name}\`.\`${fieldName}\`` : `\`${fieldName}\``;
where = Sequelize.literal(`NOT JSON_OVERLAPS(${name}, ${value})`);
} else {
const subQuery = sqliteExistQuery(value, ctx);