mirror of https://github.com/nocodb/nocodb
8 changed files with 222 additions and 13 deletions
@ -0,0 +1,26 @@
|
||||
<template> |
||||
<v-text-field |
||||
v-model="formula.value" |
||||
outlined |
||||
class="caption" |
||||
hide-details="auto" |
||||
label="Formula" |
||||
persistent-hint |
||||
hint="Available formulas are ADD, AVG, CONCAT, +, -, /" |
||||
/> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'FormulaOptions', |
||||
props: ['nodes', 'column', 'meta', 'isSQLite', 'alias'], |
||||
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()'] |
||||
}) |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,77 @@
|
||||
import {expect} from 'chai'; |
||||
import 'mocha'; |
||||
import knex from '../lib/dataMapper/lib/sql/CustomKnex'; |
||||
import formulaQueryBuilder from "../lib/dataMapper/lib/sql/formulaQueryBuilder"; |
||||
|
||||
process.env.TEST = 'test'; |
||||
|
||||
describe('{Auth, CRUD, HasMany, Belongs} Tests', () => { |
||||
|
||||
let knexMysqlRef; |
||||
let knexPgRef; |
||||
let knexMssqlRef; |
||||
let knexSqliteRef; |
||||
|
||||
|
||||
// Called once before any of the tests in this block begin.
|
||||
before(function (done) { |
||||
knexMysqlRef = knex({client:'mysql2'}) |
||||
knexMssqlRef = knex({client:'mssql'}) |
||||
knexPgRef = knex({client:'pg'}) |
||||
knexSqliteRef = knex({client:'sqlite3'}) |
||||
done() |
||||
}); |
||||
|
||||
|
||||
after((done) => { |
||||
done(); |
||||
}); |
||||
|
||||
|
||||
describe('Formulas', function () { |
||||
|
||||
it('Simple formula', function (done) { |
||||
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') |
||||
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') |
||||
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') |
||||
done() |
||||
}); |
||||
}); |
||||
|
||||
});/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Naveen MR <oof1lab@gmail.com> |
||||
* @author Pranav C Balan <pranavxc@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
*"concat(city, ' _ ',city_id+country_id)", 'a' |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
@ -0,0 +1,70 @@
|
||||
import jsep from 'jsep'; |
||||
|
||||
|
||||
// todo: switch function based on database
|
||||
|
||||
export default function formulaQueryBuilder(str, alias, knex) { |
||||
const fn = (pt, a?, nestedBinary?) => { |
||||
const colAlias = a ? ` as ${a}` : ''; |
||||
if (pt.type === 'CallExpression') { |
||||
switch (pt.callee.name) { |
||||
case 'ADD': |
||||
case 'SUM': |
||||
if (pt.arguments.length > 1) { |
||||
return fn({ |
||||
type: 'BinaryExpression', |
||||
operator: '+', |
||||
left: pt.arguments[0], |
||||
right: {...pt, arguments: pt.arguments.slice(1)} |
||||
}, a, nestedBinary) |
||||
} else { |
||||
return fn(pt.arguments[0], a, nestedBinary) |
||||
} |
||||
break; |
||||
case 'AVG': |
||||
if (pt.arguments.length > 1) { |
||||
return fn({ |
||||
type: 'BinaryExpression', |
||||
operator: '/', |
||||
left: {...pt, callee: {name: 'SUM'}}, |
||||
right: {type: 'Literal', value: pt.arguments.length} |
||||
}, a, nestedBinary) |
||||
} else { |
||||
return fn(pt.arguments[0], a, nestedBinary) |
||||
} |
||||
break; |
||||
case 'concat': |
||||
case 'CONCAT': |
||||
if (knex.clientType() === 'sqlite3') { |
||||
if (pt.arguments.length > 1) { |
||||
return fn({ |
||||
type: 'BinaryExpression', |
||||
operator: '||', |
||||
left: pt.arguments[0], |
||||
right: {...pt, arguments: pt.arguments.slice(1)} |
||||
}, a, nestedBinary) |
||||
} else { |
||||
return fn(pt.arguments[0], a, nestedBinary) |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
|
||||
return knex.raw(`${pt.callee.name}(${pt.arguments.map(arg => fn(arg).toQuery()).join()})${colAlias}`) |
||||
} else if (pt.type === 'Literal') { |
||||
return knex.raw(`?${colAlias}`, [pt.value]); |
||||
} 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) { |
||||
query.wrap('(', ')') |
||||
} |
||||
return query; |
||||
} |
||||
}; |
||||
return fn(jsep(str), alias) |
||||
} |
||||
|
||||
|
||||
|
Loading…
Reference in new issue