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:
parent
a1dc139cf4
commit
fdfa79cf8f
66
packages/core/database/src/__tests__/fields/array.test.ts
Normal file
66
packages/core/database/src/__tests__/fields/array.test.ts
Normal 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'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -124,12 +124,13 @@ export default class FilterParser {
|
|||||||
skipPrefix = origins.join('.');
|
skipPrefix = origins.join('.');
|
||||||
|
|
||||||
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
|
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
|
||||||
|
const [fieldName, fullName] = this.getFieldNameFromQueryPath(skipPrefix);
|
||||||
value = opKey(queryValue, {
|
value = opKey(queryValue, {
|
||||||
app: this.context.app,
|
app: this.context.app,
|
||||||
db: this.database,
|
db: this.database,
|
||||||
path: skipPrefix,
|
path: skipPrefix,
|
||||||
fieldName: this.getFieldNameFromQueryPath(skipPrefix),
|
fullName,
|
||||||
|
fieldName,
|
||||||
model: this.model,
|
model: this.model,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -230,14 +231,14 @@ export default class FilterParser {
|
|||||||
private getFieldNameFromQueryPath(queryPath: string) {
|
private getFieldNameFromQueryPath(queryPath: string) {
|
||||||
const paths = queryPath.split('.');
|
const paths = queryPath.split('.');
|
||||||
let fieldName;
|
let fieldName;
|
||||||
|
let fullPaths = [];
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (path.startsWith('$') || !lodash.isNaN(parseInt(path))) {
|
if (path.startsWith('$') || !lodash.isNaN(parseInt(path))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
fullPaths.push(path);
|
||||||
fieldName = path;
|
fieldName = path;
|
||||||
}
|
}
|
||||||
|
return [fieldName, fullPaths.join('.')];
|
||||||
return fieldName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Op, Sequelize } from 'sequelize';
|
import { Op, Sequelize } from 'sequelize';
|
||||||
import { isPg, isMySQL } from './utils';
|
import { isMySQL, isPg } from './utils';
|
||||||
|
|
||||||
const getFieldName = (ctx) => {
|
const getFieldName = (ctx) => {
|
||||||
const fieldName = ctx.fieldName;
|
const fieldName = ctx.fieldName;
|
||||||
@ -13,10 +13,11 @@ const escape = (value, ctx) => {
|
|||||||
|
|
||||||
const sqliteExistQuery = (value, ctx) => {
|
const sqliteExistQuery = (value, ctx) => {
|
||||||
const fieldName = getFieldName(ctx);
|
const fieldName = getFieldName(ctx);
|
||||||
|
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
|
||||||
|
|
||||||
const sqlArray = `(${value.map((v) => `'${v}'`).join(', ')})`;
|
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;
|
return subQuery;
|
||||||
};
|
};
|
||||||
@ -53,7 +54,8 @@ export default {
|
|||||||
value = escape(JSON.stringify(value.sort()), ctx);
|
value = escape(JSON.stringify(value.sort()), ctx);
|
||||||
|
|
||||||
if (isMySQL(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 {
|
return {
|
||||||
@ -66,11 +68,13 @@ export default {
|
|||||||
value = escape(JSON.stringify(value), ctx);
|
value = escape(JSON.stringify(value), ctx);
|
||||||
|
|
||||||
if (isPg(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)) {
|
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 {
|
return {
|
||||||
[Op.ne]: Sequelize.literal(`json(${value})`),
|
[Op.ne]: Sequelize.literal(`json(${value})`),
|
||||||
@ -81,8 +85,9 @@ export default {
|
|||||||
const fieldName = getFieldName(ctx);
|
const fieldName = getFieldName(ctx);
|
||||||
|
|
||||||
if (isPg(ctx)) {
|
if (isPg(ctx)) {
|
||||||
|
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
|
||||||
return Sequelize.literal(
|
return Sequelize.literal(
|
||||||
`${fieldName} ?| ${escape(
|
`${name} ?| ${escape(
|
||||||
value.map((i) => `${i}`),
|
value.map((i) => `${i}`),
|
||||||
ctx,
|
ctx,
|
||||||
)}`,
|
)}`,
|
||||||
@ -91,8 +96,8 @@ export default {
|
|||||||
|
|
||||||
if (isMySQL(ctx)) {
|
if (isMySQL(ctx)) {
|
||||||
value = escape(JSON.stringify(value), ctx);
|
value = escape(JSON.stringify(value), ctx);
|
||||||
|
const name = ctx.fullName === fieldName ? `\`${ctx.model.name}\`.\`${fieldName}\`` : `\`${fieldName}\``;
|
||||||
return Sequelize.literal(`JSON_OVERLAPS(${fieldName}, ${value})`);
|
return Sequelize.literal(`JSON_OVERLAPS(${name}, ${value})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const subQuery = sqliteExistQuery(value, ctx);
|
const subQuery = sqliteExistQuery(value, ctx);
|
||||||
@ -105,9 +110,10 @@ export default {
|
|||||||
|
|
||||||
if (isPg(ctx)) {
|
if (isPg(ctx)) {
|
||||||
const fieldName = getFieldName(ctx);
|
const fieldName = getFieldName(ctx);
|
||||||
|
const name = ctx.fullName === fieldName ? `"${ctx.model.name}"."${fieldName}"` : `"${fieldName}"`;
|
||||||
// pg single quote
|
// pg single quote
|
||||||
where = Sequelize.literal(
|
where = Sequelize.literal(
|
||||||
`not (${fieldName} ?| ${escape(
|
`not (${name} ?| ${escape(
|
||||||
value.map((i) => `${i}`),
|
value.map((i) => `${i}`),
|
||||||
ctx,
|
ctx,
|
||||||
)})`,
|
)})`,
|
||||||
@ -115,7 +121,8 @@ export default {
|
|||||||
} else if (isMySQL(ctx)) {
|
} else if (isMySQL(ctx)) {
|
||||||
const fieldName = getFieldName(ctx);
|
const fieldName = getFieldName(ctx);
|
||||||
value = escape(JSON.stringify(value), 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 {
|
} else {
|
||||||
const subQuery = sqliteExistQuery(value, ctx);
|
const subQuery = sqliteExistQuery(value, ctx);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user