Browse Source

feat: Formula column creation

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/448/head
Pranav C 3 years ago committed by Pranav C
parent
commit
c7ea4f82b5
  1. 610
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 33
      packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue
  3. 10
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  4. 14
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/formulaCell.vue
  5. 4
      packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue
  6. 18
      packages/nocodb/src/__tests__/formula.test.ts
  7. 18
      packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilder.ts

610
packages/nc-gui/components/project/spreadsheet/components/editColumn.vue

@ -36,308 +36,310 @@
@input="newColumn.altered = newColumn.altered || 8"
/>
</v-col>
<div
<v-container
fluid
:class="{
editDisabled :isEditDisabled
}"
>
<v-col v-if="relation" cols="12">
<div class="caption">
<p class="mb-1">
Foreign Key
</p>
<v-icon small class="mt-n1">
mdi-table
</v-icon>
<span class="text-capitalize font-weight-bold body-1"> {{ relation._rtn }}</span>
<v-icon
v-ge="['columns','fk-delete']"
small
class="ml-3 mt-n1"
color="error"
@click="deleteRelation('showDialog', column)"
>
mdi-delete-forever
</v-icon>
<span v-if="relation.type=== 'virtual'" class="caption">(v)</span>
</div>
</v-col>
<template v-else>
<v-col cols="12">
<v-autocomplete
v-model="newColumn.uidt"
hide-details
item-value="name"
item-text="name"
class="caption ui-type"
:class="{'primary lighten-5' : newColumn.uidt }"
label="Column type"
dense
outlined
:items="uiTypes"
@change="onUiTypeChange"
>
<template #selection="{item}">
<div>
<v-icon color="grey darken-4" small class="mr-1">
{{ item.icon }}
</v-icon>
<span class="caption grey--text text--darken-4"> {{ item.name }}</span>
</div>
</template>
<v-row>
<v-col v-if="relation" cols="12">
<div class="caption">
<p class="mb-1">
Foreign Key
</p>
<v-icon small class="mt-n1">
mdi-table
</v-icon>
<span class="text-capitalize font-weight-bold body-1"> {{ relation._rtn }}</span>
<v-icon
v-ge="['columns','fk-delete']"
small
class="ml-3 mt-n1"
color="error"
@click="deleteRelation('showDialog', column)"
>
mdi-delete-forever
</v-icon>
<span v-if="relation.type=== 'virtual'" class="caption">(v)</span>
</div>
</v-col>
<template v-else>
<v-col cols="12">
<v-autocomplete
v-model="newColumn.uidt"
hide-details
item-value="name"
item-text="name"
class="caption ui-type"
:class="{'primary lighten-5' : newColumn.uidt }"
label="Column type"
dense
outlined
:items="uiTypes"
@change="onUiTypeChange"
>
<template #selection="{item}">
<div>
<v-icon color="grey darken-4" small class="mr-1">
{{ item.icon }}
</v-icon>
<span class="caption grey--text text--darken-4"> {{ item.name }}</span>
</div>
</template>
<template #item="{item}">
<div class="caption">
<v-icon small class="mr-1">
{{ item.icon }}
</v-icon>
{{ item.name }}
</div>
</template>
</v-autocomplete>
<template #item="{item}">
<div class="caption">
<v-icon small class="mr-1">
{{ item.icon }}
</v-icon>
{{ item.name }}
</div>
</template>
</v-autocomplete>
<!-- <v-list dense max-height="calc(100vh - 300px)" style="overflow: auto">-->
<!-- <v-list-item v-for="item in uiTypes" @click.stop :key="item">-->
<!-- <span class="caption">{{ item }}</span>-->
<!-- </v-list-item>-->
<!-- </v-list>-->
</v-col>
<template v-if="newColumn.uidt !== 'Formula'">
<v-col
v-if="isLookup"
cols="12"
>
<lookup-options
ref="lookup"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
v-on="$listeners"
/>
</v-col>
<v-col
v-if="isLinkToAnotherRecord"
cols="12"
>
<linked-to-another-options
ref="relation"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
@onColumnSelect="onRelColumnSelect"
/>
</v-col>
<v-col
v-if="isRelation"
cols="12"
>
<relation-options
ref="relation"
:column="newColumn"
:nodes="nodes"
:is-m-s-s-q-l="isMSSQL"
:is-s-q-lite="isSQLite"
@onColumnSelect="onRelColumnSelect"
/>
</v-col>
<v-col v-if="isSelect" cols="12">
<custom-select-options
v-model="newColumn.dtxp"
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<template v-if="newColumn.uidt !== 'Formula'">
<v-col
v-if="isLookup"
cols="12"
>
<lookup-options
ref="lookup"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
v-on="$listeners"
/>
</v-col>
<v-col
v-if="isLinkToAnotherRecord"
cols="12"
>
<linked-to-another-options
ref="relation"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
@onColumnSelect="onRelColumnSelect"
/>
</v-col>
<v-col
v-if="isRelation"
cols="12"
>
<relation-options
ref="relation"
:column="newColumn"
:nodes="nodes"
:is-m-s-s-q-l="isMSSQL"
:is-s-q-lite="isSQLite"
@onColumnSelect="onRelColumnSelect"
/>
</v-col>
<template v-if="newColumn.cn && newColumn.uidt && !isLinkToAnotherRecord && !isLookup">
<v-col cols="12">
<v-container fluid class="wrapper">
<v-row>
<v-col cols="12">
<div class="d-flex justify-space-between caption">
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.rqd"
:disabled="newColumn.pk || !sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="NN"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">NN</span>
</template>
</v-checkbox>
</div>
</template>
<span>Not Null</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.pk"
:disabled="!sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="PK"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">PK</span>
</template>
</v-checkbox>
</div>
</template>
<span>Primary Key</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.ai"
:disabled="sqlUi.colPropUNDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="AI"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">AI</span>
</template>
</v-checkbox>
</div>
</template>
<span>Auto Increment</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.un"
class="mr-2 mt-0"
dense
hide-details
label="UN"
:disabled="sqlUi.colPropUNDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">UN</span>
</template>
</v-checkbox>
</div>
</template>
<span>Unsigned</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.au"
class="mr-2 mt-0"
dense
hide-details
label="UN"
:disabled=" sqlUi.colPropAuDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">AU</span>
</template>
</v-checkbox>
</div>
</template>
<span>Auto Update Timestamp</span>
</v-tooltip>
</div>
</v-col>
<v-col cols="12">
<v-autocomplete
v-model="newColumn.dt"
hide-details
class="caption data-type"
label="Type in Database"
dense
outlined
:items="dataTypes"
@change="onDataTypeChange"
/>
</v-col>
<v-col :cols="sqlUi.showScale(newColumn) && !isSelect ? 6 : 12">
<v-text-field
v-if="!isSelect"
v-model="newColumn.dtxp"
dense
:disabled="sqlUi.getDefaultLengthIsDisabled(newColumn.dt) || !sqlUi.columnEditable(newColumn)"
class="caption"
label="Length / Values"
outlined
hide-details
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<v-col v-if="sqlUi.showScale(newColumn)" :cols="isSelect ?12 : 6">
<v-text-field
v-model="newColumn.dtxs"
dense
:disabled=" !sqlUi.columnEditable(newColumn)"
class="caption"
label="Scale"
outlined
hide-details
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="newColumn.cdf"
label="Default value"
:hint="sqlUi.getDefaultValueForDatatype(newColumn.dt)"
persistent-hint
rows="3"
outlined
dense
class="caption"
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
</v-row>
</v-container>
<v-col v-if="isSelect" cols="12">
<custom-select-options
v-model="newColumn.dtxp"
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<template v-if="newColumn.cn && newColumn.uidt && !isLinkToAnotherRecord && !isLookup">
<v-col cols="12">
<v-container fluid class="wrapper">
<v-row>
<v-col cols="12">
<div class="d-flex justify-space-between caption">
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.rqd"
:disabled="newColumn.pk || !sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="NN"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">NN</span>
</template>
</v-checkbox>
</div>
</template>
<span>Not Null</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.pk"
:disabled="!sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="PK"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">PK</span>
</template>
</v-checkbox>
</div>
</template>
<span>Primary Key</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.ai"
:disabled="sqlUi.colPropUNDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
class="mr-2 mt-0"
dense
hide-details
label="AI"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">AI</span>
</template>
</v-checkbox>
</div>
</template>
<span>Auto Increment</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.un"
class="mr-2 mt-0"
dense
hide-details
label="UN"
:disabled="sqlUi.colPropUNDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">UN</span>
</template>
</v-checkbox>
</div>
</template>
<span>Unsigned</span>
</v-tooltip>
<v-tooltip bottom z-index="99999">
<template #activator="{on}">
<div v-on="on">
<v-checkbox
v-model="newColumn.au"
class="mr-2 mt-0"
dense
hide-details
label="UN"
:disabled=" sqlUi.colPropAuDisabled(newColumn) || !sqlUi.columnEditable(newColumn)"
@input="newColumn.altered = newColumn.altered || 2"
>
<template #label>
<span class="caption font-weight-bold">AU</span>
</template>
</v-checkbox>
</div>
</template>
<span>Auto Update Timestamp</span>
</v-tooltip>
</div>
</v-col>
<v-col cols="12">
<v-autocomplete
v-model="newColumn.dt"
hide-details
class="caption data-type"
label="Type in Database"
dense
outlined
:items="dataTypes"
@change="onDataTypeChange"
/>
</v-col>
<v-col :cols="sqlUi.showScale(newColumn) && !isSelect ? 6 : 12">
<v-text-field
v-if="!isSelect"
v-model="newColumn.dtxp"
dense
:disabled="sqlUi.getDefaultLengthIsDisabled(newColumn.dt) || !sqlUi.columnEditable(newColumn)"
class="caption"
label="Length / Values"
outlined
hide-details
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<v-col v-if="sqlUi.showScale(newColumn)" :cols="isSelect ?12 : 6">
<v-text-field
v-model="newColumn.dtxs"
dense
:disabled=" !sqlUi.columnEditable(newColumn)"
class="caption"
label="Scale"
outlined
hide-details
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="newColumn.cdf"
label="Default value"
:hint="sqlUi.getDefaultValueForDatatype(newColumn.dt)"
persistent-hint
rows="3"
outlined
dense
class="caption"
@input="newColumn.altered = newColumn.altered || 2"
/>
</v-col>
</v-row>
</v-container>
</v-col>
</template>
</template>
</template>
<template v-else>
<v-col cols12>
<formula-options
ref="formula"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
v-on="$listeners"
/>
<template v-else>
<v-col cols="12">
<formula-options
ref="formula"
:column="newColumn"
:nodes="nodes"
:meta="meta"
:is-s-q-lite="isSQLite"
:alias="newColumn.cn"
:is-m-s-s-q-l="isMSSQL"
v-on="$listeners"
/>
<!-- <v-autocomplete
label="Formula"
@ -351,20 +353,21 @@
<span class="green&#45;&#45;text text&#45;&#45;darken-2 caption font-weight-regular">{{ item }}</span>
</template>
</v-autocomplete>-->
</v-col>
</v-col>
</template>
</template>
</template>
<div class="disabled-info" :class="{'d-none':!isEditDisabled}">
<v-alert dense type="warning" icon="info" class="caption mx-2" outlined>
This spreadsheet is connected to an SQLite DB.<br>
For production please see <a
href="https://github.com/nocodb/nocodb#production-setup"
target="_blank"
>here</a>.
</v-alert>
</div>
</div>
<div class="disabled-info" :class="{'d-none':!isEditDisabled}">
<v-alert dense type="warning" icon="info" class="caption mx-2" outlined>
This spreadsheet is connected to an SQLite DB.<br>
For production please see <a
href="https://github.com/nocodb/nocodb#production-setup"
target="_blank"
>here</a>.
</v-alert>
</div>
</v-row>
</v-container>
</v-row>
</v-container>
</v-form>
@ -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

33
packages/nc-gui/components/project/spreadsheet/components/editColumn/formulaOptions.vue

@ -1,6 +1,7 @@
<template>
<v-text-field
v-model="formula.value"
dense
outlined
class="caption"
hide-details="auto"
@ -17,7 +18,37 @@ export default {
data: () => ({
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)
}
}
}
}
</script>

10
packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue

@ -63,11 +63,17 @@
:column="column"
v-on="$listeners "
/>
<formula-cell
v-else-if="formula"
:row="row"
:column="column"
/>
</v-lazy>
</div>
</template>
<script>
import FormulaCell from '@/components/project/spreadsheet/components/virtualCell/formulaCell'
import hasManyCell from '@/components/project/spreadsheet/components/virtualCell/hasManyCell'
import LookupCell from '@/components/project/spreadsheet/components/virtualCell/lookupCell'
import manyToManyCell from '@/components/project/spreadsheet/components/virtualCell/manyToManyCell'
@ -78,6 +84,7 @@ import belongsToCell from '@/components/project/spreadsheet/components/virtualCe
export default {
name: 'VirtualCell',
components: {
FormulaCell,
LookupCell,
belongsToCell,
manyToManyCell,
@ -119,6 +126,9 @@ export default {
},
lookup() {
return this.column && this.column.lk
},
formula() {
return this.column && this.column.formula
}
},
methods: {

14
packages/nc-gui/components/project/spreadsheet/components/virtualCell/formulaCell.vue

@ -0,0 +1,14 @@
<template>
<div>{{ row[column._cn] }}</div>
</template>
<script>
export default {
name: 'FormulaCell',
props: ['column', 'row']
}
</script>
<style scoped>
</style>

4
packages/nc-gui/components/project/spreadsheet/components/virtualHeaderCell.vue

@ -11,6 +11,9 @@
<v-icon v-else-if="column.mm" color="pink" x-small class="mr-1" v-on="on">
mdi-table-network
</v-icon>
<v-icon v-else-if="column.formula" x-small class="mr-1" v-on="on">
mdi-math-integral
</v-icon>
<template v-else-if="column.lk">
<v-icon v-if="column.lk.type === 'hm'" color="warning" x-small class="mr-1" v-on="on">
mdi-table-column-plus-before
@ -24,7 +27,6 @@
</template>
<span class="name flex-grow-1" :title="column._cn" v-on="on" v-html="alias">
<span v-if="column.rqd" class="error--text text--lighten-1" v-on="on">&nbsp;*</span>
</span>
</template>

18
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()
});
});

18
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;

Loading…
Cancel
Save