|
|
@ -2343,141 +2343,152 @@ class BaseModelSqlv2 { |
|
|
|
value: Record<string, unknown>[]; |
|
|
|
value: Record<string, unknown>[]; |
|
|
|
}[] |
|
|
|
}[] |
|
|
|
> { |
|
|
|
> { |
|
|
|
const column = await this.model |
|
|
|
try { |
|
|
|
.getColumns() |
|
|
|
const column = await this.model |
|
|
|
.then((cols) => cols?.find((col) => col.id === args.groupColumnId)); |
|
|
|
.getColumns() |
|
|
|
|
|
|
|
.then((cols) => cols?.find((col) => col.id === args.groupColumnId)); |
|
|
|
if (!column) NcError.notFound('Column not found'); |
|
|
|
|
|
|
|
if (isVirtualCol(column)) |
|
|
|
if (!column) NcError.notFound('Column not found'); |
|
|
|
NcError.notImplemented('Grouping for virtual columns not implemented'); |
|
|
|
if (isVirtualCol(column)) |
|
|
|
|
|
|
|
NcError.notImplemented('Grouping for virtual columns not implemented'); |
|
|
|
// extract distinct group column values
|
|
|
|
|
|
|
|
let groupingValues; |
|
|
|
// extract distinct group column values
|
|
|
|
if (column.uidt === UITypes.SingleSelect) { |
|
|
|
let groupingValues; |
|
|
|
const colOptions = await column.getColOptions<SelectOption[]>(); |
|
|
|
if (column.uidt === UITypes.SingleSelect) { |
|
|
|
groupingValues = colOptions?.map((opt) => opt.title); |
|
|
|
const colOptions = await column.getColOptions< |
|
|
|
} else { |
|
|
|
SelectOption[] & { options } |
|
|
|
groupingValues = ( |
|
|
|
>(); |
|
|
|
await this.dbDriver(this.model.table_name) |
|
|
|
groupingValues = colOptions.options.map((opt) => opt.title); |
|
|
|
.select(column.column_name) |
|
|
|
} else { |
|
|
|
.distinct() |
|
|
|
groupingValues = ( |
|
|
|
).map((row) => row[column.column_name]); |
|
|
|
await this.dbDriver(this.model.table_name) |
|
|
|
} |
|
|
|
.select(column.column_name) |
|
|
|
|
|
|
|
.distinct() |
|
|
|
const qb = this.dbDriver(this.model.table_name); |
|
|
|
).map((row) => row[column.column_name]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
qb.limit(+args?.limit || 20); |
|
|
|
const qb = this.dbDriver(this.model.table_name); |
|
|
|
qb.offset(+args?.offset || 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await this.selectObject({ qb }); |
|
|
|
qb.limit(+args?.limit || 20); |
|
|
|
|
|
|
|
qb.offset(+args?.offset || 0); |
|
|
|
|
|
|
|
|
|
|
|
// todo: refactor and move to a method (applyFilterAndSort)
|
|
|
|
await this.selectObject({ qb }); |
|
|
|
const aliasColObjMap = await this.model.getAliasColObjMap(); |
|
|
|
|
|
|
|
let sorts = extractSortsObject(args?.sort, aliasColObjMap); |
|
|
|
|
|
|
|
const filterObj = extractFilterFromXwhere(args.where, aliasColObjMap); |
|
|
|
|
|
|
|
// todo: replace with view id
|
|
|
|
|
|
|
|
if (!args.ignoreFilterSort && this.viewId) { |
|
|
|
|
|
|
|
await conditionV2( |
|
|
|
|
|
|
|
[ |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: |
|
|
|
|
|
|
|
(await Filter.rootFilterList({ viewId: this.viewId })) || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: args.filterArr || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: filterObj, |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
qb, |
|
|
|
|
|
|
|
this.dbDriver |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!sorts) |
|
|
|
// todo: refactor and move to a method (applyFilterAndSort)
|
|
|
|
sorts = args.sortArr?.length |
|
|
|
const aliasColObjMap = await this.model.getAliasColObjMap(); |
|
|
|
? args.sortArr |
|
|
|
let sorts = extractSortsObject(args?.sort, aliasColObjMap); |
|
|
|
: await Sort.list({ viewId: this.viewId }); |
|
|
|
const filterObj = extractFilterFromXwhere(args.where, aliasColObjMap); |
|
|
|
|
|
|
|
// todo: replace with view id
|
|
|
|
|
|
|
|
if (!args.ignoreFilterSort && this.viewId) { |
|
|
|
|
|
|
|
await conditionV2( |
|
|
|
|
|
|
|
[ |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: |
|
|
|
|
|
|
|
(await Filter.rootFilterList({ viewId: this.viewId })) || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: args.filterArr || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: filterObj, |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
qb, |
|
|
|
|
|
|
|
this.dbDriver |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (sorts?.['length']) await sortV2(sorts, qb, this.dbDriver); |
|
|
|
if (!sorts) |
|
|
|
} else { |
|
|
|
sorts = args.sortArr?.length |
|
|
|
await conditionV2( |
|
|
|
? args.sortArr |
|
|
|
[ |
|
|
|
: await Sort.list({ viewId: this.viewId }); |
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: args.filterArr || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: filterObj, |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
qb, |
|
|
|
|
|
|
|
this.dbDriver |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!sorts) sorts = args.sortArr; |
|
|
|
if (sorts?.['length']) await sortV2(sorts, qb, this.dbDriver); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
await conditionV2( |
|
|
|
|
|
|
|
[ |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: args.filterArr || [], |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
new Filter({ |
|
|
|
|
|
|
|
children: filterObj, |
|
|
|
|
|
|
|
is_group: true, |
|
|
|
|
|
|
|
logical_op: 'and', |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
qb, |
|
|
|
|
|
|
|
this.dbDriver |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (sorts?.['length']) await sortV2(sorts, qb, this.dbDriver); |
|
|
|
if (!sorts) sorts = args.sortArr; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// sort by primary key if not autogenerated string
|
|
|
|
if (sorts?.['length']) await sortV2(sorts, qb, this.dbDriver); |
|
|
|
// if autogenerated string sort by created_at column if present
|
|
|
|
} |
|
|
|
if (this.model.primaryKey && this.model.primaryKey.ai) { |
|
|
|
|
|
|
|
qb.orderBy(this.model.primaryKey.column_name); |
|
|
|
|
|
|
|
} else if (this.model.columns.find((c) => c.column_name === 'created_at')) { |
|
|
|
|
|
|
|
qb.orderBy('created_at'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const nullListQb = qb.clone().whereNull(column.title); |
|
|
|
// sort by primary key if not autogenerated string
|
|
|
|
|
|
|
|
// if autogenerated string sort by created_at column if present
|
|
|
|
|
|
|
|
if (this.model.primaryKey && this.model.primaryKey.ai) { |
|
|
|
|
|
|
|
qb.orderBy(this.model.primaryKey.column_name); |
|
|
|
|
|
|
|
} else if ( |
|
|
|
|
|
|
|
this.model.columns.find((c) => c.column_name === 'created_at') |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
qb.orderBy('created_at'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const groupedQb = this.dbDriver.from( |
|
|
|
const nullListQb = qb.clone().whereNull(column.title); |
|
|
|
this.dbDriver |
|
|
|
|
|
|
|
.unionAll( |
|
|
|
|
|
|
|
[ |
|
|
|
|
|
|
|
this.isSqlite |
|
|
|
|
|
|
|
? this.dbDriver.select().from(nullListQb) |
|
|
|
|
|
|
|
: nullListQb, |
|
|
|
|
|
|
|
...groupingValues.map((r) => { |
|
|
|
|
|
|
|
const query = qb.clone().where(column.title, r); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return this.isSqlite ? this.dbDriver.select().from(query) : query; |
|
|
|
const groupedQb = this.dbDriver.from( |
|
|
|
}), |
|
|
|
this.dbDriver |
|
|
|
], |
|
|
|
.unionAll( |
|
|
|
!this.isSqlite |
|
|
|
[ |
|
|
|
) |
|
|
|
this.isSqlite |
|
|
|
.as('__nc_grouped_list') |
|
|
|
? this.dbDriver.select().from(nullListQb) |
|
|
|
); |
|
|
|
: nullListQb, |
|
|
|
|
|
|
|
...groupingValues.map((r) => { |
|
|
|
|
|
|
|
const query = qb.clone().where(column.title, r); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return this.isSqlite |
|
|
|
|
|
|
|
? this.dbDriver.select().from(query) |
|
|
|
|
|
|
|
: query; |
|
|
|
|
|
|
|
}), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
!this.isSqlite |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.as('__nc_grouped_list') |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const proto = await this.getProto(); |
|
|
|
const proto = await this.getProto(); |
|
|
|
|
|
|
|
|
|
|
|
const result = (await groupedQb)?.map((d) => { |
|
|
|
const result = (await groupedQb)?.map((d) => { |
|
|
|
d.__proto__ = proto; |
|
|
|
d.__proto__ = proto; |
|
|
|
return d; |
|
|
|
return d; |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// todo: handle null values
|
|
|
|
// todo: handle null values
|
|
|
|
const groupedResult: Record<string, Record<string, unknown>[]> = _.groupBy( |
|
|
|
const groupedResult: Record<string, Record<string, unknown>[]> = |
|
|
|
result, |
|
|
|
_.groupBy(result, column.title); |
|
|
|
column.title |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const r = Object.entries(groupedResult).map(([key, value]) => ({ |
|
|
|
const r = Object.entries(groupedResult).map(([key, value]) => ({ |
|
|
|
key, |
|
|
|
key, |
|
|
|
value, |
|
|
|
value, |
|
|
|
})); |
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
return r; |
|
|
|
return r; |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
console.log(e); |
|
|
|
|
|
|
|
throw e; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public async groupedListCount(args: { groupColumnId: string ; ignoreFilterSort?:boolean} & XcFilter) { |
|
|
|
public async groupedListCount( |
|
|
|
|
|
|
|
args: { groupColumnId: string; ignoreFilterSort?: boolean } & XcFilter |
|
|
|
|
|
|
|
) { |
|
|
|
const column = await this.model |
|
|
|
const column = await this.model |
|
|
|
.getColumns() |
|
|
|
.getColumns() |
|
|
|
.then((cols) => cols?.find((col) => col.id === args.groupColumnId)); |
|
|
|
.then((cols) => cols?.find((col) => col.id === args.groupColumnId)); |
|
|
@ -2536,7 +2547,6 @@ class BaseModelSqlv2 { |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await this.selectObject({ |
|
|
|
await this.selectObject({ |
|
|
|
qb, |
|
|
|
qb, |
|
|
|
columns: [new Column({ ...column, title: 'key' })], |
|
|
|
columns: [new Column({ ...column, title: 'key' })], |
|
|
|