Browse Source

Merge pull request #2160 from nocodb/enhancement/formula

enhancement: formula
pull/2169/head
աɨռɢӄաօռɢ 3 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. 14
      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 <span
class="caption primary--text text--lighten-2 font-weight-bold" 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 }} {{ it.text }}
</span> </span>
</v-list-item-content> </v-list-item-content>
@ -70,10 +73,12 @@
<span <span
class="caption text--darken-3 font-weight-bold" class="caption text--darken-3 font-weight-bold"
> >
<v-icon color="grey darken-4" small class="mr-1">
{{ it.icon }}
</v-icon>
{{ it.text }} {{ it.text }}
</span> </span>
</v-list-item-content> </v-list-item-content>
<v-list-item-action> <v-list-item-action>
<span class="caption"> <span class="caption">
Column Column
@ -87,6 +92,9 @@
<span <span
class="caption indigo--text text--darken-3 font-weight-bold" 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 }} {{ it.text }}
</span> </span>
</v-list-item-content> </v-list-item-content>
@ -109,6 +117,7 @@
import debounce from 'debounce' import debounce from 'debounce'
import jsep from 'jsep' import jsep from 'jsep'
import { UITypes, jsepCurlyHook } from 'nocodb-sdk' import { UITypes, jsepCurlyHook } from 'nocodb-sdk'
import { getUIDTIcon } from '../../helpers/uiTypes'
import formulaList, { formulas, formulaTypes } from '../../../../../helpers/formulaList' import formulaList, { formulas, formulaTypes } from '../../../../../helpers/formulaList'
import { getWordUntilCaret, insertAtCursor } from '@/helpers' import { getWordUntilCaret, insertAtCursor } from '@/helpers'
import NcAutocompleteTree from '@/helpers/NcAutocompleteTree' 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 => ({ ...this.meta.columns.filter(c => !this.column || this.column.id !== c.id).map(c => ({
text: c.title, text: c.title,
type: 'column', type: 'column',
icon: getUIDTIcon(c.uidt),
c c
})), })),
...this.availableBinOps.map(op => ({ ...this.availableBinOps.map(op => ({
@ -534,13 +544,18 @@ export default {
return 'N/A' 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) { appendText(it) {
const text = it.text const text = it.text
const len = this.wordToComplete.length const len = this.wordToComplete.length
if (it.type === 'function') { if (it.type === 'function') {
this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1)) this.$set(this.formula, 'value', insertAtCursor(this.$refs.input.$el.querySelector('input'), text, len, 1))
} else if (it.type === 'column') { } 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 { } else {
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))
} }
@ -560,6 +575,9 @@ export default {
const parts = query.split(/\W+/) const parts = query.split(/\W+/)
this.wordToComplete = parts.pop() this.wordToComplete = parts.pop()
this.suggestion = this.acTree.complete(this.wordToComplete)?.sort((x, y) => this.sortOrder[x.type] - this.sortOrder[y.type]) 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 this.autocomplete = !!this.suggestion.length
}, },
selectText() { selectText() {

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

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

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

@ -612,7 +612,21 @@ export default async function formulaQueryBuilderv2(
return knex.raw( return knex.raw(
`${pt.callee.name}(${pt.arguments `${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}` .join()})${colAlias}`
); );
} else if (pt.type === 'Literal') { } else if (pt.type === 'Literal') {
@ -637,13 +651,16 @@ export default async function formulaQueryBuilderv2(
pt.left.fnName = pt.left.fnName || 'ARITH'; pt.left.fnName = pt.left.fnName || 'ARITH';
pt.right.fnName = pt.right.fnName || 'ARITH'; pt.right.fnName = pt.right.fnName || 'ARITH';
const query = knex.raw( const left = fn(pt.left, null, pt.operator).toQuery();
`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn( const right = fn(pt.right, null, pt.operator).toQuery();
pt.right, let sql = `${left} ${pt.operator} ${right}${colAlias}`;
null,
pt.operator // handle NULL values when calling CONCAT for sqlite3
).toQuery()}${colAlias}` 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) { if (prevBinaryOp && pt.operator !== prevBinaryOp) {
query.wrap('(', ')'); 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 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, formula,
[new_column] [new_column]
); );
await FormulaColumn.update(f.id, { await FormulaColumn.update(c.id, {
formula_raw: new_formula_raw formula_raw: new_formula_raw
}); });
} }

Loading…
Cancel
Save