mirror of https://github.com/nocodb/nocodb
Pranav C
12 months ago
committed by
GitHub
10 changed files with 587 additions and 211 deletions
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts"> |
||||
import type { ColumnType } from 'nocodb-sdk' |
||||
import { isVirtualCol } from 'nocodb-sdk' |
||||
|
||||
defineProps<{ |
||||
column: ColumnType |
||||
modelValue: any |
||||
}>() |
||||
|
||||
provide(ReadonlyInj, true) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="pointer-events-none"> |
||||
<LazySmartsheetRow :row="{ row: { [column.title]: modelValue }, rowMeta: {} }"> |
||||
<LazySmartsheetVirtualCell v-if="isVirtualCol(column)" :model-value="modelValue" class="!text-gray-600" :column="column" /> |
||||
|
||||
<LazySmartsheetCell |
||||
v-else |
||||
:model-value="modelValue" |
||||
class="!text-gray-600" |
||||
:column="column" |
||||
:edit-enabled="false" |
||||
:read-only="true" |
||||
/> |
||||
</LazySmartsheetRow> |
||||
</div> |
||||
</template> |
@ -1,163 +0,0 @@
|
||||
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||
import type LookupColumn from '../models/LookupColumn'; |
||||
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; |
||||
import type { |
||||
Column, |
||||
FormulaColumn, |
||||
LinkToAnotherRecordColumn, |
||||
Model, |
||||
RollupColumn, |
||||
} from '~/models'; |
||||
import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; |
||||
import genRollupSelectv2 from '~/db/genRollupSelectv2'; |
||||
import { NcError } from '~/helpers/catchError'; |
||||
|
||||
export default async function generateBTLookupSelectQuery({ |
||||
column, |
||||
baseModelSqlv2, |
||||
alias, |
||||
model, |
||||
}: { |
||||
column: Column; |
||||
baseModelSqlv2: BaseModelSqlv2; |
||||
alias: string; |
||||
model: Model; |
||||
}): Promise<any> { |
||||
const knex = baseModelSqlv2.dbDriver; |
||||
|
||||
const rootAlias = alias; |
||||
|
||||
{ |
||||
let aliasCount = 0, |
||||
selectQb; |
||||
const alias = `__nc_lk_${aliasCount++}`; |
||||
const lookup = await column.getColOptions<LookupColumn>(); |
||||
{ |
||||
const relationCol = await lookup.getRelationColumn(); |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
if (relation.type !== RelationTypes.BELONGS_TO) |
||||
NcError.badRequest('HasMany/ManyToMany lookup is not supported'); |
||||
|
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${parentColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
let lookupColumn = await lookup.getLookupColumn(); |
||||
let prevAlias = alias; |
||||
while (lookupColumn.uidt === UITypes.Lookup) { |
||||
const nestedAlias = `__nc_lk_nested_${aliasCount++}`; |
||||
const nestedLookup = await lookupColumn.getColOptions<LookupColumn>(); |
||||
const relationCol = await nestedLookup.getRelationColumn(); |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if any of the relation in nested lookup is
|
||||
// not belongs to then throw error as we don't support
|
||||
if (relation.type !== RelationTypes.BELONGS_TO) |
||||
NcError.badRequest('HasMany/ManyToMany lookup is not supported'); |
||||
|
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${nestedAlias}`, |
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
); |
||||
|
||||
lookupColumn = await nestedLookup.getLookupColumn(); |
||||
prevAlias = nestedAlias; |
||||
} |
||||
|
||||
switch (lookupColumn.uidt) { |
||||
case UITypes.Links: |
||||
case UITypes.Rollup: |
||||
{ |
||||
const builder = ( |
||||
await genRollupSelectv2({ |
||||
baseModelSqlv2, |
||||
knex, |
||||
columnOptions: |
||||
(await lookupColumn.getColOptions()) as RollupColumn, |
||||
alias: prevAlias, |
||||
}) |
||||
).builder; |
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.LinkToAnotherRecord: |
||||
{ |
||||
const nestedAlias = `__nc_sort${aliasCount++}`; |
||||
const relation = |
||||
await lookupColumn.getColOptions<LinkToAnotherRecordColumn>(); |
||||
if (relation.type !== 'bt') return; |
||||
|
||||
const colOptions = |
||||
(await column.getColOptions()) as LinkToAnotherRecordColumn; |
||||
const childColumn = await colOptions.getChildColumn(); |
||||
const parentColumn = await colOptions.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb |
||||
.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
parentModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
) |
||||
.select(parentModel?.displayValue?.column_name); |
||||
} |
||||
break; |
||||
case UITypes.Formula: |
||||
{ |
||||
const builder = ( |
||||
await formulaQueryBuilderv2( |
||||
baseModelSqlv2, |
||||
( |
||||
await column.getColOptions<FormulaColumn>() |
||||
).formula, |
||||
null, |
||||
model, |
||||
column, |
||||
) |
||||
).builder; |
||||
|
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
default: |
||||
{ |
||||
selectQb.select(`${prevAlias}.${lookupColumn.column_name}`); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
return { builder: selectQb }; |
||||
} |
||||
} |
@ -0,0 +1,399 @@
|
||||
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||
import type LookupColumn from '../models/LookupColumn'; |
||||
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; |
||||
import type { |
||||
BarcodeColumn, |
||||
Column, |
||||
FormulaColumn, |
||||
LinksColumn, |
||||
LinkToAnotherRecordColumn, |
||||
QrCodeColumn, |
||||
RollupColumn, |
||||
} from '~/models'; |
||||
import { Model } from '~/models'; |
||||
import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; |
||||
import genRollupSelectv2 from '~/db/genRollupSelectv2'; |
||||
import { getAliasGenerator } from '~/utils'; |
||||
import { NcError } from '~/helpers/catchError'; |
||||
|
||||
const LOOKUP_VAL_SEPARATOR = '___'; |
||||
|
||||
export async function getDisplayValueOfRefTable( |
||||
relationCol: Column<LinkToAnotherRecordColumn | LinksColumn>, |
||||
) { |
||||
return await relationCol |
||||
.getColOptions() |
||||
.then((colOpt) => colOpt.getRelatedTable()) |
||||
.then((model) => model.getColumns()) |
||||
.then((cols) => cols.find((col) => col.pv)); |
||||
} |
||||
|
||||
export default async function generateLookupSelectQuery({ |
||||
column, |
||||
baseModelSqlv2, |
||||
alias, |
||||
model: _model, |
||||
getAlias = getAliasGenerator('__lk_slt_'), |
||||
}: { |
||||
column: Column; |
||||
baseModelSqlv2: BaseModelSqlv2; |
||||
alias: string; |
||||
model: Model; |
||||
getAlias?: ReturnType<typeof getAliasGenerator>; |
||||
}): Promise<any> { |
||||
const knex = baseModelSqlv2.dbDriver; |
||||
|
||||
const rootAlias = alias; |
||||
|
||||
{ |
||||
let selectQb; |
||||
const alias = getAlias(); |
||||
let lookupColOpt: LookupColumn; |
||||
let isBtLookup = true; |
||||
|
||||
if (column.uidt === UITypes.Lookup) { |
||||
lookupColOpt = await column.getColOptions<LookupColumn>(); |
||||
} else if (column.uidt !== UITypes.LinkToAnotherRecord) { |
||||
NcError.badRequest('Invalid column type'); |
||||
} |
||||
|
||||
await column.getColOptions<LookupColumn>(); |
||||
{ |
||||
const relationCol = lookupColOpt |
||||
? await lookupColOpt.getRelationColumn() |
||||
: column; |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
if (relation.type === RelationTypes.BELONGS_TO) { |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${parentColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
else if (relation.type === RelationTypes.HAS_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(childModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${childColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(parentModel.table_name)}.${ |
||||
parentColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
else if (relation.type === RelationTypes.MANY_TO_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
); |
||||
|
||||
const mmTableAlias = getAlias(); |
||||
|
||||
const mmModel = await relation.getMMModel(); |
||||
const mmChildCol = await relation.getMMChildColumn(); |
||||
const mmParentCol = await relation.getMMParentColumn(); |
||||
|
||||
selectQb |
||||
.innerJoin( |
||||
baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias), |
||||
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${alias}.${parentColumn.column_name}`), |
||||
) |
||||
.where( |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref( |
||||
`${ |
||||
rootAlias || baseModelSqlv2.getTnPath(childModel.table_name) |
||||
}.${childColumn.column_name}`,
|
||||
), |
||||
); |
||||
} |
||||
} |
||||
let lookupColumn = lookupColOpt |
||||
? await lookupColOpt.getLookupColumn() |
||||
: await getDisplayValueOfRefTable(column); |
||||
|
||||
// if lookup column is qr code or barcode extract the referencing column
|
||||
if ([UITypes.QrCode, UITypes.Barcode].includes(lookupColumn.uidt)) { |
||||
lookupColumn = await lookupColumn |
||||
.getColOptions<BarcodeColumn | QrCodeColumn>() |
||||
.then((barcode) => barcode.getValueColumn()); |
||||
} |
||||
|
||||
let prevAlias = alias; |
||||
while ( |
||||
lookupColumn.uidt === UITypes.Lookup || |
||||
lookupColumn.uidt === UITypes.LinkToAnotherRecord |
||||
) { |
||||
const nestedAlias = getAlias(); |
||||
|
||||
let relationCol: Column<LinkToAnotherRecordColumn | LinksColumn>; |
||||
let nestedLookupColOpt: LookupColumn; |
||||
|
||||
if (lookupColumn.uidt === UITypes.Lookup) { |
||||
nestedLookupColOpt = await lookupColumn.getColOptions<LookupColumn>(); |
||||
relationCol = await nestedLookupColOpt.getRelationColumn(); |
||||
} else { |
||||
relationCol = lookupColumn; |
||||
} |
||||
|
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if any of the relation in nested lookupColOpt is
|
||||
// not belongs to then throw error as we don't support
|
||||
if (relation.type === RelationTypes.BELONGS_TO) { |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
parentModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
); |
||||
} else if (relation.type === RelationTypes.HAS_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
childModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${childColumn.column_name}`, |
||||
`${prevAlias}.${parentColumn.column_name}`, |
||||
); |
||||
} else if (relation.type === RelationTypes.MANY_TO_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
const mmTableAlias = getAlias(); |
||||
|
||||
const mmModel = await relation.getMMModel(); |
||||
const mmChildCol = await relation.getMMChildColumn(); |
||||
const mmParentCol = await relation.getMMParentColumn(); |
||||
|
||||
selectQb |
||||
.innerJoin( |
||||
baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias), |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${prevAlias}.${childColumn.column_name}`), |
||||
) |
||||
.innerJoin( |
||||
knex.raw('?? as ??', [ |
||||
baseModelSqlv2.getTnPath(parentModel.table_name), |
||||
nestedAlias, |
||||
]), |
||||
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${nestedAlias}.${parentColumn.column_name}`), |
||||
) |
||||
.where( |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref( |
||||
`${alias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
), |
||||
); |
||||
} |
||||
|
||||
if (lookupColumn.uidt === UITypes.Lookup) |
||||
lookupColumn = await nestedLookupColOpt.getLookupColumn(); |
||||
else lookupColumn = await getDisplayValueOfRefTable(relationCol); |
||||
prevAlias = nestedAlias; |
||||
} |
||||
|
||||
{ |
||||
// get basemodel and model of lookup column
|
||||
const model = await lookupColumn.getModel(); |
||||
const baseModelSqlv2 = await Model.getBaseModelSQL({ |
||||
model, |
||||
dbDriver: knex, |
||||
}); |
||||
|
||||
switch (lookupColumn.uidt) { |
||||
case UITypes.Attachment: |
||||
NcError.badRequest( |
||||
'Group by using attachment column is not supported', |
||||
); |
||||
break; |
||||
case UITypes.Links: |
||||
case UITypes.Rollup: |
||||
{ |
||||
const builder = ( |
||||
await genRollupSelectv2({ |
||||
baseModelSqlv2, |
||||
knex, |
||||
columnOptions: |
||||
(await lookupColumn.getColOptions()) as RollupColumn, |
||||
alias: prevAlias, |
||||
}) |
||||
).builder; |
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.Formula: |
||||
{ |
||||
const builder = ( |
||||
await formulaQueryBuilderv2( |
||||
baseModelSqlv2, |
||||
( |
||||
await lookupColumn.getColOptions<FormulaColumn>() |
||||
).formula, |
||||
lookupColumn.title, |
||||
model, |
||||
lookupColumn, |
||||
await model.getAliasColMapping(), |
||||
prevAlias, |
||||
) |
||||
).builder; |
||||
|
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.DateTime: |
||||
{ |
||||
await baseModelSqlv2.selectObject({ |
||||
qb: selectQb, |
||||
columns: [lookupColumn], |
||||
alias: prevAlias, |
||||
}); |
||||
} |
||||
break; |
||||
default: |
||||
{ |
||||
selectQb.select( |
||||
`${prevAlias}.${lookupColumn.column_name} as ${lookupColumn.title}`, |
||||
); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
// if all relation are belongs to then we don't need to do the aggregation
|
||||
if (isBtLookup) { |
||||
return { |
||||
builder: selectQb, |
||||
}; |
||||
} |
||||
|
||||
const subQueryAlias = getAlias(); |
||||
|
||||
if (baseModelSqlv2.isPg) { |
||||
// alternate approach with array_agg
|
||||
return { |
||||
builder: knex |
||||
.select(knex.raw('json_agg(??)::text', [lookupColumn.title])) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
/* |
||||
// alternate approach with array_agg
|
||||
return { |
||||
builder: knex |
||||
.select(knex.raw('array_agg(??)', [lookupColumn.title])) |
||||
.from(selectQb), |
||||
};*/ |
||||
// alternate approach with string aggregation
|
||||
// return {
|
||||
// builder: knex
|
||||
// .select(
|
||||
// knex.raw('STRING_AGG(??::text, ?)', [
|
||||
// lookupColumn.title,
|
||||
// LOOKUP_VAL_SEPARATOR,
|
||||
// ]),
|
||||
// )
|
||||
// .from(selectQb.as(subQueryAlias)),
|
||||
// };
|
||||
} else if (baseModelSqlv2.isMySQL) { |
||||
return { |
||||
builder: knex |
||||
.select( |
||||
knex.raw('cast(JSON_ARRAYAGG(??) as NCHAR)', [lookupColumn.title]), |
||||
) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
|
||||
// return {
|
||||
// builder: knex
|
||||
// .select(
|
||||
// knex.raw('GROUP_CONCAT(?? ORDER BY ?? ASC SEPARATOR ?)', [
|
||||
// lookupColumn.title,
|
||||
// lookupColumn.title,
|
||||
// LOOKUP_VAL_SEPARATOR,
|
||||
// ]),
|
||||
// )
|
||||
// .from(selectQb.as(subQueryAlias)),
|
||||
// };
|
||||
} else if (baseModelSqlv2.isSqlite) { |
||||
// ref: https://stackoverflow.com/questions/13382856/sqlite3-join-group-concat-using-distinct-with-custom-separator
|
||||
// selectQb.orderBy(`${lookupColumn.title}`, 'asc');
|
||||
return { |
||||
builder: knex |
||||
.select( |
||||
knex.raw(`group_concat(??, ?)`, [ |
||||
lookupColumn.title, |
||||
LOOKUP_VAL_SEPARATOR, |
||||
]), |
||||
) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
} |
||||
|
||||
NcError.notImplemented('Database not supported Group by on Lookup'); |
||||
} |
||||
} |
Loading…
Reference in new issue