mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 10:17:00 +00:00
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('.');
|
||||
|
||||
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('.')];
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user