From c7ea4f82b5f2575c90454b0e36340d125c9aab18 Mon Sep 17 00:00:00 2001
From: Pranav C <61551451+pranavxc@users.noreply.github.com>
Date: Mon, 2 Aug 2021 20:22:25 +0530
Subject: [PATCH] feat: Formula column creation
Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
---
.../spreadsheet/components/editColumn.vue | 610 +++++++++---------
.../components/editColumn/formulaOptions.vue | 33 +-
.../spreadsheet/components/virtualCell.vue | 10 +
.../components/virtualCell/formulaCell.vue | 14 +
.../components/virtualHeaderCell.vue | 4 +-
packages/nocodb/src/__tests__/formula.test.ts | 18 +-
.../dataMapper/lib/sql/formulaQueryBuilder.ts | 18 +-
7 files changed, 385 insertions(+), 322 deletions(-)
create mode 100644 packages/nc-gui/components/project/spreadsheet/components/virtualCell/formulaCell.vue
diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
index c53bba7605..1711c49bb3 100644
--- a/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
+++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
@@ -36,308 +36,310 @@
@input="newColumn.altered = newColumn.altered || 8"
/>
-
-
-
-
- Foreign Key
-
-
-
- mdi-table
-
-
{{ relation._rtn }}
-
- mdi-delete-forever
-
-
(v)
-
-
-
-
-
-
-
-
- {{ item.icon }}
-
- {{ item.name }}
-
-
+
+
+
+
+ Foreign Key
+
+
+
+ mdi-table
+
+
{{ relation._rtn }}
+
+ mdi-delete-forever
+
+
(v)
+
+
+
+
+
+
+
+
+ {{ item.icon }}
+
+ {{ item.name }}
+
+
-
-
-
- {{ item.icon }}
-
- {{ item.name }}
-
-
-
+
+
+
+ {{ item.icon }}
+
+ {{ item.name }}
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- NN
-
-
-
-
- Not Null
-
-
-
-
-
-
- PK
-
-
-
-
- Primary Key
-
-
-
-
-
-
-
- AI
-
-
-
-
- Auto Increment
-
-
-
-
-
-
-
- UN
-
-
-
-
- Unsigned
-
-
-
-
-
-
-
- AU
-
-
-
-
- Auto Update Timestamp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NN
+
+
+
+
+ Not Null
+
+
+
+
+
+
+ PK
+
+
+
+
+ Primary Key
+
+
+
+
+
+
+
+ AI
+
+
+
+
+ Auto Increment
+
+
+
+
+
+
+
+ UN
+
+
+
+
+ Unsigned
+
+
+
+
+
+
+
+ AU
+
+
+
+
+ Auto Update Timestamp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
+
+
-
-
-
-
- This spreadsheet is connected to an SQLite DB.
- For production please see here.
-
-
-
+
+
+
+ This spreadsheet is connected to an SQLite DB.
+ For production please see here.
+
+
+
+
@@ -495,9 +498,9 @@ export default {
return
}
try {
- if (this.newColumn.uidt === 'Formula') {
- return this.$toast.info('Coming Soon...').goAway(3000)
- }
+ // if (this.newColumn.uidt === 'Formula') {
+ // return this.$toast.info('Coming Soon...').goAway(3000)
+ // }
if (this.isLinkToAnotherRecord && this.$refs.relation) {
await this.$refs.relation.saveRelation()
@@ -506,6 +509,9 @@ export default {
if (this.isLookup && this.$refs.lookup) {
return await this.$refs.lookup.save()
}
+ if (this.newColumn.uidt === 'Formula' && this.$refs.formula) {
+ return await this.$refs.formula.save()
+ }
this.newColumn.tn = this.nodes.tn
this.newColumn._cn = this.newColumn.cn
diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
index 12dff6199d..5a7f1afe59 100644
--- a/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
+++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
@@ -1,6 +1,7 @@
({
formula: {},
formulas: ['AVERAGE()', 'COUNT()', 'COUNTA()', 'COUNTALL()', 'SUM()', 'MIN()', 'MAX()', 'AND()', 'OR()', 'TRUE()', 'FALSE()', 'NOT()', 'XOR()', 'ISERROR()', 'IF()', 'LEN()', 'MID()', 'LEFT()', 'RIGHT()', 'FIND()', 'CONCATENATE()', 'T()', 'VALUE()', 'ARRAYJOIN()', 'ARRAYUNIQUE()', 'ARRAYCOMPACT()', 'ARRAYFLATTEN()', 'ROUND()', 'ROUNDUP()', 'ROUNDDOWN()', 'INT()', 'EVEN()', 'ODD()', 'MOD()', 'LOG()', 'EXP()', 'POWER()', 'SQRT()', 'CEILING()', 'FLOOR()', 'ABS()', 'RECORD_ID()', 'CREATED_TIME()', 'ERROR()', 'BLANK()', 'YEAR()', 'MONTH()', 'DAY()', 'HOUR()', 'MINUTE()', 'SECOND()', 'TODAY()', 'NOW()', 'WORKDAY()', 'DATETIME_PARSE()', 'DATETIME_FORMAT()', 'SET_LOCALE()', 'SET_TIMEZONE()', 'DATESTR()', 'TIMESTR()', 'TONOW()', 'FROMNOW()', 'DATEADD()', 'WEEKDAY()', 'WEEKNUM()', 'DATETIME_DIFF()', 'WORKDAY_DIFF()', 'IS_BEFORE()', 'IS_SAME()', 'IS_AFTER()', 'REPLACE()', 'REPT()', 'LOWER()', 'UPPER()', 'TRIM()', 'SUBSTITUTE()', 'SEARCH()', 'SWITCH()', 'LAST_MODIFIED_TIME()', 'ENCODE_URL_COMPONENT()', 'REGEX_EXTRACT()', 'REGEX_MATCH()', 'REGEX_REPLACE()']
- })
+ }),
+ methods: {
+ async save() {
+ try {
+ await this.$store.dispatch('meta/ActLoadMeta', {
+ dbAlias: this.nodes.dbAlias,
+ env: this.nodes.env,
+ tn: this.meta.tn,
+ force: true
+ })
+ const meta = JSON.parse(JSON.stringify(this.$store.state.meta.metas[this.meta.tn]))
+
+ meta.v.push({
+ _cn: this.alias,
+ formula: this.formula
+ })
+
+ await this.$store.dispatch('sqlMgr/ActSqlOp', [{
+ env: this.nodes.env,
+ dbAlias: this.nodes.dbAlias
+ }, 'xcModelSet', {
+ tn: this.nodes.tn,
+ meta
+ }])
+
+ return this.$emit('saved', this.alias)
+ } catch (e) {
+ this.$toast.error(e.message).goAway(3000)
+ }
+ }
+ }
}
diff --git a/packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue b/packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
index e61bc952da..c2401ef6d6 100644
--- a/packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
+++ b/packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
@@ -63,11 +63,17 @@
:column="column"
v-on="$listeners "
/>
+
+
+
diff --git a/packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue b/packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
index 8d7ca27d7b..34970e8f82 100644
--- a/packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
+++ b/packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
@@ -11,6 +11,9 @@
mdi-table-network
+
+ mdi-math-integral
+
mdi-table-column-plus-before
@@ -24,7 +27,6 @@
-
*
diff --git a/packages/nocodb/src/__tests__/formula.test.ts b/packages/nocodb/src/__tests__/formula.test.ts
index 73922a2ba8..3f60128298 100644
--- a/packages/nocodb/src/__tests__/formula.test.ts
+++ b/packages/nocodb/src/__tests__/formula.test.ts
@@ -34,21 +34,21 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
expect(formulaQueryBuilder("concat(city, ' _ ',city_id+country_id)", 'a',knexMysqlRef).toQuery()).eq('concat(`city`,\' _ \',`city_id` + `country_id`) as a')
expect(formulaQueryBuilder("concat(city, ' _ ',city_id+country_id)", 'a',knexPgRef).toQuery()).eq('concat("city",\' _ \',"city_id" + "country_id") as a')
expect(formulaQueryBuilder("concat(city, ' _ ',city_id+country_id)", 'a',knexMssqlRef).toQuery()).eq('concat([city],\' _ \',[city_id] + [country_id]) as a')
- expect(formulaQueryBuilder("concat(city, ' _ ',city_id+country_id)", 'a',knexSqliteRef).toQuery()).eq('`city` || (\' _ \' || (`city_id` + `country_id`)) as a')
+ expect(formulaQueryBuilder("concat(city, ' _ ',city_id+country_id)", 'a',knexSqliteRef).toQuery()).eq('`city` || \' _ \' || (`city_id` + `country_id`) as a')
done()
});
it('Addition', function (done) {
- expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('`city_id` + (`country_id` + (2 + (3 + (4 + (5 + 4))))) as a')
- expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('"city_id" + ("country_id" + (2 + (3 + (4 + (5 + 4))))) as a')
- expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('[city_id] + ([country_id] + (2 + (3 + (4 + (5 + 4))))) as a')
- expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('`city_id` + (`country_id` + (2 + (3 + (4 + (5 + 4))))) as a')
+ expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a')
+ expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('"city_id" + "country_id" + 2 + 3 + 4 + 5 + 4 as a')
+ expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('[city_id] + [country_id] + 2 + 3 + 4 + 5 + 4 as a')
+ expect(formulaQueryBuilder("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a')
done()
});
it('Average', function (done) {
- expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('(`city_id` + (`country_id` + (2 + (3 + (4 + (5 + 4)))))) / 7 as a')
- expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('("city_id" + ("country_id" + (2 + (3 + (4 + (5 + 4)))))) / 7 as a')
- expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('([city_id] + ([country_id] + (2 + (3 + (4 + (5 + 4)))))) / 7 as a')
- expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('(`city_id` + (`country_id` + (2 + (3 + (4 + (5 + 4)))))) / 7 as a')
+ expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a')
+ expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('("city_id" + "country_id" + 2 + 3 + 4 + 5 + 4) / 7 as a')
+ expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('([city_id] + [country_id] + 2 + 3 + 4 + 5 + 4) / 7 as a')
+ expect(formulaQueryBuilder("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a')
done()
});
});
diff --git a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilder.ts b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilder.ts
index 0fa0308db2..cf5404f7e6 100644
--- a/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilder.ts
+++ b/packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilder.ts
@@ -4,7 +4,7 @@ import jsep from 'jsep';
// todo: switch function based on database
export default function formulaQueryBuilder(str, alias, knex) {
- const fn = (pt, a?, nestedBinary?) => {
+ const fn = (pt, a?, prevBinaryOp ?) => {
const colAlias = a ? ` as ${a}` : '';
if (pt.type === 'CallExpression') {
switch (pt.callee.name) {
@@ -16,9 +16,9 @@ export default function formulaQueryBuilder(str, alias, knex) {
operator: '+',
left: pt.arguments[0],
right: {...pt, arguments: pt.arguments.slice(1)}
- }, a, nestedBinary)
+ }, a, prevBinaryOp)
} else {
- return fn(pt.arguments[0], a, nestedBinary)
+ return fn(pt.arguments[0], a, prevBinaryOp)
}
break;
case 'AVG':
@@ -28,9 +28,9 @@ export default function formulaQueryBuilder(str, alias, knex) {
operator: '/',
left: {...pt, callee: {name: 'SUM'}},
right: {type: 'Literal', value: pt.arguments.length}
- }, a, nestedBinary)
+ }, a, prevBinaryOp)
} else {
- return fn(pt.arguments[0], a, nestedBinary)
+ return fn(pt.arguments[0], a, prevBinaryOp)
}
break;
case 'concat':
@@ -42,9 +42,9 @@ export default function formulaQueryBuilder(str, alias, knex) {
operator: '||',
left: pt.arguments[0],
right: {...pt, arguments: pt.arguments.slice(1)}
- }, a, nestedBinary)
+ }, a, prevBinaryOp)
} else {
- return fn(pt.arguments[0], a, nestedBinary)
+ return fn(pt.arguments[0], a, prevBinaryOp)
}
}
break;
@@ -56,8 +56,8 @@ export default function formulaQueryBuilder(str, alias, knex) {
} else if (pt.type === 'Identifier') {
return knex.raw(`??${colAlias}`, [pt.name]);
} else if (pt.type === 'BinaryExpression') {
- const query = knex.raw(`${fn(pt.left, null, true).toQuery()} ${pt.operator} ${fn(pt.right, null, true).toQuery()}${colAlias}`)
- if (nestedBinary) {
+ const query = knex.raw(`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(pt.right, null, pt.operator).toQuery()}${colAlias}`)
+ if (prevBinaryOp && pt.operator !== prevBinaryOp) {
query.wrap('(', ')')
}
return query;