Merge branch 'main' into next

This commit is contained in:
nocobase[bot] 2025-01-25 14:20:19 +00:00
commit de782020d2
2 changed files with 106 additions and 37 deletions

View File

@ -40,6 +40,25 @@ describe('uuid field', () => {
expect(item['uuid']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
});
it('should filter uuid field', async () => {
const Test = db.collection({
name: 'tests',
fields: [{ type: 'uuid', name: 'uuid' }],
});
await Test.sync();
const item1 = await Test.model.create();
const result = await Test.repository.find({
filter: {
uuid: { $includes: [item1['uuid']] },
},
});
expect(result.length).toBe(1);
});
it('should set autofill attribute', async () => {
const Test = db.collection({
name: 'tests',

View File

@ -7,13 +7,75 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Op } from 'sequelize';
import { Op, cast, where, col, Sequelize } from 'sequelize';
import { isPg } from './utils';
function escapeLike(value: string) {
return value.replace(/[_%]/g, '\\$&');
}
const getFieldName = (ctx) => {
const fullNameSplit = ctx.fullName.split('.');
const fieldName = ctx.fieldName;
let columnName = fieldName;
const associationPath = [];
if (fullNameSplit.length > 1) {
for (let i = 0; i < fullNameSplit.length - 1; i++) {
associationPath.push(fullNameSplit[i]);
}
}
const getModelFromAssociationPath = () => {
let model = ctx.model;
for (const association of associationPath) {
model = model.associations[association].target;
}
return model;
};
const model = getModelFromAssociationPath();
let columnPrefix = model.name;
if (model.rawAttributes[fieldName]) {
columnName = model.rawAttributes[fieldName].field || fieldName;
}
if (associationPath.length > 0) {
const association = associationPath.join('->');
columnPrefix = association;
}
columnName = `${columnPrefix}.${columnName}`;
return columnName;
};
// Helper function to handle field casting for PostgreSQL
function getFieldExpression(value, ctx, operator) {
if (isPg(ctx)) {
const fieldName = getFieldName(ctx);
const queryInterface = ctx.db.sequelize.getQueryInterface();
const quotedField = queryInterface.quoteIdentifiers(fieldName);
return Sequelize.literal(`CAST(${quotedField} AS TEXT) ${operator} ${ctx.db.sequelize.escape(value)}`);
}
// For MySQL and other databases, return the operator directly
const op =
operator === 'LIKE'
? Op.like
: operator === 'NOT LIKE'
? Op.notLike
: operator === 'ILIKE'
? Op.like
: operator === 'NOT ILIKE'
? Op.notLike
: Op.like;
return { [op]: value };
}
export default {
$includes(value, ctx) {
if (value === null) {
@ -22,18 +84,16 @@ export default {
};
}
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(item)}%`,
}));
const conditions = value.map((item) =>
getFieldExpression(`%${escapeLike(item)}%`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE'),
);
return {
[Op.or]: conditions,
};
}
return {
[isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(value)}%`,
};
return getFieldExpression(`%${escapeLike(value)}%`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE');
},
$notIncludes(value, ctx) {
@ -43,81 +103,71 @@ export default {
};
}
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.notILike : Op.notLike]: `%${escapeLike(item)}%`,
}));
const conditions = value.map((item) =>
getFieldExpression(`%${escapeLike(item)}%`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE'),
);
return {
[Op.and]: conditions,
};
}
return {
[isPg(ctx) ? Op.notILike : Op.notLike]: `%${escapeLike(value)}%`,
};
return getFieldExpression(`%${escapeLike(value)}%`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE');
},
$startsWith(value, ctx) {
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.iLike : Op.like]: `${escapeLike(item)}%`,
}));
const conditions = value.map((item) =>
getFieldExpression(`${escapeLike(item)}%`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE'),
);
return {
[Op.or]: conditions,
};
}
return {
[isPg(ctx) ? Op.iLike : Op.like]: `${escapeLike(value)}%`,
};
return getFieldExpression(`${escapeLike(value)}%`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE');
},
$notStartsWith(value, ctx) {
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.notILike : Op.notLike]: `${escapeLike(item)}%`,
}));
const conditions = value.map((item) =>
getFieldExpression(`${escapeLike(item)}%`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE'),
);
return {
[Op.and]: conditions,
};
}
return {
[isPg(ctx) ? Op.notILike : Op.notLike]: `${escapeLike(value)}%`,
};
return getFieldExpression(`${escapeLike(value)}%`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE');
},
$endWith(value, ctx) {
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(item)}`,
}));
const conditions = value.map((item) =>
getFieldExpression(`%${escapeLike(item)}`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE'),
);
return {
[Op.or]: conditions,
};
}
return {
[isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(value)}`,
};
return getFieldExpression(`%${escapeLike(value)}`, ctx, isPg(ctx) ? 'ILIKE' : 'LIKE');
},
$notEndWith(value, ctx) {
if (Array.isArray(value)) {
const conditions = value.map((item) => ({
[isPg(ctx) ? Op.notILike : Op.notLike]: `%${escapeLike(item)}`,
}));
const conditions = value.map((item) =>
getFieldExpression(`%${escapeLike(item)}`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE'),
);
return {
[Op.and]: conditions,
};
}
return {
[isPg(ctx) ? Op.notILike : Op.notLike]: `%${escapeLike(value)}`,
};
return getFieldExpression(`%${escapeLike(value)}`, ctx, isPg(ctx) ? 'NOT ILIKE' : 'NOT LIKE');
},
} as Record<string, any>;