|
|
@ -613,7 +613,7 @@ async function _formulaQueryBuilder( |
|
|
|
break; |
|
|
|
break; |
|
|
|
default: |
|
|
|
default: |
|
|
|
{ |
|
|
|
{ |
|
|
|
const res = mapFunctionName({ |
|
|
|
const res = await mapFunctionName({ |
|
|
|
pt, |
|
|
|
pt, |
|
|
|
knex, |
|
|
|
knex, |
|
|
|
alias, |
|
|
|
alias, |
|
|
@ -630,23 +630,25 @@ async function _formulaQueryBuilder( |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
|
builder: knex.raw( |
|
|
|
builder: knex.raw( |
|
|
|
`${pt.callee.name}(${pt.arguments |
|
|
|
`${pt.callee.name}(${( |
|
|
|
.map((arg) => { |
|
|
|
await Promise.all( |
|
|
|
const query = fn(arg).toQuery(); |
|
|
|
pt.arguments.map(async (arg) => { |
|
|
|
if (pt.callee.name === 'CONCAT') { |
|
|
|
const query = (await fn(arg)).builder.toQuery(); |
|
|
|
if (knex.clientType() === 'mysql2') { |
|
|
|
if (pt.callee.name === 'CONCAT') { |
|
|
|
// mysql2: CONCAT() returns NULL if any argument is NULL.
|
|
|
|
if (knex.clientType() === 'mysql2') { |
|
|
|
// adding IFNULL to convert NULL values to empty strings
|
|
|
|
// mysql2: CONCAT() returns NULL if any argument is NULL.
|
|
|
|
return `IFNULL(${query}, '')`; |
|
|
|
// adding IFNULL to convert NULL values to empty strings
|
|
|
|
} else { |
|
|
|
return `IFNULL(${query}, '')`; |
|
|
|
// do nothing
|
|
|
|
} else { |
|
|
|
// pg / mssql: Concatenate all arguments. NULL arguments are ignored.
|
|
|
|
// do nothing
|
|
|
|
// sqlite3: special handling - See BinaryExpression
|
|
|
|
// pg / mssql: Concatenate all arguments. NULL arguments are ignored.
|
|
|
|
|
|
|
|
// sqlite3: special handling - See BinaryExpression
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return query; |
|
|
|
return query; |
|
|
|
}) |
|
|
|
}) |
|
|
|
) |
|
|
|
.join()})${colAlias}`.replace(/\?/g, '\\?')
|
|
|
|
).join()})${colAlias}`.replace(/\?/g, '\\?')
|
|
|
|
), |
|
|
|
), |
|
|
|
}; |
|
|
|
}; |
|
|
|
} else if (pt.type === 'Literal') { |
|
|
|
} else if (pt.type === 'Literal') { |
|
|
@ -844,7 +846,7 @@ export default async function formulaQueryBuilderv2( |
|
|
|
// dry run qb.builder to see if it will break the grid view or not
|
|
|
|
// dry run qb.builder to see if it will break the grid view or not
|
|
|
|
// if so, set formula error and show empty selectQb instead
|
|
|
|
// if so, set formula error and show empty selectQb instead
|
|
|
|
await knex(getTnPath(model, knex, tableAlias)) |
|
|
|
await knex(getTnPath(model, knex, tableAlias)) |
|
|
|
.select(qb.builder) |
|
|
|
.select(knex.raw(`?? as ??`, [qb.builder, '__dry_run_alias'])) |
|
|
|
.as('dry-run-only'); |
|
|
|
.as('dry-run-only'); |
|
|
|
|
|
|
|
|
|
|
|
// if column is provided, i.e. formula has been created
|
|
|
|
// if column is provided, i.e. formula has been created
|
|
|
@ -878,62 +880,3 @@ export default async function formulaQueryBuilderv2( |
|
|
|
} |
|
|
|
} |
|
|
|
return qb; |
|
|
|
return qb; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export async function validateFormula( |
|
|
|
|
|
|
|
_tree, |
|
|
|
|
|
|
|
alias, |
|
|
|
|
|
|
|
knex: XKnex, |
|
|
|
|
|
|
|
model: Model, |
|
|
|
|
|
|
|
column?: Column, |
|
|
|
|
|
|
|
aliasToColumn = {}, |
|
|
|
|
|
|
|
tableAlias?: string |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
// register jsep curly hook once only
|
|
|
|
|
|
|
|
jsep.plugins.register(jsepCurlyHook); |
|
|
|
|
|
|
|
// generate qb
|
|
|
|
|
|
|
|
const qb = await _formulaQueryBuilder( |
|
|
|
|
|
|
|
_tree, |
|
|
|
|
|
|
|
alias, |
|
|
|
|
|
|
|
knex, |
|
|
|
|
|
|
|
model, |
|
|
|
|
|
|
|
aliasToColumn, |
|
|
|
|
|
|
|
tableAlias |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// dry run qb.builder to see if it will break the grid view or not
|
|
|
|
|
|
|
|
// if so, set formula error and show empty selectQb instead
|
|
|
|
|
|
|
|
await knex(getTnPath(model, knex, tableAlias)) |
|
|
|
|
|
|
|
.select(qb.builder) |
|
|
|
|
|
|
|
.as('dry-run-only'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if column is provided, i.e. formula has been created
|
|
|
|
|
|
|
|
if (column) { |
|
|
|
|
|
|
|
const formula = await column.getColOptions<FormulaColumn>(); |
|
|
|
|
|
|
|
// clean the previous formula error if the formula works this time
|
|
|
|
|
|
|
|
if (formula.error) { |
|
|
|
|
|
|
|
await FormulaColumn.update(formula.id, { |
|
|
|
|
|
|
|
error: null, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
console.error(e); |
|
|
|
|
|
|
|
if (column) { |
|
|
|
|
|
|
|
const formula = await column.getColOptions<FormulaColumn>(); |
|
|
|
|
|
|
|
// add formula error to show in UI
|
|
|
|
|
|
|
|
await FormulaColumn.update(formula.id, { |
|
|
|
|
|
|
|
error: e.message, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
// update cache to reflect the error in UI
|
|
|
|
|
|
|
|
const key = `${CacheScope.COL_FORMULA}:${column.id}`; |
|
|
|
|
|
|
|
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); |
|
|
|
|
|
|
|
if (o) { |
|
|
|
|
|
|
|
o = { ...o, error: e.message }; |
|
|
|
|
|
|
|
// set cache
|
|
|
|
|
|
|
|
await NocoCache.set(key, o); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw new Error(`Formula error: ${e.message}`); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|