Browse Source

Merge pull request #2160 from nocodb/enhancement/formula

enhancement: formula
pull/2169/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
f1cd141922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue
  2. 18
      packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js
  3. 33
      packages/nocodb/src/lib/dataMapper/lib/sql/formulav2/formulaQueryBuilderv2.ts
  4. 2
      packages/nocodb/src/lib/noco-models/Column.ts
  5. 2
      packages/nocodb/src/lib/noco/meta/api/columnApis.ts

22
packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

@ -43,6 +43,9 @@
<span
class="caption primary--text text--lighten-2 font-weight-bold"
>
<v-icon color="primary lighten-2" small class="mr-1">
mdi-function
</v-icon>
{{ it.text }}
</span>
</v-list-item-content>
@ -70,10 +73,12 @@
<span
class="caption text--darken-3 font-weight-bold"
>
<v-icon color="grey darken-4" small class="mr-1">
{{ it.icon }}
</v-icon>
{{ it.text }}
</span>
</v-list-item-content>
<v-list-item-action>
<span class="caption">
Column
@ -87,6 +92,9 @@
<span
class="caption indigo--text text--darken-3 font-weight-bold"
>
<v-icon color="indigo darken-3" small class="mr-1">
mdi-calculator-variant
</v-icon>
{{ it.text }}
</span>
</v-list-item-content>
@ -109,6 +117,7 @@
import debounce from 'debounce'
import jsep from 'jsep'
import { UITypes, jsepCurlyHook } from 'nocodb-sdk'
import { getUIDTIcon } from '../../helpers/uiTypes'
import formulaList, { formulas, formulaTypes } from '../../../../../helpers/formulaList'
import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree'
@ -146,6 +155,7 @@ export default {
...this.meta.columns.filter(c => !this.column || this.column.id !== c.id).map(c => ({
text: c.title,
type: 'column',
icon: getUIDTIcon(c.uidt),
c
})),
...this.availableBinOps.map(op => ({
@ -534,13 +544,18 @@ export default {
return 'N/A'
}
},
isCurlyBracketBalanced() {
// count number of opening curly brackets and closing curly brackets
const cntCurlyBrackets = (this.$refs.input.$el.querySelector('input').value.match(/\{|}/g) || []).reduce((acc, cur) => (acc[cur] = (acc[cur] || 0) + 1, acc), {})
return (cntCurlyBrackets['{'] || 0) === (cntCurlyBrackets['}'] || 0)
},
appendText(it) {
const text = it.text
const len = this.wordToComplete.length
if (it.type === 'function') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1))
} else if (it.type === 'column') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), '{' + text + '}', len))
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), '{' + text + '}', len + (!this.isCurlyBracketBalanced())))
} else {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len))
}
@ -560,6 +575,9 @@ export default {
const parts = query.split(/\W+/)
this.wordToComplete = parts.pop()
this.suggestion = this.acTree.complete(this.wordToComplete)?.sort((x, y) => this.sortOrder[x.type] - this.sortOrder[y.type])
if (!this.isCurlyBracketBalanced()) {
this.suggestion = this.suggestion.filter(v => v.type === 'column')
}
this.autocomplete = !!this.suggestion.length
},
selectText() {

18
packages/nc-gui/components/project/spreadsheet/helpers/uiTypes.js

@ -151,10 +151,20 @@ const uiTypes = [
]
const getUIDTIcon = (uidt) => {
return ([...uiTypes, {
name: 'CreateTime',
icon: 'mdi-calendar-clock'
}].find(t => t.name === uidt) || {}).icon
return ([...uiTypes,
{
name: 'CreateTime',
icon: 'mdi-calendar-clock'
},
{
name: 'ID',
icon: 'mdi-identifier'
},
{
name: 'ForeignKey',
icon: 'mdi-link-variant'
}
].find(t => t.name === uidt) || {}).icon
}
export {

33
packages/nocodb/src/lib/dataMapper/lib/sql/formulav2/formulaQueryBuilderv2.ts

@ -612,7 +612,21 @@ export default async function formulaQueryBuilderv2(
return knex.raw(
`${pt.callee.name}(${pt.arguments
.map(arg => fn(arg).toQuery())
.map(arg => {
const query = fn(arg).toQuery();
if (pt.callee.name === 'CONCAT') {
if (knex.clientType() === 'mysql2') {
// mysql2: CONCAT() returns NULL if any argument is NULL.
// adding IFNULL to convert NULL values to empty strings
return `IFNULL(${query}, '')`;
} else {
// do nothing
// pg / mssql: Concatenate all arguments. NULL arguments are ignored.
// sqlite3: special handling - See BinaryExpression
}
}
return query;
})
.join()})${colAlias}`
);
} else if (pt.type === 'Literal') {
@ -637,13 +651,16 @@ export default async function formulaQueryBuilderv2(
pt.left.fnName = pt.left.fnName || 'ARITH';
pt.right.fnName = pt.right.fnName || 'ARITH';
const query = knex.raw(
`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(
pt.right,
null,
pt.operator
).toQuery()}${colAlias}`
);
const left = fn(pt.left, null, pt.operator).toQuery();
const right = fn(pt.right, null, pt.operator).toQuery();
let sql = `${left} ${pt.operator} ${right}${colAlias}`;
// handle NULL values when calling CONCAT for sqlite3
if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') {
sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`;
}
const query = knex.raw(sql);
if (prevBinaryOp && pt.operator !== prevBinaryOp) {
query.wrap('(', ')');
}

2
packages/nocodb/src/lib/noco-models/Column.ts

@ -541,7 +541,7 @@ export default class Column<T = any> implements ColumnType {
title: col?.title
})
)
await FormulaColumn.update(formula.id, formula, ncMeta);
await FormulaColumn.update(formulaCol.id, formula, ncMeta);
}
}

2
packages/nocodb/src/lib/noco/meta/api/columnApis.ts

@ -649,7 +649,7 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
formula,
[new_column]
);
await FormulaColumn.update(f.id, {
await FormulaColumn.update(c.id, {
formula_raw: new_formula_raw
});
}

Loading…
Cancel
Save