Browse Source

Merge pull request #8571 from nocodb/nc-refactor/meta-service

refactor: Meta service
pull/8592/head
Pranav C 1 month ago committed by GitHub
parent
commit
fd501ed758
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 51
      packages/nocodb/src/db/CustomKnex.ts
  2. 5
      packages/nocodb/src/filters/global-exception/global-exception.filter.ts
  3. 11
      packages/nocodb/src/helpers/catchError.ts
  4. 629
      packages/nocodb/src/meta/meta.service.ts

51
packages/nocodb/src/db/CustomKnex.ts

@ -492,23 +492,27 @@ const appendWhereCondition = function (
return knexRef;
};
type XcConditionObjVal = {
[key in 'eq' | 'neq' | 'lt' | 'gt' | 'ge' | 'le' | 'like' | 'nlike']:
| string
| number
| any;
};
interface XcXonditionObj {
_or: XcXonditionObj[];
_and: XcXonditionObj[];
_not: XcXonditionObj;
[key: string]:
| XcXonditionObj
| XcXonditionObj[]
| XcConditionObjVal
| XcConditionObjVal[];
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> &
U[keyof U];
export type ConditionVal = AtLeastOne<{
eq: string | number | boolean | Date;
neq: string | number | boolean | Date;
lt: number | string | Date;
gt: number | string | Date;
ge: number | string | Date;
le: number | string | Date;
like: string;
nlike: string;
}>;
export interface Condition {
_or?: Condition[];
_and?: Condition[];
_not?: Condition;
[key: string]: ConditionVal | Condition | Condition[];
}
declare module 'knex' {
@ -527,7 +531,7 @@ declare module 'knex' {
): Knex.QueryBuilder<TRecord, TResult>;
condition<TRecord, TResult>(
conditionObj: XcXonditionObj,
conditionObj: Condition,
columnAliases?: {
[columnAlias: string]: string;
},
@ -542,7 +546,7 @@ declare module 'knex' {
): Knex.QueryBuilder<TRecord, TResult>;
conditionGraph<TRecord, TResult>(condition: {
condition: XcXonditionObj;
condition: Condition;
models: { [key: string]: BaseModelSql };
}): Knex.QueryBuilder<TRecord, TResult>;
@ -552,6 +556,8 @@ declare module 'knex' {
[columnAlias: string]: string;
},
): Knex.QueryBuilder<TRecord, TResult>;
hasWhere(): boolean;
}
}
}
@ -1274,8 +1280,15 @@ function parseNestedConditionv2(obj, qb, pKey?, table?, tableAlias?) {
return qb;
}
// Conditionv2
// extend the knex query builder with a method to check if a where clause exists
knex.QueryBuilder.extend('hasWhere', function () {
// Inspect the _statements array for 'where' clauses
return (
this as unknown as { _statements: { grouping: string }[] }
)._statements.some((statement) => statement.grouping === 'where') as any;
});
// Conditionv2
/**
* Append custom where condition(nested object) to knex query builder
*/

5
packages/nocodb/src/filters/global-exception/global-exception.filter.ts

@ -41,9 +41,8 @@ export class GlobalExceptionFilter implements ExceptionFilter {
}
// try to extract db error for unknown errors
const dbError = !(exception instanceof NcBaseError)
? extractDBError(exception)
: null;
const dbError =
exception instanceof NcBaseError ? null : extractDBError(exception);
// skip unnecessary error logging
if (

11
packages/nocodb/src/helpers/catchError.ts

@ -415,6 +415,13 @@ export class NotFound extends NcBaseError {}
export class SsoError extends NcBaseError {}
export class MetaError extends NcBaseError {
constructor(param: { message: string; sql: string }) {
super(param.message);
Object.assign(this, param);
}
}
export class ExternalError extends NcBaseError {
constructor(error: Error) {
super(error.message);
@ -747,4 +754,8 @@ export class NcError {
`Email domain ${domain} is not allowed for this organization`,
);
}
static metaError(param: { message: string; sql: string }) {
throw new MetaError(param);
}
}

629
packages/nocodb/src/meta/meta.service.ts

@ -6,20 +6,17 @@ import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import type * as knex from 'knex';
import type { Knex } from 'knex';
import type { Condition } from '~/db/CustomKnex';
import XcMigrationSource from '~/meta/migrations/XcMigrationSource';
import XcMigrationSourcev2 from '~/meta/migrations/XcMigrationSourcev2';
import { XKnex } from '~/db/CustomKnex';
import { NcConfig } from '~/utils/nc-config';
import { MetaTable } from '~/utils/globals';
import { NcError } from '~/helpers/catchError';
dayjs.extend(utc);
dayjs.extend(timezone);
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
// todo: tobe fixed
const META_TABLES = [];
const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14);
@Injectable()
@ -56,6 +53,14 @@ export class MetaService {
return this.knexConnection;
}
/***
* Get single record from meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param idOrCondition - If string, will get the record with the given id. If object, will get the record with the given condition.
* @param fields - Fields to be selected
*/
public async metaGet(
base_id: string,
dbAlias: string,
@ -96,6 +101,14 @@ export class MetaService {
return query.first();
}
/***
* Insert record into meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param data - Data to be inserted
* @param ignoreIdGeneration - If true, will not generate id for the record
*/
public async metaInsert2(
base_id: string,
source_id: string,
@ -119,6 +132,15 @@ export class MetaService {
});
return insertObj;
}
/***
* Insert multiple records into meta data
* @param base_id - Base id
* @param source_id - Source id
* @param target - Table name
* @param data - Data to be inserted
* @param ignoreIdGeneration - If true, will not generate id for the record
*/
public async bulkMetaInsert(
base_id: string,
source_id: string,
@ -156,120 +178,67 @@ export class MetaService {
return insertObj;
}
/***
* Generate nanoid for the given target
* @param target - Table name
* @returns {string} - Generated nanoid
* */
public async genNanoid(target: string) {
let prefix;
switch (target) {
case MetaTable.PROJECT:
prefix = 'p';
break;
case MetaTable.BASES:
prefix = 'b';
break;
case MetaTable.MODELS:
prefix = 'm';
break;
case MetaTable.COLUMNS:
prefix = 'c';
break;
case MetaTable.COL_RELATIONS:
prefix = 'l';
break;
case MetaTable.COL_SELECT_OPTIONS:
prefix = 's';
break;
case MetaTable.COL_LOOKUP:
prefix = 'lk';
break;
case MetaTable.COL_ROLLUP:
prefix = 'rl';
break;
case MetaTable.COL_FORMULA:
prefix = 'f';
break;
case MetaTable.FILTER_EXP:
prefix = 'fi';
break;
case MetaTable.SORT:
prefix = 'so';
break;
case MetaTable.SHARED_VIEWS:
prefix = 'sv';
break;
case MetaTable.ACL:
prefix = 'ac';
break;
case MetaTable.FORM_VIEW:
prefix = 'fv';
break;
case MetaTable.FORM_VIEW_COLUMNS:
prefix = 'fvc';
break;
case MetaTable.GALLERY_VIEW:
prefix = 'gv';
break;
case MetaTable.GALLERY_VIEW_COLUMNS:
prefix = 'gvc';
break;
case MetaTable.KANBAN_VIEW:
prefix = 'kv';
break;
case MetaTable.KANBAN_VIEW_COLUMNS:
prefix = 'kvc';
break;
case MetaTable.CALENDAR_VIEW:
prefix = 'cv';
break;
case MetaTable.CALENDAR_VIEW_COLUMNS:
prefix = 'cvc';
break;
case MetaTable.CALENDAR_VIEW_RANGE:
prefix = 'cvr';
break;
case MetaTable.USERS:
prefix = 'us';
break;
case MetaTable.ORGS_OLD:
prefix = 'org';
break;
case MetaTable.TEAMS:
prefix = 'tm';
break;
case MetaTable.VIEWS:
prefix = 'vw';
break;
case MetaTable.HOOKS:
prefix = 'hk';
break;
case MetaTable.HOOK_LOGS:
prefix = 'hkl';
break;
case MetaTable.AUDIT:
prefix = 'adt';
break;
case MetaTable.API_TOKENS:
prefix = 'tkn';
break;
case MetaTable.EXTENSIONS:
prefix = 'ext';
break;
case MetaTable.COMMENTS:
prefix = 'com';
break;
case MetaTable.COMMENTS_REACTIONS:
prefix = 'cre';
break;
case MetaTable.USER_COMMENTS_NOTIFICATIONS_PREFERENCE:
prefix = 'cnp';
break;
default:
prefix = 'nc';
break;
}
const prefixMap: { [key: string]: string } = {
[MetaTable.PROJECT]: 'p',
[MetaTable.BASES]: 'b',
[MetaTable.MODELS]: 'm',
[MetaTable.COLUMNS]: 'c',
[MetaTable.COL_RELATIONS]: 'l',
[MetaTable.COL_SELECT_OPTIONS]: 's',
[MetaTable.COL_LOOKUP]: 'lk',
[MetaTable.COL_ROLLUP]: 'rl',
[MetaTable.COL_FORMULA]: 'f',
[MetaTable.FILTER_EXP]: 'fi',
[MetaTable.SORT]: 'so',
[MetaTable.SHARED_VIEWS]: 'sv',
[MetaTable.ACL]: 'ac',
[MetaTable.FORM_VIEW]: 'fv',
[MetaTable.FORM_VIEW_COLUMNS]: 'fvc',
[MetaTable.GALLERY_VIEW]: 'gv',
[MetaTable.GALLERY_VIEW_COLUMNS]: 'gvc',
[MetaTable.KANBAN_VIEW]: 'kv',
[MetaTable.KANBAN_VIEW_COLUMNS]: 'kvc',
[MetaTable.CALENDAR_VIEW]: 'cv',
[MetaTable.CALENDAR_VIEW_COLUMNS]: 'cvc',
[MetaTable.CALENDAR_VIEW_RANGE]: 'cvr',
[MetaTable.USERS]: 'us',
[MetaTable.ORGS_OLD]: 'org',
[MetaTable.TEAMS]: 'tm',
[MetaTable.VIEWS]: 'vw',
[MetaTable.HOOKS]: 'hk',
[MetaTable.HOOK_LOGS]: 'hkl',
[MetaTable.AUDIT]: 'adt',
[MetaTable.API_TOKENS]: 'tkn',
[MetaTable.EXTENSIONS]: 'ext',
[MetaTable.COMMENTS]: 'com',
[MetaTable.COMMENTS_REACTIONS]: 'cre',
[MetaTable.USER_COMMENTS_NOTIFICATIONS_PREFERENCE]: 'cnp',
};
const prefix = prefixMap[target] || 'nc';
// using nanoid to avoid collision with existing ids when duplicating
return `${prefix}${nanoidv2()}`;
}
/***
* Get paginated list of meta data
* @param baseId - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param args.condition - Condition to be applied
* @param args.limit - Limit of records
* @param args.offset - Offset of records
* @param args.xcCondition - Additional nested or complex condition to be added to the query.
* @param args.fields - Fields to be selected
* @param args.sort - Sort field and direction
* @returns {Promise<{list: any[]; count: number}>} - List of records and count
* */
public async metaPaginatedList(
baseId: string,
dbAlias: string,
@ -278,7 +247,7 @@ export class MetaService {
condition?: { [key: string]: any };
limit?: number;
offset?: number;
xcCondition?;
xcCondition?: Condition;
fields?: string[];
sort?: { field: string; desc?: boolean };
},
@ -327,50 +296,22 @@ export class MetaService {
// todo: need to fix
private trx: Knex.Transaction;
// constructor(app: Noco, config: NcConfig, trx = null) {
// super(app, config);
//
// if (this.config?.meta?.db) {
// this.connection = trx || XKnex(this.config?.meta?.db);
// } else {
// let dbIndex = this.config.envs?.[this.config.workingEnv]?.db.findIndex(
// (c) => c.meta.dbAlias === this.config?.auth?.jwt?.dbAlias
// );
// dbIndex = dbIndex === -1 ? 0 : dbIndex;
// this.connection = XKnex(
// this.config.envs?.[this.config.workingEnv]?.db[dbIndex] as any
// );
// }
// this.trx = trx;
// NcConnectionMgr.setXcMeta(this);
// }
// public get knexConnection(): XKnex {
// return (this.trx || this.connection) as any;
// }
// public updateKnex(connectionConfig): void {
// this.connection = XKnex(connectionConfig);
// }
// public async metaInit(): Promise<boolean> {
// await this.connection.migrate.latest({
// migrationSource: new XcMigrationSource(),
// tableName: 'xc_knex_migrations',
// });
// await this.connection.migrate.latest({
// migrationSource: new XcMigrationSourcev2(),
// tableName: 'xc_knex_migrationsv2',
// });
// return true;
// }
/***
* Delete meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param idOrCondition - If string, will delete the record with the given id. If object, will delete the record with the given condition.
* @param xcCondition - Additional nested or complex condition to be added to the query.
* @param force - If true, will not check if a condition is present in the query builder and will execute the query as is.
*/
public async metaDelete(
base_id: string,
dbAlias: string,
target: string,
idOrCondition: string | { [p: string]: any },
xcCondition?,
xcCondition?: Condition,
force = false,
): Promise<void> {
const query = this.knexConnection(target);
@ -391,16 +332,30 @@ export class MetaService {
query.condition(xcCondition, {});
}
// Check if a condition is present in the query builder and throw an error if not.
if (!force) {
this.checkConditionPresent(query, 'delete');
}
return query.del();
}
/***
* Get meta data
* @param base_id - Base id
* @param sourceId - Source id
* @param target - Table name
* @param idOrCondition - If string, will get the record with the given id. If object, will get the record with the given condition.
* @param fields - Fields to be selected
* @param xcCondition - Additional nested or complex condition to be added to the query.
*/
public async metaGet2(
base_id: string,
sourceId: string,
target: string,
idOrCondition: string | { [p: string]: any },
fields?: string[],
xcCondition?,
xcCondition?: Condition,
): Promise<any> {
const query = this.knexConnection(target);
@ -431,6 +386,12 @@ export class MetaService {
return query.first();
}
/***
* Get order value for the next record
* @param target - Table name
* @param condition - Condition to be applied
* @returns {Promise<number>} - Order value
* */
public async metaGetNextOrder(
target: string,
condition: { [key: string]: any },
@ -466,7 +427,7 @@ export class MetaService {
condition?: { [p: string]: any };
limit?: number;
offset?: number;
xcCondition?;
xcCondition?: Condition;
fields?: string[];
orderBy?: { [key: string]: 'asc' | 'desc' };
},
@ -505,6 +466,19 @@ export class MetaService {
return query;
}
/***
* Get list of meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param args.condition - Condition to be applied
* @param args.limit - Limit of records
* @param args.offset - Offset of records
* @param args.xcCondition - Additional nested or complex condition to be added to the query.
* @param args.fields - Fields to be selected
* @param args.orderBy - Order by fields
* @returns {Promise<any[]>} - List of records
* */
public async metaList2(
base_id: string,
dbAlias: string,
@ -513,7 +487,7 @@ export class MetaService {
condition?: { [p: string]: any };
limit?: number;
offset?: number;
xcCondition?;
xcCondition?: Condition;
fields?: string[];
orderBy?: { [key: string]: 'asc' | 'desc' };
},
@ -552,13 +526,23 @@ export class MetaService {
return query;
}
/***
* Get count of meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param args.condition - Condition to be applied
* @param args.xcCondition - Additional nested or complex condition to be added to the query.
* @param args.aggField - Field to be aggregated
* @returns {Promise<number>} - Count of records
* */
public async metaCount(
base_id: string,
dbAlias: string,
target: string,
args?: {
condition?: { [p: string]: any };
xcCondition?;
xcCondition?: Condition;
aggField?: string;
},
): Promise<number> {
@ -584,13 +568,24 @@ export class MetaService {
return +(await query)?.['count'] || 0;
}
/***
* Update meta data
* @param base_id - Base id
* @param dbAlias - Database alias
* @param target - Table name
* @param data - Data to be updated
* @param idOrCondition - If string, will update the record with the given id. If object, will update the record with the given condition.
* @param xcCondition - Additional nested or complex condition to be added to the query.
* @param force - If true, will not check if a condition is present in the query builder and will execute the query as is.
*/
public async metaUpdate(
base_id: string,
dbAlias: string,
target: string,
data: any,
idOrCondition?: string | { [p: string]: any },
xcCondition?,
xcCondition?: Condition,
force = false,
): Promise<any> {
const query = this.knexConnection(target);
if (base_id !== null && base_id !== undefined) {
@ -612,34 +607,12 @@ export class MetaService {
query.condition(xcCondition);
}
return await query;
}
public async metaDeleteAll(
_project_id: string,
_dbAlias: string,
): Promise<void> {
// await this.knexConnection..dropTableIfExists('nc_roles').;
// await this.knexConnection.schema.dropTableIfExists('nc_store').;
// await this.knexConnection.schema.dropTableIfExists('nc_hooks').;
// await this.knexConnection.schema.dropTableIfExists('nc_cron').;
// await this.knexConnection.schema.dropTableIfExists('nc_acl').;
}
public async isMetaDataExists(
base_id: string,
dbAlias: string,
): Promise<boolean> {
const query = this.knexConnection('nc_models');
if (base_id !== null && base_id !== undefined) {
query.where('base_id', base_id);
}
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
// Check if a condition is present in the query builder and throw an error if not.
if (!force) {
this.checkConditionPresent(query, 'update');
}
const data = await query.first();
return !!data;
return await query;
}
async commit() {
@ -669,78 +642,19 @@ export class MetaService {
return new MetaService(this.config, trx);
}
async metaReset(
base_id: string,
dbAlias: string,
apiType?: string,
): Promise<void> {
// const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => {
// return d.meta.dbAlias === dbAlias;
// })?.meta?.api?.type;
if (apiType) {
await Promise.all(
META_TABLES?.[apiType]?.map((table) => {
return (async () => {
try {
await this.knexConnection(table)
.where({ db_alias: dbAlias, base_id })
.del();
} catch (e) {
console.warn(`Error: ${table} reset failed`);
}
})();
}),
);
}
}
public async baseCreate(
baseName: string,
config: any,
description?: string,
meta?: boolean,
): Promise<any> {
try {
const ranId = this.getNanoId();
const id = `${baseName.toLowerCase().replace(/\W+/g, '_')}_${ranId}`;
if (meta) {
config.prefix = `nc_${ranId}__`;
// if(config.envs._noco?.db?.[0]?.meta?.tn){
// config.envs._noco.db[0].meta.tn += `_${prefix}`
// }
}
config.id = id;
const base: any = {
id,
title: baseName,
description,
config: CryptoJS.AES.encrypt(
JSON.stringify(config),
'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret
).toString(),
};
// todo: check base name used or not
await this.knexConnection('nc_projects').insert({
...base,
created_at: this.now(),
updated_at: this.now(),
});
// todo
await this.knexConnection(MetaTable.PROJECT).insert({
id,
title: baseName,
/***
* Update base config
* @param baseId - Base id
* @param config - Base config
* */
public async baseUpdate(baseId: string, config: any): Promise<any> {
if (!baseId) {
NcError.metaError({
message: 'Base Id is required to update base config',
sql: '',
});
base.prefix = config.prefix;
return base;
} catch (e) {
console.log(e);
}
}
public async baseUpdate(baseId: string, config: any): Promise<any> {
try {
const base = {
config: CryptoJS.AES.encrypt(
@ -757,6 +671,10 @@ export class MetaService {
}
}
/***
* Get base list with decrypted config
* @returns {Promise<any[]>} - List of bases
* */
public async baseList(): Promise<any[]> {
return (await this.knexConnection('nc_projects').select()).map((p) => {
p.config = CryptoJS.AES.decrypt(
@ -767,171 +685,6 @@ export class MetaService {
});
}
public async userProjectList(userId: any): Promise<any[]> {
return (
await this.knexConnection('nc_projects')
.leftJoin(
this.knexConnection('nc_projects_users')
.where(`nc_projects_users.user_id`, userId)
.as('user'),
'user.base_id',
'nc_projects.id',
)
.select('nc_projects.*')
.select('user.user_id')
.select(
this.knexConnection('xc_users')
.select('xc_users.email')
.innerJoin(
'nc_projects_users',
'nc_projects_users.user_id',
'=',
'xc_users.id',
)
.whereRaw('nc_projects.id = nc_projects_users.base_id')
.where('nc_projects_users.roles', 'like', '%owner%')
.first()
.as('owner'),
)
.select(
this.knexConnection('xc_users')
.count('xc_users.id')
.innerJoin(
'nc_projects_users',
'nc_projects_users.user_id',
'=',
'xc_users.id',
)
.where((qb) => {
qb.where('nc_projects_users.roles', 'like', '%creator%').orWhere(
'nc_projects_users.roles',
'like',
'%owner%',
);
})
.whereRaw('nc_projects.id = nc_projects_users.base_id')
.andWhere('xc_users.id', userId)
.first()
.as('is_creator'),
)
).map((p) => {
p.allowed = p.user_id === userId;
p.config = CryptoJS.AES.decrypt(
p.config,
'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
return p;
});
}
public async isUserHaveAccessToProject(
baseId: string,
userId: any,
): Promise<boolean> {
return !!(await this.knexConnection('nc_projects_users')
.where({
base_id: baseId,
user_id: userId,
})
.first());
}
public async baseGet(baseName: string, encrypt?): Promise<any> {
const base = await this.knexConnection('nc_projects')
.where({
title: baseName,
})
.first();
if (base && !encrypt) {
base.config = CryptoJS.AES.decrypt(
base.config,
'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
}
return base;
}
public async baseGetById(baseId: string, encrypt?): Promise<any> {
const base = await this.knexConnection('nc_projects')
.where({
id: baseId,
})
.first();
if (base && !encrypt) {
base.config = CryptoJS.AES.decrypt(
base.config,
'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
}
return base;
}
public baseDelete(title: string): Promise<any> {
return this.knexConnection('nc_projects')
.where({
title,
})
.delete();
}
public baseDeleteById(id: string): Promise<any> {
return this.knexConnection('nc_projects')
.where({
id,
})
.delete();
}
public async baseStatusUpdate(baseId: string, status: string): Promise<any> {
return this.knexConnection('nc_projects')
.update({
status,
})
.where({
id: baseId,
});
}
public async baseAddUser(
baseId: string,
userId: any,
roles: string,
): Promise<any> {
if (
await this.knexConnection('nc_projects_users')
.where({
user_id: userId,
base_id: baseId,
})
.first()
) {
return {};
}
return this.knexConnection('nc_projects_users').insert({
user_id: userId,
base_id: baseId,
roles,
});
}
public baseRemoveUser(baseId: string, userId: any): Promise<any> {
return this.knexConnection('nc_projects_users')
.where({
user_id: userId,
base_id: baseId,
})
.delete();
}
public removeXcUser(userId: any): Promise<any> {
return this.knexConnection('xc_users')
.where({
id: userId,
})
.delete();
}
private getNanoId() {
return nanoid();
}
@ -949,18 +702,6 @@ export class MetaService {
.format(this.isMySQL() ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ');
}
public async audit(
base_id: string,
dbAlias: string,
target: string,
data: any,
): Promise<any> {
if (['DATA', 'COMMENT'].includes(data?.op_type)) {
return Promise.resolve(undefined);
}
return this.metaInsert(base_id, dbAlias, target, data);
}
public async init(): Promise<boolean> {
await this.connection.migrate.latest({
migrationSource: new XcMigrationSource(),
@ -972,4 +713,30 @@ export class MetaService {
});
return true;
}
/**
* Checks if a condition is present in the query builder and throws an error if not.
*
* @param queryBuilder - The Knex QueryBuilder instance to check.
*/
private checkConditionPresent(
queryBuilder: Knex.QueryBuilder,
operation: 'delete' | 'update',
) {
// Convert the query builder to a SQL string to inspect the presence of a WHERE clause.
const sql = queryBuilder.toString();
// Ensure that a WHERE condition is present in the query builder.
// Note: The `hasWhere` method alone is not sufficient since it can indicate an empty nested WHERE group.
// Therefore, also check the SQL string for the presence of the 'WHERE' keyword.
if (queryBuilder.hasWhere() && /\bWHERE\b/i.test(sql)) {
return;
}
// Throw an error if no condition is found in the query builder.
NcError.metaError({
message: 'A condition is required to ' + operation + ' records.',
sql,
});
}
}

Loading…
Cancel
Save