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